|
|
@@ -12,7 +12,14 @@
|
|
|
class="header"
|
|
|
>
|
|
|
<h1 class="title">
|
|
|
- {{ systemTitle }}
|
|
|
+ <button
|
|
|
+ class="title-copy-btn"
|
|
|
+ type="button"
|
|
|
+ title="点击复制当前登录 Token"
|
|
|
+ @click="handleTitleTokenCopy"
|
|
|
+ >
|
|
|
+ {{ systemTitle }}
|
|
|
+ </button>
|
|
|
<span class="title-meta">库位
|
|
|
<span class="title-meta-value">{{ filteredOccupiedLocationsCount }}/{{ filteredLocationsCount }}</span></span>
|
|
|
<span class="title-fill-rate">
|
|
|
@@ -315,6 +322,25 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <Transition name="toast-fade">
|
|
|
+ <div
|
|
|
+ v-if="toastVisible"
|
|
|
+ class="copy-toast"
|
|
|
+ >
|
|
|
+ <div class="copy-toast-badge">
|
|
|
+ Token
|
|
|
+ </div>
|
|
|
+ <div class="copy-toast-content">
|
|
|
+ <div class="copy-toast-title">
|
|
|
+ {{ toastTitle }}
|
|
|
+ </div>
|
|
|
+ <div class="copy-toast-text">
|
|
|
+ {{ toastText }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+
|
|
|
<CreateLocationSqlModal
|
|
|
:visible="createLocationSqlModalVisible"
|
|
|
:floor="createLocationSqlPayload.floor"
|
|
|
@@ -389,7 +415,13 @@ import LoginModal from './components/LoginModal.vue'
|
|
|
import WorkingHighlight from './components/WorkingHighlight.vue'
|
|
|
import CreateLocationSqlModal from './components/CreateLocationSqlModal.vue'
|
|
|
import { config } from './config'
|
|
|
-import { AUTH_INVALID_EVENT, getApiEnvironment, isAuthenticated, removeToken } from './utils/auth'
|
|
|
+import {
|
|
|
+ AUTH_INVALID_EVENT,
|
|
|
+ getApiEnvironment,
|
|
|
+ getToken,
|
|
|
+ isAuthenticated,
|
|
|
+ removeToken
|
|
|
+} from './utils/auth'
|
|
|
|
|
|
const LEVEL_STORAGE_KEY = 'warehouse-map.current-level'
|
|
|
const REFRESH_INTERVAL_STORAGE_KEY = 'warehouse-map.refresh-interval-ms'
|
|
|
@@ -480,8 +512,12 @@ const fillRateTooltipPosition = ref({
|
|
|
x: 0,
|
|
|
y: 0
|
|
|
})
|
|
|
+const toastVisible = ref(false)
|
|
|
+const toastTitle = ref('')
|
|
|
+const toastText = ref('')
|
|
|
let refreshTimer: number | null = null
|
|
|
let countdownTimer: number | null = null
|
|
|
+let toastTimer: number | null = null
|
|
|
const WORKING_HIGHLIGHT_REFRESH_EVENT = 'working-highlight-refresh'
|
|
|
|
|
|
const LOCATION_ATTRIBUTE_LABEL_MAP: Record<LocationAttributeCode, string> = {
|
|
|
@@ -741,6 +777,21 @@ const handleFillRateMouseLeave = () => {
|
|
|
fillRateTooltipText.value = ''
|
|
|
}
|
|
|
|
|
|
+const showToast = (title: string, text: string) => {
|
|
|
+ toastTitle.value = title
|
|
|
+ toastText.value = text
|
|
|
+ toastVisible.value = true
|
|
|
+
|
|
|
+ if (toastTimer !== null) {
|
|
|
+ window.clearTimeout(toastTimer)
|
|
|
+ }
|
|
|
+
|
|
|
+ toastTimer = window.setTimeout(() => {
|
|
|
+ toastVisible.value = false
|
|
|
+ toastTimer = null
|
|
|
+ }, 2200)
|
|
|
+}
|
|
|
+
|
|
|
const copyText = async (text: string) => {
|
|
|
if (!text) return
|
|
|
|
|
|
@@ -762,6 +813,17 @@ const copyText = async (text: string) => {
|
|
|
document.body.removeChild(textarea)
|
|
|
}
|
|
|
|
|
|
+const handleTitleTokenCopy = async () => {
|
|
|
+ const token = getToken()
|
|
|
+ if (!token) {
|
|
|
+ showToast('未检测到 Token', '当前未登录,暂无可复制内容')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ await copyText(token)
|
|
|
+ showToast('Token 已复制', '当前登录 Token 已复制到剪贴板')
|
|
|
+}
|
|
|
+
|
|
|
const countHyphen = (text: string) => text.split('-').length - 1
|
|
|
|
|
|
const applyLocationKeywordFilter = () => {
|
|
|
@@ -1037,6 +1099,9 @@ onBeforeUnmount(() => {
|
|
|
if (countdownTimer !== null) {
|
|
|
window.clearInterval(countdownTimer)
|
|
|
}
|
|
|
+ if (toastTimer !== null) {
|
|
|
+ window.clearTimeout(toastTimer)
|
|
|
+ }
|
|
|
document.removeEventListener('mousedown', handleDocumentClick)
|
|
|
window.removeEventListener(AUTH_INVALID_EVENT, handleAuthInvalid)
|
|
|
window.removeEventListener('resize', updateSelectWidths)
|
|
|
@@ -1168,6 +1233,33 @@ onBeforeUnmount(() => {
|
|
|
text-shadow: 0 0 8px rgba(255, 255, 255, 0.08);
|
|
|
}
|
|
|
|
|
|
+.title-copy-btn {
|
|
|
+ appearance: none;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ font: inherit;
|
|
|
+ font-weight: inherit;
|
|
|
+ color: inherit;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 10px;
|
|
|
+ transition:
|
|
|
+ transform 0.18s ease,
|
|
|
+ opacity 0.18s ease,
|
|
|
+ color 0.18s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.title-copy-btn:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ opacity: 0.92;
|
|
|
+}
|
|
|
+
|
|
|
+.title-copy-btn:focus-visible {
|
|
|
+ outline: 2px solid rgba(79, 140, 255, 0.55);
|
|
|
+ outline-offset: 4px;
|
|
|
+}
|
|
|
+
|
|
|
.title-meta {
|
|
|
font-size: 11px;
|
|
|
font-weight: normal;
|
|
|
@@ -1216,6 +1308,70 @@ onBeforeUnmount(() => {
|
|
|
margin-top: 3px;
|
|
|
}
|
|
|
|
|
|
+.copy-toast {
|
|
|
+ position: fixed;
|
|
|
+ top: 18px;
|
|
|
+ right: 18px;
|
|
|
+ z-index: 1200;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ min-width: 248px;
|
|
|
+ max-width: 360px;
|
|
|
+ padding: 12px 14px;
|
|
|
+ border: 1px solid var(--panel-border);
|
|
|
+ border-radius: 14px;
|
|
|
+ background:
|
|
|
+ linear-gradient(135deg, rgba(79, 140, 255, 0.16), rgba(38, 214, 171, 0.1)), var(--panel-bg);
|
|
|
+ box-shadow: var(--panel-shadow);
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+}
|
|
|
+
|
|
|
+.copy-toast-badge {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ min-width: 48px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 10px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: rgba(79, 140, 255, 0.14);
|
|
|
+ color: var(--text);
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 28px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.copy-toast-content {
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.copy-toast-title {
|
|
|
+ color: var(--text);
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.copy-toast-text {
|
|
|
+ margin-top: 3px;
|
|
|
+ color: var(--text-muted);
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.35;
|
|
|
+}
|
|
|
+
|
|
|
+.toast-fade-enter-active,
|
|
|
+.toast-fade-leave-active {
|
|
|
+ transition:
|
|
|
+ opacity 0.2s ease,
|
|
|
+ transform 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.toast-fade-enter-from,
|
|
|
+.toast-fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-8px) scale(0.98);
|
|
|
+}
|
|
|
+
|
|
|
.title-meta-a,
|
|
|
.title-meta-b,
|
|
|
.title-meta-c {
|