|
@@ -0,0 +1,204 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 安全限制插件
|
|
|
|
|
+ * 用于 Kiosk 模式下的安全控制
|
|
|
|
|
+ * 仅在立式一体机(展示机)模式下生效
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+import type { App } from 'vue'
|
|
|
|
|
+import { isKioskDevice } from '@/utils/useDeviceDetection'
|
|
|
|
|
+
|
|
|
|
|
+// 立即执行:在模块加载时就绑定事件,确保最早生效
|
|
|
|
|
+const initSecurityRestrictions = () => {
|
|
|
|
|
+ // 检测是否为展示机模式,非展示机模式不启用安全限制
|
|
|
|
|
+ if (!isKioskDevice()) {
|
|
|
|
|
+ console.log('非展示机模式,安全限制未启用')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log('检测到展示机模式,启用安全限制')
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 禁用所有外部链接跳转
|
|
|
|
|
+ const preventExternalLinks = (e: MouseEvent) => {
|
|
|
|
|
+ const target = e.target as HTMLElement
|
|
|
|
|
+ const link = target.closest('a')
|
|
|
|
|
+
|
|
|
|
|
+ if (link && link.href) {
|
|
|
|
|
+ // 阻止所有 <a> 标签的默认跳转行为
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ e.stopPropagation()
|
|
|
|
|
+ console.warn('外部链接跳转已被禁用')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 禁用右键菜单
|
|
|
|
|
+ const preventContextMenu = (e: MouseEvent) => {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ e.stopPropagation()
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 禁用文本选择
|
|
|
|
|
+ const preventSelection = (e: Event) => {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 禁用特定键盘快捷键
|
|
|
|
|
+ const preventKeyboardShortcuts = (e: KeyboardEvent) => {
|
|
|
|
|
+ // F5 - 刷新
|
|
|
|
|
+ if (e.key === 'F5') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('F5 刷新已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // F12 - 开发者工具
|
|
|
|
|
+ if (e.key === 'F12') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('F12 开发者工具已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+Shift+I / Cmd+Option+I - 开发者工具
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'I') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('开发者工具快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+Shift+J / Cmd+Option+J - 控制台
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'J') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('控制台快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+Shift+C / Cmd+Option+C - 元素检查
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'C') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('元素检查快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+U / Cmd+U - 查看源代码
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.key === 'u') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('查看源代码快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+S / Cmd+S - 保存页面
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('保存页面快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+P / Cmd+P - 打印
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('打印快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Alt+F4 - 关闭窗口(仅 Windows)
|
|
|
|
|
+ if (e.altKey && e.key === 'F4') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('Alt+F4 关闭窗口已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+W / Cmd+W - 关闭标签页
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.key === 'w') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('关闭标签页快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+R / Cmd+R - 刷新
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('刷新快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Ctrl+Shift+R / Cmd+Shift+R - 强制刷新
|
|
|
|
|
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'R') {
|
|
|
|
|
+ e.preventDefault()
|
|
|
|
|
+ console.warn('强制刷新快捷键已被禁用')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 立即绑定事件监听器(在 DOM 加载前)
|
|
|
|
|
+ const bindEvents = () => {
|
|
|
|
|
+ // 禁用链接跳转
|
|
|
|
|
+ document.addEventListener('click', preventExternalLinks, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用右键菜单 - 使用捕获阶段和多种方式确保生效
|
|
|
|
|
+ document.addEventListener('contextmenu', preventContextMenu, true)
|
|
|
|
|
+ window.addEventListener('contextmenu', preventContextMenu, true)
|
|
|
|
|
+ document.body?.addEventListener('contextmenu', preventContextMenu, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用文本选择
|
|
|
|
|
+ document.addEventListener('selectstart', preventSelection, true)
|
|
|
|
|
+ document.addEventListener('dragstart', preventSelection, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用键盘快捷键
|
|
|
|
|
+ document.addEventListener('keydown', preventKeyboardShortcuts, true)
|
|
|
|
|
+ window.addEventListener('keydown', preventKeyboardShortcuts, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 添加 CSS 禁用文本选择
|
|
|
|
|
+ const style = document.createElement('style')
|
|
|
|
|
+ style.textContent = `
|
|
|
|
|
+ * {
|
|
|
|
|
+ -webkit-user-select: none !important;
|
|
|
|
|
+ -moz-user-select: none !important;
|
|
|
|
|
+ -ms-user-select: none !important;
|
|
|
|
|
+ user-select: none !important;
|
|
|
|
|
+ -webkit-touch-callout: none !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 允许输入框选择文本 */
|
|
|
|
|
+ input, textarea, [contenteditable="true"] {
|
|
|
|
|
+ -webkit-user-select: text !important;
|
|
|
|
|
+ -moz-user-select: text !important;
|
|
|
|
|
+ -ms-user-select: text !important;
|
|
|
|
|
+ user-select: text !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ `
|
|
|
|
|
+ document.head.appendChild(style)
|
|
|
|
|
+
|
|
|
|
|
+ console.log('安全限制已启用:禁用外部链接、右键菜单、文本选择和特定快捷键')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果 DOM 已加载,立即绑定;否则等待 DOMContentLoaded
|
|
|
|
|
+ if (document.readyState === 'loading') {
|
|
|
|
|
+ document.addEventListener('DOMContentLoaded', bindEvents)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ bindEvents()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 额外保险:在 body 存在后再绑定一次
|
|
|
|
|
+ if (!document.body) {
|
|
|
|
|
+ const observer = new MutationObserver(() => {
|
|
|
|
|
+ if (document.body) {
|
|
|
|
|
+ document.body.addEventListener('contextmenu', preventContextMenu, true)
|
|
|
|
|
+ observer.disconnect()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ observer.observe(document.documentElement, { childList: true })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 立即执行初始化
|
|
|
|
|
+initSecurityRestrictions()
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ install(app: App) {
|
|
|
|
|
+ // Vue 插件安装时不需要做额外操作,事件已在模块加载时绑定
|
|
|
|
|
+ console.log('安全限制插件已安装')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|