mirror of https://github.com/veypi/OneAuth.git
fix upgrade bug
parent
89e7caa7b0
commit
89a2ea17e2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,293 @@
|
||||
|
||||
|
||||
function CamelToKebabCase(str) {
|
||||
// 首先将字符串的第一个字符转换为小写,避免在首字符前加上'-'
|
||||
if (str.length === 0) return '';
|
||||
let firstChar = str.charAt(0).toLowerCase();
|
||||
|
||||
// 对剩余部分应用原逻辑:找到每个大写字母,并替换为连字符加上该字母的小写形式
|
||||
let rest = str.slice(1).replace(/([A-Z])/g, function(match, p1) {
|
||||
return '-' + p1.toLowerCase();
|
||||
});
|
||||
|
||||
return firstChar + rest;
|
||||
}
|
||||
|
||||
const outerClickList = []
|
||||
const globalClick = document.addEventListener('click', (event) => {
|
||||
outerClickList.forEach((item) => {
|
||||
if (item?.dom instanceof Element && typeof item?.callback === 'function') {
|
||||
if (!item.dom.contains(event.target)) {
|
||||
item.callback(event)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
const AddClicker = (dom, typ, callback) => {
|
||||
if (typ === 'outer') {
|
||||
let idx = outerClickList.length
|
||||
outerClickList.push({ dom, callback })
|
||||
return () => {
|
||||
outerClickList[idx] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const EventsList = [
|
||||
// 窗口和框架事件
|
||||
'load',
|
||||
'unload',
|
||||
'beforeunload',
|
||||
'resize',
|
||||
'scroll',
|
||||
|
||||
// 表单事件
|
||||
'submit',
|
||||
'reset',
|
||||
'input',
|
||||
'change',
|
||||
'focus',
|
||||
'blur',
|
||||
|
||||
// 键盘事件
|
||||
'keydown',
|
||||
'keypress',
|
||||
'keyup',
|
||||
|
||||
// 鼠标事件
|
||||
'click',
|
||||
'dblclick',
|
||||
'contextmenu',
|
||||
'mousedown',
|
||||
'mouseup',
|
||||
'mousemove',
|
||||
'mouseover',
|
||||
'mouseout',
|
||||
'mouseenter',
|
||||
'mouseleave',
|
||||
|
||||
// 触摸事件
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'touchend',
|
||||
'touchcancel',
|
||||
|
||||
// 拖拽事件
|
||||
'drag',
|
||||
'dragstart',
|
||||
'dragend',
|
||||
'dragover',
|
||||
'dragenter',
|
||||
'dragleave',
|
||||
'drop',
|
||||
|
||||
// 剪贴板事件
|
||||
'copy',
|
||||
'cut',
|
||||
'paste',
|
||||
|
||||
// 动画事件
|
||||
'animationstart',
|
||||
'animationend',
|
||||
'animationiteration',
|
||||
|
||||
// 过渡事件
|
||||
'transitionend',
|
||||
|
||||
// 文件操作事件
|
||||
'abort',
|
||||
'error',
|
||||
'loadstart',
|
||||
'progress',
|
||||
|
||||
// 音视频事件
|
||||
'play',
|
||||
'pause',
|
||||
'ended',
|
||||
'volumechange',
|
||||
'timeupdate',
|
||||
'loadeddata',
|
||||
'waiting',
|
||||
'playing',
|
||||
|
||||
// 网络状态事件
|
||||
'online',
|
||||
'offline',
|
||||
|
||||
// 存储事件
|
||||
'storage',
|
||||
|
||||
// 页面可见性事件
|
||||
'visibilitychange'
|
||||
];
|
||||
|
||||
function BindInputDomValue(dom, data, key, watch) {
|
||||
const element = typeof dom === 'string' ? document.querySelector(dom) : dom;
|
||||
|
||||
if (!element) {
|
||||
console.error('DOM元素未找到');
|
||||
return;
|
||||
}
|
||||
// 根据元素类型进行双向绑定
|
||||
const elementType = element.type || element.tagName.toLowerCase();
|
||||
switch (elementType) {
|
||||
// 文本输入类
|
||||
case 'text':
|
||||
case 'password':
|
||||
case 'email':
|
||||
case 'tel':
|
||||
case 'url':
|
||||
case 'search':
|
||||
case 'number':
|
||||
case 'range':
|
||||
case 'color':
|
||||
case 'date':
|
||||
case 'time':
|
||||
case 'datetime-local':
|
||||
case 'month':
|
||||
case 'week':
|
||||
case 'hidden':
|
||||
case 'textarea':
|
||||
watch(() => data[key], (value) => {
|
||||
if (value === undefined) {
|
||||
element.value = ''
|
||||
} else {
|
||||
element.value = value
|
||||
}
|
||||
})
|
||||
element.addEventListener('input', function() {
|
||||
data[key] = this.value;
|
||||
});
|
||||
break;
|
||||
case 'checkbox':
|
||||
watch(function() {
|
||||
element.checked = !!data[key];
|
||||
});
|
||||
element.addEventListener('change', function() {
|
||||
data[key] = this.checked;
|
||||
console.log(data, data[key])
|
||||
});
|
||||
break;
|
||||
// 单选框
|
||||
case 'radio':
|
||||
// 初始化
|
||||
watch(() => {
|
||||
element.checked = element.value === data[key];
|
||||
})
|
||||
element.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
data[key] = this.value;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
// 下拉选择框
|
||||
case 'select-one':
|
||||
case 'select-multiple':
|
||||
watch(() => {
|
||||
let newValue = data[key]
|
||||
if (element.multiple) {
|
||||
const values = Array.isArray(newValue) ? newValue : [];
|
||||
for (let i = 0; i < element.options.length; i++) {
|
||||
element.options[i].selected = values.includes(element.options[i].value);
|
||||
}
|
||||
} else {
|
||||
element.value = newValue || '';
|
||||
}
|
||||
});
|
||||
// 监听变化
|
||||
element.addEventListener('change', function() {
|
||||
if (this.multiple) {
|
||||
// 多选
|
||||
const selectedValues = [];
|
||||
for (let i = 0; i < this.options.length; i++) {
|
||||
if (this.options[i].selected) {
|
||||
selectedValues.push(this.options[i].value);
|
||||
}
|
||||
}
|
||||
data[key] = selectedValues;
|
||||
} else {
|
||||
// 单选
|
||||
data[key] = this.value;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`${elementType} not support v!bind only for input element`, element);
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function SetAttr(dom, key, value) {
|
||||
// 属性名映射表
|
||||
const propertyMap = {
|
||||
'htmlfor': 'htmlFor',
|
||||
'readonly': 'readOnly',
|
||||
'maxlength': 'maxLength',
|
||||
'minlength': 'minLength',
|
||||
'cellspacing': 'cellSpacing',
|
||||
'cellpadding': 'cellPadding',
|
||||
'rowspan': 'rowSpan',
|
||||
'colspan': 'colSpan',
|
||||
'tabindex': 'tabIndex',
|
||||
'usemap': 'useMap',
|
||||
'frameborder': 'frameBorder',
|
||||
'contenteditable': 'contentEditable',
|
||||
'spellcheck': 'spellcheck',
|
||||
'innerhtml': 'innerHTML',
|
||||
'innertext': 'innerText',
|
||||
'autocapitalize': 'autocapitalize',
|
||||
};
|
||||
|
||||
// 需要使用 DOM 属性设置的属性
|
||||
const domProperties = new Set([
|
||||
'innerHTML', 'innerText', 'outerHTML', 'textContent',
|
||||
'value', 'checked', 'selected', 'disabled', 'readOnly',
|
||||
'maxLength', 'minLength', 'htmlFor',
|
||||
'tabIndex', 'scrollTop', 'scrollLeft', 'scrollWidth', 'scrollHeight',
|
||||
'clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight',
|
||||
'style', 'dataset'
|
||||
]);
|
||||
|
||||
// 布尔属性
|
||||
const booleanAttributes = new Set([
|
||||
'checked', 'selected', 'disabled', 'readonly', 'required',
|
||||
'hidden', 'autofocus', 'multiple', 'novalidate'
|
||||
]);
|
||||
|
||||
// 转换属性名
|
||||
const lowerKey = key.toLowerCase();
|
||||
const mappedKey = propertyMap[lowerKey] || key;
|
||||
|
||||
|
||||
|
||||
// 设置属性的策略:
|
||||
if (domProperties.has(mappedKey)) {
|
||||
// DOM 属性
|
||||
if (value === undefined) {
|
||||
dom[mappedKey] = ''
|
||||
} else {
|
||||
dom[mappedKey] = value;
|
||||
}
|
||||
} else if (booleanAttributes.has(lowerKey)) {
|
||||
// 布尔属性
|
||||
if (value) {
|
||||
dom.setAttribute(lowerKey, '');
|
||||
} else {
|
||||
dom.removeAttribute(lowerKey);
|
||||
}
|
||||
} else {
|
||||
// 其他属性使用 setAttribute
|
||||
if (value === undefined) {
|
||||
dom.removeAttribute(key);
|
||||
} else {
|
||||
dom.setAttribute(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default { CamelToKebabCase, EventsList, BindInputDomValue, SetAttr, AddClicker }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* vdev.js
|
||||
* Copyright (C) 2025 veypi <i@veypi.com>
|
||||
*
|
||||
* Distributed under terms of the MIT license.
|
||||
*/
|
||||
|
||||
import DivSelectorPlugin from './vdevselect.js'
|
||||
|
||||
let isSetup = false;
|
||||
let divSelector = null;
|
||||
|
||||
const postMessage = (typ, args) => {
|
||||
if (!isSetup) return; // 未初始化前不发送消息
|
||||
if (typeof args === 'string' || typeof args === 'number') {
|
||||
args = { value: args }
|
||||
}
|
||||
window.parent.postMessage(Object.assign({ type: typ, from: 'vdev' }, args), '*')
|
||||
}
|
||||
|
||||
// 延迟执行的初始化函数
|
||||
const initializeVdev = () => {
|
||||
// 在初始化时创建 divSelector 实例
|
||||
divSelector = new DivSelectorPlugin();
|
||||
|
||||
postMessage('iframe-loaded')
|
||||
divSelector.postMessage = postMessage
|
||||
|
||||
window.addEventListener('keyup', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
postMessage('key-esc')
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (window.$vhtml && window.$vhtml.$router) {
|
||||
$vhtml.$router.onChange((url) => {
|
||||
postMessage('url-change', url.fullPath)
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
console.log(data)
|
||||
if (data.from != 'vhtml') {
|
||||
return
|
||||
}
|
||||
switch (data.type) {
|
||||
case 'reload':
|
||||
window.location.reload()
|
||||
break;
|
||||
case 'magic':
|
||||
if (divSelector && divSelector.isActive) {
|
||||
divSelector.deactivate()
|
||||
} else if (divSelector) {
|
||||
divSelector.activate()
|
||||
}
|
||||
break
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setup() {
|
||||
if (isSetup) return; // 防止重复初始化
|
||||
isSetup = true;
|
||||
initializeVdev();
|
||||
}
|
||||
|
||||
export default setup
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* verror.js
|
||||
* Copyright (C) 2025 veypi <i@veypi.com>
|
||||
*
|
||||
* Distributed under terms of the MIT license.
|
||||
*/
|
||||
class ErrorCollector {
|
||||
constructor() {
|
||||
this.errorQueue = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// 监听未处理的 Promise 拒绝
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
this.handleError({
|
||||
type: 'unhandledrejection',
|
||||
message: event.reason.message || String(event.reason),
|
||||
stack: event.reason.stack,
|
||||
timestamp: new Date().toISOString(),
|
||||
userAgent: navigator.userAgent,
|
||||
url: window.location.href,
|
||||
promiseRejection: true
|
||||
});
|
||||
});
|
||||
|
||||
// 监听 JavaScript 错误
|
||||
window.addEventListener('error', (event) => {
|
||||
if (event.target === window) {
|
||||
this.handleError({
|
||||
type: 'javascript-error',
|
||||
message: event.message,
|
||||
source: event.filename,
|
||||
line: event.lineno,
|
||||
column: event.colno,
|
||||
stack: event.error ? event.error.stack : null,
|
||||
timestamp: new Date().toISOString(),
|
||||
userAgent: navigator.userAgent,
|
||||
url: window.location.href
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleError(errorInfo) {
|
||||
console.error('Error collected:', errorInfo);
|
||||
this.queueError(errorInfo);
|
||||
}
|
||||
|
||||
queueError(errorInfo) {
|
||||
this.errorQueue.push(errorInfo);
|
||||
|
||||
// 限制队列大小
|
||||
if (this.errorQueue.length > 100) {
|
||||
this.errorQueue.shift();
|
||||
}
|
||||
|
||||
// 尝试存储到 localStorage
|
||||
try {
|
||||
localStorage.setItem('errorQueue', JSON.stringify(this.errorQueue));
|
||||
} catch (e) {
|
||||
console.warn('Failed to store error queue in localStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 手动报告错误
|
||||
reportError(error, context = {}) {
|
||||
this.handleError({
|
||||
type: 'manual-report',
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
context: context,
|
||||
timestamp: new Date().toISOString(),
|
||||
userAgent: navigator.userAgent,
|
||||
url: window.location.href
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化错误收集器
|
||||
const errorCollector = new ErrorCollector();
|
||||
|
||||
// 导出实例供其他模块使用
|
||||
window.errorCollector = errorCollector;
|
||||
|
||||
export default errorCollector
|
||||
|
||||
|
||||
Loading…
Reference in New Issue