项目初始化

This commit is contained in:
leilei
2025-09-19 17:24:46 +08:00
commit 293951a610
107 changed files with 10222 additions and 0 deletions

30
src/utils/auth.js Normal file
View File

@@ -0,0 +1,30 @@
import Cookies from "js-cookie";
const TokenKey = "token";
const ExpiresInKey = "Meta-Enterprise-Expires-In";
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token) {
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}
export function getExpiresIn() {
return Cookies.get(ExpiresInKey) || -1;
}
export function setExpiresIn(time) {
return Cookies.set(ExpiresInKey, time);
}
export function removeExpiresIn() {
return Cookies.remove(ExpiresInKey);
}

583
src/utils/bigDecimal.js Normal file
View File

@@ -0,0 +1,583 @@
var bigDecimal = (function (e) {
var n = {}
function t(r) {
if (n[r]) return n[r].exports
var i = (n[r] = { i: r, l: !1, exports: {} })
return e[r].call(i.exports, i, i.exports, t), (i.l = !0), i.exports
}
return (
(t.m = e),
(t.c = n),
(t.d = function (e, n, r) {
t.o(e, n) || Object.defineProperty(e, n, { enumerable: !0, get: r })
}),
(t.r = function (e) {
'undefined' != typeof Symbol &&
Symbol.toStringTag &&
Object.defineProperty(e, Symbol.toStringTag, { value: 'Module' }),
Object.defineProperty(e, '__esModule', { value: !0 })
}),
(t.t = function (e, n) {
if ((1 & n && (e = t(e)), 8 & n)) return e
if (4 & n && 'object' == typeof e && e && e.__esModule) return e
var r = Object.create(null)
if (
(t.r(r),
Object.defineProperty(r, 'default', { enumerable: !0, value: e }),
2 & n && 'string' != typeof e)
)
for (var i in e)
t.d(
r,
i,
function (n) {
return e[n]
}.bind(null, i)
)
return r
}),
(t.n = function (e) {
var n =
e && e.__esModule
? function () {
return e.default
}
: function () {
return e
}
return t.d(n, 'a', n), n
}),
(t.o = function (e, n) {
return Object.prototype.hasOwnProperty.call(e, n)
}),
(t.p = ''),
t((t.s = 6))
)
})([
function (e, n, t) {
'use strict'
function r(e) {
for (
var n = '',
t = e.length,
r = e.split('.')[1],
i = r ? r.length : 0,
o = 0;
o < t;
o++
)
e[o] >= '0' && e[o] <= '9' ? (n += 9 - parseInt(e[o])) : (n += e[o])
return u(n, i > 0 ? '0.' + new Array(i).join('0') + '1' : '1')
}
function i(e) {
var n = e.split('.')
for (n[0] || (n[0] = '0'); '0' == n[0][0] && n[0].length > 1;)
n[0] = n[0].substring(1)
return n[0] + (n[1] ? '.' + n[1] : '')
}
function o(e, n) {
var t = e.split('.'),
r = n.split('.'),
i = t[0].length,
o = r[0].length
return (
i > o
? (r[0] =
new Array(Math.abs(i - o) + 1).join('0') + (r[0] ? r[0] : ''))
: (t[0] =
new Array(Math.abs(i - o) + 1).join('0') + (t[0] ? t[0] : '')),
(i = t[1] ? t[1].length : 0),
(o = r[1] ? r[1].length : 0),
(i || o) &&
(i > o
? (r[1] =
(r[1] ? r[1] : '') + new Array(Math.abs(i - o) + 1).join('0'))
: (t[1] =
(t[1] ? t[1] : '') + new Array(Math.abs(i - o) + 1).join('0'))),
[
(e = t[0] + (t[1] ? '.' + t[1] : '')),
(n = r[0] + (r[1] ? '.' + r[1] : '')),
]
)
}
function u(e, n) {
var t
; (e = (t = o(e, n))[0]), (n = t[1])
for (var r = '', i = 0, u = e.length - 1; u >= 0; u--)
if ('.' !== e[u]) {
var a = parseInt(e[u]) + parseInt(n[u]) + i
; (r = (a % 10) + r), (i = Math.floor(a / 10))
} else r = '.' + r
return i ? i.toString() + r : r
}
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.pad = n.trim = n.add = void 0),
(n.add = function (e, n) {
var t
void 0 === n && (n = '0')
var a = 0,
s = -1
'-' == e[0] && (a++, (s = 1), (e = e.substring(1)).length),
'-' == n[0] && (a++, (s = 2), (n = n.substring(1)).length),
(e = i(e)),
(n = i(n)),
(e = (t = o(i(e), i(n)))[0]),
(n = t[1]),
1 == a && (1 == s ? (e = r(e)) : (n = r(n)))
var d = u(e, n)
return a
? 2 == a
? '-' + i(d)
: e.length < d.length
? i(d.substring(1))
: '-' + i(r(d))
: i(d)
}),
(n.trim = i),
(n.pad = o)
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.roundOff = void 0)
var r = t(2)
function i(e, n, t, i) {
if (!e || e === new Array(e.length + 1).join('0')) return !1
if (
i === r.RoundingModes.DOWN ||
(!t && i === r.RoundingModes.FLOOR) ||
(t && i === r.RoundingModes.CEILING)
)
return !1
if (
i === r.RoundingModes.UP ||
(t && i === r.RoundingModes.FLOOR) ||
(!t && i === r.RoundingModes.CEILING)
)
return !0
var o = '5' + new Array(e.length).join('0')
if (e > o) return !0
if (e < o) return !1
switch (i) {
case r.RoundingModes.HALF_DOWN:
return !1
case r.RoundingModes.HALF_UP:
return !0
case r.RoundingModes.HALF_EVEN:
default:
return parseInt(n[n.length - 1]) % 2 == 1
}
}
function o(e, n) {
void 0 === n && (n = 0),
n || (n = 1),
'number' == typeof e && e.toString()
for (var t = '', r = e.length - 1; r >= 0; r--) {
var i = parseInt(e[r]) + n
10 == i ? ((n = 1), (i = 0)) : (n = 0), (t += i)
}
return n && (t += n), t.split('').reverse().join('')
}
n.roundOff = function e(n, t, u) {
if (
(void 0 === t && (t = 0),
void 0 === u && (u = r.RoundingModes.HALF_EVEN),
u === r.RoundingModes.UNNECESSARY)
)
throw new Error(
'UNNECESSARY Rounding Mode has not yet been implemented'
)
'number' == typeof n && (n = n.toString())
var a = !1
'-' === n[0] && ((a = !0), (n = n.substring(1)))
var s = n.split('.'),
d = s[0],
l = s[1]
if (t < 0) {
if (((t = -t), d.length <= t)) return '0'
var f = d.substr(0, d.length - t)
return (
(a ? '-' : '') +
(f = e((n = f + '.' + d.substr(d.length - t) + l), 0, u)) +
new Array(t + 1).join('0')
)
}
if (0 == t) {
d.length
return i(s[1], d, a, u) ? (a ? '-' : '') + o(d) : (a ? '-' : '') + d
}
if (!s[1]) return (a ? '-' : '') + d + '.' + new Array(t + 1).join('0')
if (s[1].length < t)
return (
(a ? '-' : '') +
d +
'.' +
s[1] +
new Array(t - s[1].length + 1).join('0')
)
l = s[1].substring(0, t)
var g = s[1].substring(t)
return g && i(g, l, a, u) && (l = o(l)).length > t
? o(d, parseInt(l[0])) + '.' + l.substring(1)
: (a ? '-' : '') + d + '.' + l
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.RoundingModes = void 0),
(function (e) {
; (e[(e.CEILING = 0)] = 'CEILING'),
(e[(e.DOWN = 1)] = 'DOWN'),
(e[(e.FLOOR = 2)] = 'FLOOR'),
(e[(e.HALF_DOWN = 3)] = 'HALF_DOWN'),
(e[(e.HALF_EVEN = 4)] = 'HALF_EVEN'),
(e[(e.HALF_UP = 5)] = 'HALF_UP'),
(e[(e.UNNECESSARY = 6)] = 'UNNECESSARY'),
(e[(e.UP = 7)] = 'UP')
})(n.RoundingModes || (n.RoundingModes = {}))
},
function (e, n, t) {
'use strict'
function r(e) {
for (; '0' == e[0];) e = e.substr(1)
if (-1 != e.indexOf('.'))
for (; '0' == e[e.length - 1];) e = e.substr(0, e.length - 1)
return (
'' == e || '.' == e
? (e = '0')
: '.' == e[e.length - 1] && (e = e.substr(0, e.length - 1)),
'.' == e[0] && (e = '0' + e),
e
)
}
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.multiply = void 0),
(n.multiply = function (e, n) {
; (e = e.toString()), (n = n.toString())
var t = 0
'-' == e[0] && (t++, (e = e.substr(1))),
'-' == n[0] && (t++, (n = n.substr(1))),
(e = r(e)),
(n = r(n))
var i = 0,
o = 0
; -1 != e.indexOf('.') && (i = e.length - e.indexOf('.') - 1),
-1 != n.indexOf('.') && (o = n.length - n.indexOf('.') - 1)
var u = i + o
if (
((e = r(e.replace('.', ''))),
(n = r(n.replace('.', ''))),
e.length < n.length)
) {
var a = e
; (e = n), (n = a)
}
if ('0' == n) return '0'
for (
var s, d, l = n.length, f = 0, g = [], c = l - 1, v = '', p = 0;
p < l;
p++
)
g[p] = e.length - 1
for (p = 0; p < 2 * e.length; p++) {
for (var h = 0, b = n.length - 1; b >= c && b >= 0; b--)
g[b] > -1 &&
g[b] < e.length &&
(h += parseInt(e[g[b]--]) * parseInt(n[b]))
; (h += f), (f = Math.floor(h / 10)), (v = (h % 10) + v), c--
}
return (
(v = r(
((s = v),
0 == (d = u)
? s
: (s =
d >= s.length
? new Array(d - s.length + 1).join('0') + s
: s).substr(0, s.length - d) +
'.' +
s.substr(s.length - d, d))
)),
1 == t && (v = '-' + v),
v
)
})
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.divide = void 0)
var r = t(0),
i = t(1)
n.divide = function (e, n, t) {
if ((void 0 === t && (t = 8), 0 == n))
throw new Error('Cannot divide by 0')
if (
((e = e.toString()),
(n = n.toString()),
(e = e.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, '')),
(n = n.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, '')),
0 == e)
)
return '0'
var o = 0
'-' == n[0] && ((n = n.substring(1)), o++),
'-' == e[0] && ((e = e.substring(1)), o++)
var u = n.indexOf('.') > 0 ? n.length - n.indexOf('.') - 1 : -1
if (((n = r.trim(n.replace('.', ''))), u >= 0)) {
var a = e.indexOf('.') > 0 ? e.length - e.indexOf('.') - 1 : -1
if (-1 == a) e = r.trim(e + new Array(u + 1).join('0'))
else if (u > a)
(e = e.replace('.', '')),
(e = r.trim(e + new Array(u - a + 1).join('0')))
else if (u < a) {
var s = (e = e.replace('.', '')).length - a + u
e = r.trim(e.substring(0, s) + '.' + e.substring(s))
} else u == a && (e = r.trim(e.replace('.', '')))
}
var d = 0,
l = n.length,
f = '',
g =
e.indexOf('.') > -1 && e.indexOf('.') < l
? e.substring(0, l + 1)
: e.substring(0, l)
if (
((e =
e.indexOf('.') > -1 && e.indexOf('.') < l
? e.substring(l + 1)
: e.substring(l)),
g.indexOf('.') > -1)
) {
var c = g.length - g.indexOf('.') - 1
l > (g = g.replace('.', '')).length &&
((c += l - g.length), (g += new Array(l - g.length + 1).join('0'))),
(d = c),
(f = '0.' + new Array(c).join('0'))
}
for (t += 2; d <= t;) {
for (var v = 0; parseInt(g) >= parseInt(n);)
(g = r.add(g, '-' + n)), v++
; (f += v),
e
? ('.' == e[0] && ((f += '.'), d++, (e = e.substring(1))),
(g += e.substring(0, 1)),
(e = e.substring(1)))
: (d || (f += '.'), d++, (g += '0'))
}
return (1 == o ? '-' : '') + r.trim(i.roundOff(f, t - 2))
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.negate = n.subtract = void 0)
var r = t(0)
function i(e) {
return (e = '-' == e[0] ? e.substr(1) : '-' + e)
}
; (n.subtract = function (e, n) {
return (e = e.toString()), (n = i((n = n.toString()))), r.add(e, n)
}),
(n.negate = i)
},
function (e, n, t) {
'use strict'
var r = t(0),
i = t(1),
o = t(3),
u = t(4),
a = t(7),
s = t(8),
d = t(5),
l = t(2),
f = (function () {
function e(n) {
void 0 === n && (n = '0'), (this.value = e.validate(n))
}
return (
(e.validate = function (e) {
if (e) {
if (((e = e.toString()), isNaN(e)))
throw Error('Parameter is not a number: ' + e)
'+' == e[0] && (e = e.substring(1))
} else e = '0'
if (/e/i.test(e)) {
var n = e.split(/[eE]/),
t = n[0],
i = n[1]
; (t = r.trim(t)),
(i = parseInt(i) + t.indexOf('.')),
(e =
(t = t.replace('.', '')).length < i
? t + new Array(i - t.length + 1).join('0')
: t.length >= i && i > 0
? r.trim(t.substring(0, i)) +
(t.length > i ? '.' + t.substring(i) : '')
: '0.' + new Array(1 - i).join('0') + t)
}
return e
}),
(e.prototype.getValue = function () {
return this.value
}),
(e.getPrettyValue = function (n, t, r) {
if (t || r) {
if (!t || !r)
throw Error(
'Illegal Arguments. Should pass both digits and separator or pass none'
)
} else (t = 3), (r = ',')
var i = '-' == (n = e.validate(n)).charAt(0)
i && (n = n.substring(1))
for (
var o = n.indexOf('.'), u = '', a = (o = o > 0 ? o : n.length);
a > 0;
)
a < t ? ((t = a), (a = 0)) : (a -= t),
(u = n.substring(a, a + t) + (a < o - t && a >= 0 ? r : '') + u)
return (i ? '-' : '') + u + n.substring(o)
}),
(e.prototype.getPrettyValue = function (n, t) {
return e.getPrettyValue(this.value, n, t)
}),
(e.round = function (n, t, r) {
if (
(void 0 === t && (t = 0),
void 0 === r && (r = l.RoundingModes.HALF_EVEN),
(n = e.validate(n)),
isNaN(t))
)
throw Error('Precision is not a number: ' + t)
return i.roundOff(n, t, r)
}),
(e.prototype.round = function (n, t) {
if (
(void 0 === n && (n = 0),
void 0 === t && (t = l.RoundingModes.HALF_EVEN),
isNaN(n))
)
throw Error('Precision is not a number: ' + n)
return new e(i.roundOff(this.value, n, t))
}),
(e.floor = function (n) {
return -1 === (n = e.validate(n)).indexOf('.')
? n
: e.round(n, 0, l.RoundingModes.FLOOR)
}),
(e.prototype.floor = function () {
return -1 === this.value.indexOf('.')
? new e(this.value)
: new e(this.value).round(0, l.RoundingModes.FLOOR)
}),
(e.ceil = function (n) {
return -1 === (n = e.validate(n)).indexOf('.')
? n
: e.round(n, 0, l.RoundingModes.CEILING)
}),
(e.prototype.ceil = function () {
return -1 === this.value.indexOf('.')
? new e(this.value)
: new e(this.value).round(0, l.RoundingModes.CEILING)
}),
(e.add = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), r.add(n, t)
}),
(e.prototype.add = function (n) {
return new e(r.add(this.value, n.getValue()))
}),
(e.subtract = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), d.subtract(n, t)
}),
(e.prototype.subtract = function (n) {
return new e(d.subtract(this.value, n.getValue()))
}),
(e.multiply = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), o.multiply(n, t)
}),
(e.prototype.multiply = function (n) {
return new e(o.multiply(this.value, n.getValue()))
}),
(e.divide = function (n, t, r) {
return (n = e.validate(n)), (t = e.validate(t)), u.divide(n, t, r)
}),
(e.prototype.divide = function (n, t) {
return new e(u.divide(this.value, n.getValue(), t))
}),
(e.modulus = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), a.modulus(n, t)
}),
(e.prototype.modulus = function (n) {
return new e(a.modulus(this.value, n.getValue()))
}),
(e.compareTo = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), s.compareTo(n, t)
}),
(e.prototype.compareTo = function (e) {
return s.compareTo(this.value, e.getValue())
}),
(e.negate = function (n) {
return (n = e.validate(n)), d.negate(n)
}),
(e.prototype.negate = function () {
return new e(d.negate(this.value))
}),
(e.RoundingModes = l.RoundingModes),
e
)
})()
e.exports = f
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.modulus = void 0)
var r = t(4),
i = t(1),
o = t(3),
u = t(5),
a = t(2)
function s(e) {
if (-1 != e.indexOf('.'))
throw new Error('Modulus of non-integers not supported')
}
n.modulus = function (e, n) {
if (0 == n) throw new Error('Cannot divide by 0')
; (e = e.toString()), (n = n.toString()), s(e), s(n)
var t = ''
return (
'-' == e[0] && ((t = '-'), (e = e.substr(1))),
'-' == n[0] && (n = n.substr(1)),
t +
u.subtract(
e,
o.multiply(n, i.roundOff(r.divide(e, n), 0, a.RoundingModes.FLOOR))
)
)
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.compareTo = void 0)
var r = t(0)
n.compareTo = function (e, n) {
var t,
i = !1
if ('-' == e[0] && '-' != n[0]) return -1
if ('-' != e[0] && '-' == n[0]) return 1
if (
('-' == e[0] &&
'-' == n[0] &&
((e = e.substr(1)), (n = n.substr(1)), (i = !0)),
(e = (t = r.pad(e, n))[0]),
(n = t[1]),
0 == e.localeCompare(n))
)
return 0
for (var o = 0; o < e.length; o++)
if (e[o] != n[o]) return e[o] > n[o] ? (i ? -1 : 1) : i ? 1 : -1
return 0
}
},
])
export default bigDecimal

25
src/utils/emitter.js Normal file
View File

@@ -0,0 +1,25 @@
import mitt from 'mitt';
class EventEmitter {
constructor() {
this.emitter = mitt();
}
on(eventName, handler) {
this.emitter.on(eventName, handler);
}
off(eventName, handler) {
this.emitter.off(eventName, handler);
}
emit(eventName, event) {
this.emitter.emit(eventName, event);
}
removeAllListeners() {
this.emitter.all.clear();
}
}
export default EventEmitter;

6
src/utils/errorCode.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}

127
src/utils/mqtt.js Normal file
View File

@@ -0,0 +1,127 @@
import mqtt from "mqtt"
// MQTT 连接选项
const options = {
clean: true,
connectTimeout: 4000,
reconnectPeriod: 1000,
qos: 1, // 默认 QoS 1
}
const brokerUrl = "wss://push.cnsdt.com:443/mqtt" // 你的 MQTT 服务地址
class MQTTClient {
constructor() {
this.client = null
this.subscriptions = new Set()
this.messageHandlers = new Map()
}
// 连接
connect(clientId) {
if (this.client && this.client.connected) {
return Promise.resolve()
}
return new Promise((resolve, reject) => {
this.client = mqtt.connect(brokerUrl, {
...options,
clientId: clientId || `client_${Math.random().toString(16).substr(2, 8)}`,
})
this.client.on("connect", () => resolve())
this.client.on("error", (error) => reject(error))
// 消息分发
this.client.on("message", (topic, payload) => {
try {
const message = JSON.parse(payload.toString())
// 遍历所有订阅主题,执行通配符匹配
this.messageHandlers.forEach((handlers, subTopic) => {
if (this.topicMatch(subTopic, topic)) {
handlers.forEach((handler) => handler(message, topic))
}
})
} catch (err) {
console.error("MQTT message parse error:", err)
}
})
})
}
// 断开
disconnect() {
if (this.client) {
this.client.end()
this.client = null
this.subscriptions.clear()
this.messageHandlers.clear()
}
}
// 订阅
subscribe(topic, handler) {
if (!this.client || !this.client.connected) {
console.error("MQTT client not connected")
return
}
if (!this.subscriptions.has(topic)) {
this.client.subscribe(topic, { qos: 1 }, (err) => {
if (err) {
console.error("Subscription error:", err)
} else {
this.subscriptions.add(topic)
}
})
}
const handlers = this.messageHandlers.get(topic) || []
handlers.push(handler)
this.messageHandlers.set(topic, handlers)
}
// 取消订阅
unsubscribe(topic, handler) {
if (!this.subscriptions.has(topic)) return
if (handler) {
const handlers = this.messageHandlers.get(topic) || []
const index = handlers.indexOf(handler)
if (index !== -1) {
handlers.splice(index, 1)
this.messageHandlers.set(topic, handlers)
}
} else {
this.client.unsubscribe(topic)
this.subscriptions.delete(topic)
this.messageHandlers.delete(topic)
}
}
// 发布
publish(topic, message) {
if (!this.client || !this.client.connected) {
console.error("MQTT client not connected")
return
}
this.client.publish(topic, JSON.stringify(message), { qos: 1 })
}
// 通配符匹配
topicMatch(subTopic, msgTopic) {
const subLevels = subTopic.split("/")
const msgLevels = msgTopic.split("/")
for (let i = 0; i < subLevels.length; i++) {
if (subLevels[i] === "#") return true
if (subLevels[i] === "+") continue
if (msgLevels[i] === undefined) return false
if (subLevels[i] !== msgLevels[i]) return false
}
return subLevels.length === msgLevels.length
}
}
export const mqttClient = new MQTTClient()

194
src/utils/request.js Normal file
View File

@@ -0,0 +1,194 @@
import axios from "axios";
import {
ElNotification,
ElMessageBox,
ElMessage,
} from "element-plus";
import { tansParams } from "@/utils/ruoyi";
import cache from "@/plugins/cache";
import { getToken, removeToken } from "@/utils/auth";
import router from '@/router';
import { useMeterStore } from '@/stores/modules/meter'
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
const meterStore = useMeterStore()
meterStore.initUdid()
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000,
});
// request拦截器
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
if (getToken() && !isToken) {
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (meterStore.getSudid()) {
config.headers["X-User-Agent"] = `gxtech/web 1.0.0: c=GxTech, udid=${meterStore.getSudid()}, sv=15.4.1, app=stt`;
}
// get请求映射params参数
if (config.method === "get" && config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (
!isRepeatSubmit &&
(config.method === "post" || config.method === "put")
) {
const requestObj = {
url: config.url,
data:
typeof config.data === "object"
? JSON.stringify(config.data)
: config.data,
time: new Date().getTime(),
};
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(
`[${config.url}]: ` +
"请求数据大小超出允许的5M限制无法进行防重复提交验证。"
);
return config;
}
const sessionObj = cache.session.getJSON("sessionObj");
if (
sessionObj === undefined ||
sessionObj === null ||
sessionObj === ""
) {
cache.session.setJSON("sessionObj", requestObj);
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = "数据正在处理,请勿重复提交";
console.warn(`[${s_url}]: ` + message);
return Promise.reject(new Error(message));
} else {
cache.session.setJSON("sessionObj", requestObj);
}
}
}
return config;
},
(error) => {
Promise.reject(error);
}
);
service.interceptors.response.use(
(response) => {
// 1. 检查响应是否存在
if (!response) {
ElMessage.error('无响应数据');
return Promise.reject(new Error('无响应数据'));
}
// 2. 安全获取响应数据和状态码
const responseData = response.data || {};
const statusCode = response.status;
const businessCode = responseData.meta?.code || statusCode;
// 3. 二进制数据直接返回
if (
response.request.responseType === 'blob' ||
response.request.responseType === 'arraybuffer'
) {
return responseData;
}
// 4. 根据业务码处理不同情况
switch (businessCode) {
case 200:
case 201:
return Promise.resolve(responseData);
case 401:
return handleUnauthorized().then(() => {
return Promise.reject({ code: 401, message: '未授权' });
});
case 500:
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
ElMessage({ message: serverErrorMsg, type: 'error' });
return Promise.reject({ code: 500, message: serverErrorMsg });
default:
const errorMsg = responseData.meta?.message || `业务错误 (${businessCode})`;
ElNotification.error({ title: errorMsg });
return Promise.reject({ code: businessCode, message: errorMsg });
}
},
(error) => {
console.error('请求错误:', error);
let { message } = error;
let code = error?.response?.status || -1;
if (message == 'Network Error') {
message = '后端接口连接异常';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('timeout')) {
message = '系统接口请求超时';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('Request failed with status code')) {
// message = '系统接口' + message.substr(message.length - 3) + '异常';
}
// 返回结构化错误
return Promise.reject({
code,
message,
raw: error // 保留原始 error
});
}
);
// 单独处理401未授权
function handleUnauthorized() {
return ElMessageBox.confirm(
'认证信息已失效,您可以继续留在该页面,或者重新登录',
'系统提示',
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
removeToken()
if (router.currentRoute.path !== '/login') {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
} else {
// 如果在登录页,强制刷新以清除残留状态
window.location.reload();
}
})
.catch(() => {
return Promise.reject('用户取消操作');
});
}
export default service;

718
src/utils/ruoyi.js Normal file
View File

@@ -0,0 +1,718 @@
import bigDecimal from "@/utils/bigDecimal";
/**
* @description 分钟转时间
* @author
* @date
* @param
* @returns
*/
export function formatMinutes(minutes) {
let tiemInfo = Number(minutes);
var time = [];
var day = parseInt(tiemInfo / 60 / 24);
var hour = parseInt((tiemInfo / 60) % 24);
var min = parseInt(tiemInfo % 60);
time[0] = day > 0 ? day : 0;
time[1] = hour > 0 ? hour : 0;
time[2] = min > 0 ? parseFloat(min) : 0;
return time;
}
/**
* @description 时间转分钟
* @author
* @date
* @param
* @returns
*/
export function totalMinutes(day, hour, minute) {
return day * 1440 + hour * 60 + minute;
}
/**
* @description 数组指定必填项判断校验
* @author // arr要判断的数组 data要判断里面那些不能为空的字段
* @date
* @param
* @returns
*/
export function everyI(arr, data, text) {
let flag = arr.flatMap((item) => {
let flag1 = data.flatMap((key) => {
if (text == "不校验零") {
if (item[key] || item[key] == 0) {
return true;
} else {
false;
}
} else {
if (item[key]) {
return true;
} else {
false;
}
}
});
return flag1;
});
// 如果不只条的时候全部都要填写 every只要有一个不满足就不让走 smoe只要有一个满足就可以走
let info = flag.every((val) => val);
return info;
}
/**
* @description 找下对应的字段显示上去 arr1枚举表 arr2对比数组 text取出的字段
* @author cl
* @date 2022-2-17
* @param
* @returns
*/
export function getArr(arr1, arr2, text) {
let arrInfo = [];
if (!arr2) return;
arr1.map((item) => {
arr2.split(",").map((n) => {
if (n == item.value) {
arrInfo.push(item[text]);
}
});
});
return arrInfo.join(",");
}
/**
* @description
* @author cl
* @date 2022-2-18
* @param arr要计算的数组数据 Array
* @param num2要计算的价格字段 string
* @param arr2要合并计算总价的数据 string
* @param currency优惠券的币种字段 Array
* @param currency要减去的优惠券币种字段 string
* @param amount要减去的优惠券价格字段 string
* @returns
*/
export function getPriceI(arr, num1, num2, arr2, currency, amount) {
let arrInfo = arr2 ? arr2 : []; // 总计
let currencyInfo = currency; // 优惠券币种
let amountInfo = amount ? amount : 0; // 优惠价钱
let price = {
USD: 0,
CNY: 0,
};
arr.concat(arrInfo).flatMap((i) => {
if (i[num1] == "USD") {
price.USD = Number(bigDecimal.add(price.USD, i[num2]));
} else {
price.CNY = Number(bigDecimal.add(price.CNY, i[num2]));
}
});
// 有优惠卷的话减去优惠间
if (currencyInfo === "CNY") {
price.CNY = Number(bigDecimal.subtract(price.CNY, amountInfo));
} else {
price.USD = Number(bigDecimal.subtract(price.USD, amountInfo));
}
let arrI = [
price.USD > 0 ? "USD" + format_with_array(price.USD) : "USD0.00",
price.CNY > 0 ? "CNY" + format_with_array(price.CNY) : "CNY0.00",
]
.filter((current) => {
return current !== "USD0.00" && current !== "CNY0.00";
})
.join(" + ");
return arrI;
}
/**
* @description 数组字段拼接回调法
* @author cl
* @date 2022-12-9
* @param
* @returns
*/
export function _getArr(arr, name, id, child) {
arr.forEach((item, index) => {
// 作为键值对
item.label = item[name];
item.value = item[id];
if (item.children) {
_getArr(item.children, name, id);
}
});
return arr;
}
/**
* 防抖原理一定时间内只有最后一次操作再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
let timeout = null;
export function debounce(func, wait = 300, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout);
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) typeof func === "function" && func();
} else {
// 设置定时器当最后一次操作后timeout不会再被清除所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === "function" && func();
}, wait);
}
}
/**
* @description 去重对比后去除已存在的替换 arrA已经选择的 arrB数据源 text去重的标识字段 type类型
* @author cl
* @date 2022-12-9
* @param
* @returns
*/
export function processI(arrA, arrB, text) {
// 缓存用于记录
const cache = [];
for (const t of arrB) {
// 检查缓存中是否已经存在
if (arrA.find((c) => c[text] === t[text])) {
// 已经存在说明以前记录过,现在这个就是多余的,直接忽略
continue;
}
// 不存在就说明以前没遇到过,把它记录下来
cache.push(t);
}
return cache;
}
/**
* @description 去重去除已存在的 typeName类型标记
* @author cl
* @date 2022-2-17
* @param
* @returns
*/
export function duplicate(arr1, arr2, text, typeName, type) {
arr1.map((item) => {
// 检查缓存中是否已经存在
if (arr2.find((c) => c[text] == item[text])) {
// 已经存在说明以前记录过,现在这个就是多余的,直接忽略
return;
}
// 不存在就说明以前没遇到过,把它记录下来
if (typeName) {
item[typeName] = type;
arr2.push(item);
} else {
arr2.push(item);
}
});
return arr2;
}
/**
* 手机号格式化
* @param {string | number} mobile 手机号
* @returns '188 8888 8888'格式的手机号
*/
export function formatMobile(mobile) {
const regMobile = /^1\d{10}/;
if (!regMobile.test(mobile)) {
return "";
}
return String(mobile).replace(/(^\d{3}|\d{4}\B)/g, "$1 ");
}
/**
* @description 复制
* @param {*} id DOM ID
*/
export function copyDomText(id) {
const node = document.getElementById(id);
if (node) {
let createRange = document.createRange();
createRange.selectNodeContents(document.getElementById(id));
const selection = document.getSelection();
selection.removeAllRanges();
selection.addRange(createRange);
document.execCommand("Copy");
selection.removeAllRanges();
}
}
/**
* 金额 千分位
* @param { number} mobile 金额
* @returns '12,300,000' 金额格式
*/
export function format_with_array(
number,
decimals,
dec_point,
thousands_sep,
round_tag
) {
number = (number + "").replace(/[^0-9+-Ee.]/g, "");
decimals = decimals || 2; //默认保留2位
dec_point = dec_point || "."; //默认是'.';
thousands_sep = thousands_sep || ","; //默认是',';
round_tag = round_tag || "round"; //默认是四舍五入
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = typeof thousands_sep === "undefined" ? "," : thousands_sep,
dec = typeof dec_point === "undefined" ? "." : dec_point,
s = "",
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return (
"" +
parseFloat(
Math[round_tag](parseFloat((n * k).toFixed(prec * 2))).toFixed(
prec * 2
)
) /
k
);
};
s = (prec ? toFixedFix(n, prec) : "" + Math.round(n)).split(".");
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, "$1" + sep + "$2");
}
if ((s[1] || "").length < prec) {
s[1] = s[1] || "";
s[1] += new Array(prec - s[1].length + 1).join("0");
}
return "¥" + s.join(dec);
}
// 获取两个时间段内的所有日期/月份 日 传入 YYYY-MM , YYYY-MM (2020-09) (2020-12) 返回 YYYY-MM 数组
export function getAllDay(start, end) {
let result = [];
let min = new Date(start);
let max = new Date(end);
let curr = min;
do {
let month = new Date(curr).getMonth() + 1;
let t = "";
if (month < 10) {
t = "0" + month;
} else t = month;
let str = curr.getFullYear() + "-" + t;
result.push(str);
if (month == 12) {
curr.setFullYear(new Date(curr).getFullYear() + 1);
curr.setMonth(0);
} else curr.setMonth(month);
} while (curr <= max);
return result;
}
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
let date;
if (typeof time === "object") {
date = time;
} else {
if (typeof time === "string" && /^[0-9]+$/.test(time)) {
time = parseInt(time);
} else if (typeof time === "string") {
time = time
.replace(new RegExp(/-/gm), "/")
.replace("T", " ")
.replace(new RegExp(/\.[\d]{3}/gm), "");
}
if (typeof time === "number" && time.toString().length === 10) {
time = time * 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === "a") {
return ["日", "一", "二", "三", "四", "五", "六"][value];
}
if (result.length > 0 && value < 10) {
value = "0" + value;
}
return value || 0;
});
return time_str;
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params;
search.params =
typeof search.params === "object" &&
search.params !== null &&
!Array.isArray(search.params)
? search.params
: {};
dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof propName === "undefined") {
search.params["beginTime"] = dateRange[0];
search.params["endTime"] = dateRange[1];
} else {
search.params["begin" + propName] = dateRange[0];
search.params["end" + propName] = dateRange[1];
}
return search;
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
}
var actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value == "" + value) {
actions.push(datas[key].label);
return true;
}
});
if (actions.length === 0) {
actions.push(value);
}
return actions.join("");
}
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length === 0) {
return "";
}
if (Array.isArray(value)) {
value = value.join(",");
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == "" + temp[val]) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
});
if (!match) {
actions.push(temp[val] + currentSeparator);
}
});
return actions.join("").substring(0, actions.join("").length - 1);
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments,
flag = true,
i = 1;
str = str.replace(/%s/g, function () {
var arg = args[i++];
if (typeof arg === "undefined") {
flag = false;
return "";
}
return arg;
});
return flag ? str : "";
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return "";
}
return str;
}
// 数据合并
export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p]);
} else {
source[p] = target[p];
}
} catch (e) {
source[p] = target[p];
}
}
return source;
}
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || "id",
parentId: parentId || "parentId",
childrenList: children || "children",
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = "";
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof value !== "undefined") {
if (typeof value === "object") {
for (const key of Object.keys(value)) {
if (
value[key] !== null &&
value[key] !== "" &&
typeof value[key] !== "undefined"
) {
let params = propName + "[" + key + "]";
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result;
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == "undefined") {
return p;
}
let res = p.replace("//", "/");
if (res[res.length - 1] === "/") {
return res.slice(0, res.length - 1);
}
return res;
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== "application/json";
}
// 处理图片格式
export function disposeImage(primary, type = false) {
if (primary && (primary.length || !Array.isArray(primary))) {
let imgValue = null;
if (Array.isArray(primary)) {
imgValue = [];
primary.forEach((item) => {
if (
(typeof item == "string" && type) ||
(item.url && item.uuid && !type)
) {
imgValue.push(item);
} else {
if (type) {
if (item.prefix && item.path) {
imgValue.push(item.prefix + item.path);
} else {
imgValue.push(item.url);
}
} else {
imgValue.push({
name: item.name,
url: item.prefix + item.path,
uuid: item.fileId,
});
}
}
});
} else {
if (
(typeof primary == "string" && type) ||
(primary.url && primary.uuid && !type)
) {
imgValue = primary;
} else {
if (type) {
if (primary.prefix && primary.path) {
imgValue = primary.prefix + primary.path;
} else {
imgValue = primary.url;
}
} else {
imgValue = {
name: primary.name,
url: primary.prefix + primary.path,
uuid: primary.fileId,
};
}
}
}
return imgValue;
} else {
return [];
}
}
// 处理图片格式
export function getImage(primary) {
if (primary && primary.length) {
let imgArr = [];
primary.forEach((item) => {
imgArr.push(item.uuid);
});
return imgArr.toString();
} else {
return "";
}
}
export function getUrl(url) {
return new Promise((resolve, reject) => {
getFileMessage({ fileId: url })
.then((res) => {
resolve(disposeImage(res.data));
})
.catch((row) => {
ElMessage({
message: "图片获取错误" + row,
type: "error",
duration: 5 * 1000,
});
reject(false);
});
});
}
// 获取图片详情
export async function getImageMessage(url, type = false) {
if (!url || (Array.isArray(url) && !url.length)) {
} else {
let uuidList = null;
if (typeof url == "string" || typeof url == "number") {
uuidList = url.toString().split(",");
} else if (Array.isArray(url)) {
uuidList = url;
}
let imgurl = [];
for (const item in uuidList) {
const url = await getUrl(uuidList[item]);
if (url) {
if (type) {
imgurl.push(url);
} else {
imgurl.push(url.url);
}
} else {
imgurl.push(img);
}
}
return imgurl;
}
}
// 克隆
export function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
// 如果 obj 是 null 或不是对象,则直接返回 obj
return obj;
}
if (Array.isArray(obj)) {
// 如果 obj 是数组
const clonedArray = [];
for (let i = 0; i < obj.length; i++) {
// 递归克隆数组中的每个元素
clonedArray[i] = deepClone(obj[i]);
}
return clonedArray;
}
// 如果 obj 是普通对象
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 递归克隆对象的每个属性
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// 数组去重
export function removeDuplicate(arr) {
const newArr = [];
arr.forEach((item) => {
if (newArr.indexOf(item) === -1) {
newArr.push(item);
}
});
return newArr; // 返回一个新数组
}

160
src/utils/tools.js Normal file
View File

@@ -0,0 +1,160 @@
// 获取uuid
export function generateUUID() {
var d = new Date().getTime();
var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16;
if (d > 0) {
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
/**
* 过滤掉可信度(confidence)小于0.6的数据项
* @param {Array} data - 要过滤的数组数组项应包含confidence属性
* @returns {Array} 过滤后的新数组
*/
export function filteConfidence(data) {
// 检查输入是否为数组
if (!Array.isArray(data)) {
console.warn('filteConfidence: 输入参数不是数组');
return [];
}
// 过滤并返回新数组
return data.filter(item => {
return item && typeof item === 'object' &&
'confidence' in item &&
Number(item.confidence) >= 0.6;
});
}
/**
* 根据检测结果截取图片中的多个区域
* @param {HTMLImageElement|String} image - 图片元素或图片URL
* @param {Array} detectionResults - 检测结果数组包含box坐标
* @param {Object} options - 可选配置
* @param {Number} options.minConfidence - 最小置信度阈值默认0.5
* @param {String} options.outputFormat - 输出格式,默认'image/png'
* @param {Number} options.outputQuality - 输出质量(0-1)默认0.92
* @returns {Promise<Array<{blob: Blob, data: Object}>>} 返回截取图片和相关数据的数组
*/
export async function cropDetectedRegions(image, detectionResults, options = {}) {
// 合并默认配置
const {
minConfidence = 0.6,
outputFormat = 'image/png',
outputQuality = 0.92
} = options;
// 如果传入的是URL字符串先加载图片
const img = typeof image === 'string'
? await loadImage(image)
: image;
// 创建画布
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 存储所有截取结果
const croppedResults = [];
// 遍历所有检测结果
for (const result of detectionResults) {
// 检查置信度是否达标
if (result.confidence < minConfidence) {
console.log(`跳过置信度低于阈值(${minConfidence})的检测结果:`, result);
continue;
}
const { x1, y1, x2, y2 } = result.box;
const width = x2 - x1;
const height = y2 - y1;
// 检查坐标是否有效
if (!isValidBox(x1, y1, x2, y2, img.width, img.height)) {
console.warn('无效的坐标区域:', result.box);
continue;
}
// 设置画布尺寸
canvas.width = width;
canvas.height = height;
// 清空画布
ctx.clearRect(0, 0, width, height);
// 绘制截取区域
ctx.drawImage(
img,
x1, y1, // 源图像裁剪起始坐标
width, height, // 源图像裁剪宽高
0, 0, // 画布起始坐标
width, height // 画布绘制宽高
);
// 将画布内容转为Blob对象
const blob = await new Promise((resolve) => {
canvas.toBlob(
(blob) => resolve(blob),
outputFormat,
outputQuality
);
});
// 保存结果及相关数据
croppedResults.push({
blob,
data: {
...result,
cropInfo: {
x: x1,
y: y1,
width,
height
}
}
});
}
return croppedResults;
}
/**
* 加载图片
* @param {String} url - 图片URL
* @returns {Promise<HTMLImageElement>}
*/
export function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域问题
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
/**
* 检查坐标框是否有效
* @param {Number} x1 - 左上角x坐标
* @param {Number} y1 - 左上角y坐标
* @param {Number} x2 - 右下角x坐标
* @param {Number} y2 - 右下角y坐标
* @param {Number} imgWidth - 图片宽度
* @param {Number} imgHeight - 图片高度
* @returns {Boolean}
*/
function isValidBox(x1, y1, x2, y2, imgWidth, imgHeight) {
return (
x1 >= 0 && y1 >= 0 &&
x2 > x1 && y2 > y1 &&
x2 <= imgWidth && y2 <= imgHeight
);
}

View File

@@ -0,0 +1,98 @@
import { mqttClient } from "./mqtt";
import { getWhiteboardShapes, getWhiteboardHistory } from "@/views/custom/api";
import { useMeterStore } from '@/stores/modules/meter';
const meterStore = useMeterStore();
meterStore.initUdid();
let isRemote = false;
let canvasInstance = null;
// 获取本地缓存 userData
function getLocalUserData() {
const dataStr = localStorage.getItem('userData');
if (!dataStr) return null;
try {
return JSON.parse(dataStr);
} catch (e) {
console.error("解析 userData 失败:", e);
return null;
}
}
export const WhiteboardSync = {
async init(canvas, roomUid) {
if (!canvas || !roomUid) return;
console.log('初始化多人同步:', roomUid);
canvasInstance = canvas;
const localUser = getLocalUserData();
const localUid = localUser?.user?.uid;
try {
// 先连接 MQTT
await mqttClient.connect(meterStore.getSudid());
console.log("✅ MQTT 已连接");
// 获取历史数据
const res = await getWhiteboardHistory({ after_timestamp: 0 }, roomUid);
if (res.meta.code === 200 && res.data.shapes.length > 0) {
canvasInstance.addShape(res.data.shapes);
}
// 订阅当前房间
const topic = `xSynergy/ROOM/${roomUid}/whiteboard/#`;
mqttClient.subscribe(topic, async (shapeData) => {
try {
isRemote = true;
// 如果 shape 来自本地用户,则跳过
if (shapeData.user_uid === localUid) return;
const res = await getWhiteboardHistory({ after_timestamp: shapeData.created_at }, roomUid);
if (res.meta.code === 200) {
canvasInstance.addShape(res.data.shapes);
} else {
console.error("获取历史数据失败");
}
} catch (e) {
console.error("处理MQTT数据失败:", e);
} finally {
isRemote = false;
}
});
console.log("✅ 已订阅:", topic);
} catch (err) {
console.log("初始化多人同步失败:",err)
// console.error("❌ 连接或订阅失败:", err);
}
// 监听画布事件:新增图形
canvas.on('drawingEnd', async (shape) => {
// 如果来自远程,或不是需要同步的类型,跳过
if (isRemote || !['pencil', 'line', 'rectangle', 'circle', 'eraser'].includes(shape.type)) return;
// 如果是本地用户自己的 shape则不调用接口
if (shape.user_uid && shape.user_uid === localUid) return;
shape.room_uid = roomUid;
try {
await getWhiteboardShapes(shape, roomUid);
} catch (err) {
console.error("提交形状失败:", err);
}
});
// 监听画布事件:清空
canvas.on('clear', async () => {
if (!isRemote) {
try {
// TODO: 调用接口,后端再发 MQTT
// await clearWhiteboard(roomUid);
} catch (err) {
console.error("提交清空失败:", err);
}
}
});
},
};