diff --git a/.env.development b/.env.development index eb9c025..49c3e77 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ # 开发环境 — 留空,走 vite.config.ts 中的 proxy VITE_API_BASE= +VITE_ENTRY_OPTIONS=ops,monitor diff --git a/.env.production b/.env.production index 287599d..cdd0f75 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,3 @@ # 生产环境 VITE_API_BASE=https://api.baoshi56.com +VITE_ENTRY_OPTIONS=ops,monitor diff --git a/.env.test b/.env.test index 7b6e102..240972f 100644 --- a/.env.test +++ b/.env.test @@ -1,2 +1,3 @@ # 测试环境(SIT) -VITE_API_BASE=https://sit-api.baoshi56.com +VITE_API_BASE=https://api.baoshi56.com +VITE_ENTRY_OPTIONS=monitor diff --git a/.gitignore b/.gitignore index f01bb3a..7a4e9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -13362,4 +13362,11 @@ node_modules/vue-tsc/LICENSE node_modules/vue-tsc/package.json node_modules/vue-tsc/README.md node_modules/vue-tsc/bin/vue-tsc.js -/dist/* \ No newline at end of file +/dist/* +/node_modules/fsevents/fsevents.d.ts +/node_modules/fsevents/fsevents.js +/node_modules/fsevents/fsevents.node +/node_modules/fsevents/LICENSE +/node_modules/fsevents/package.json +/node_modules/fsevents/README.md +/node_modules/* diff --git a/package.json b/package.json index 8e2bfeb..b91589d 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,12 @@ "preview": "vite preview" }, "dependencies": { - "vue": "^3.5.13", - "vue-router": "^4.5.0", - "pinia": "^2.3.0", + "@element-plus/icons-vue": "^2.3.1", "axios": "^1.7.9", "element-plus": "^2.9.1", - "@element-plus/icons-vue": "^2.3.1" + "pinia": "^2.3.0", + "vue": "^3.5.13", + "vue-router": "^4.5.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.1", diff --git a/src/api/task.ts b/src/api/task.ts index 4b958ea..ee0a080 100644 --- a/src/api/task.ts +++ b/src/api/task.ts @@ -73,6 +73,7 @@ export interface ChainQueryParams { type?: string businessNo?: string sourceContainer?: string + targetCode?: string startTime?: string endTime?: string } diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index c3168e2..1c0bfc1 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -15,7 +15,7 @@ active-text-color="#ffffff" class="aside-menu" > - + 首页 @@ -25,13 +25,13 @@ - 提升机管理 + 提升机查看 - + 厂商负载 - + 任务生成 @@ -191,7 +191,14 @@ function refreshCurrentTab() { refreshKeys.value[route.path] = (refreshKeys.value[route.path] ?? 0) + 1 } -const keepAliveNames = ['DashboardView', 'TaskChainList', 'TaskChainDetail', 'ElevatorList', 'VendorLoadView', 'TaskGenerateView'] +const userStore = useUserStore() +const isMonitorMode = computed(() => userStore.isMonitorMode()) +const keepAliveNames = computed(() => { + if (isMonitorMode.value) { + return ['TaskChainList', 'TaskChainDetail', 'ElevatorList'] + } + return ['DashboardView', 'TaskChainList', 'TaskChainDetail', 'ElevatorList', 'VendorLoadView', 'TaskGenerateView'] +}) // 必须在 watch 之前恢复,否则 watch 的 immediate 会先执行并覆盖 storage const activePath = tabsStore.restoreFromStorage() @@ -304,16 +311,15 @@ onMounted(() => { onUnmounted(() => { document.removeEventListener('click', hideTabMenu) }) -const userStore = useUserStore() const isCollapse = ref(false) -const tabs = [ - { path: '/dashboard', label: '首页', icon: Monitor }, +const tabs = computed(() => [ + ...(!isMonitorMode.value ? [{ path: '/dashboard', label: '首页', icon: Monitor }] : []), { path: '/task/chain', label: '任务链', icon: List }, { path: '/elevator', label: '提升机', icon: SetUp }, - { path: '/vendor/load', label: '厂商负载', icon: DataLine }, - { path: '/generate', label: '生成', icon: Plus }, -] + ...(!isMonitorMode.value ? [{ path: '/vendor/load', label: '厂商负载', icon: DataLine }] : []), + ...(!isMonitorMode.value ? [{ path: '/generate', label: '生成', icon: Plus }] : []), +]) const activeMenu = computed(() => { const path = route.path diff --git a/src/router/index.ts b/src/router/index.ts index 456f52d..17ca197 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,4 +1,12 @@ import { createRouter, createWebHashHistory } from 'vue-router' +import { useUserStore } from '@/stores/user' + +const monitorBlockedPaths = ['/dashboard', '/vendor/load', '/generate'] + +function getHomePath() { + const userStore = useUserStore() + return userStore.isMonitorMode() ? '/task/chain' : '/dashboard' +} const router = createRouter({ history: createWebHashHistory(), @@ -12,7 +20,7 @@ const router = createRouter({ { path: '/', component: () => import('@/layouts/AdminLayout.vue'), - redirect: '/dashboard', + redirect: () => getHomePath(), children: [ { path: 'dashboard', @@ -56,11 +64,14 @@ const router = createRouter({ }) router.beforeEach((to, _from, next) => { + const userStore = useUserStore() const token = localStorage.getItem('token') if (!to.meta.public && !token) { next('/login') } else if (to.path === '/login' && token) { - next('/') + next(getHomePath()) + } else if (!to.meta.public && userStore.isMonitorMode() && monitorBlockedPaths.includes(to.path)) { + next('/task/chain') } else { next() } diff --git a/src/stores/user.ts b/src/stores/user.ts index 6e7ea1e..379b899 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -3,6 +3,8 @@ import { ref } from 'vue' import { login as loginApi } from '@/api/auth' import router from '@/router' +export type EntryMode = 'ops' | 'monitor' + export interface UserInfo { id: number name: string @@ -12,6 +14,7 @@ export interface UserInfo { export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem('token') || '') const user = ref(parseStoredUser()) + const entryMode = ref(parseStoredEntryMode()) function parseStoredUser(): UserInfo | null { try { @@ -22,6 +25,11 @@ export const useUserStore = defineStore('user', () => { } } + function parseStoredEntryMode(): EntryMode { + const stored = localStorage.getItem('entryMode') + return stored === 'monitor' ? 'monitor' : 'ops' + } + async function login(username: string, password: string) { const res = await loginApi(username, password) const data = res.data @@ -35,6 +43,15 @@ export const useUserStore = defineStore('user', () => { localStorage.setItem('user', JSON.stringify(user.value)) } + function setEntryMode(mode: EntryMode) { + entryMode.value = mode + localStorage.setItem('entryMode', mode) + } + + function isMonitorMode(): boolean { + return entryMode.value === 'monitor' + } + function logout() { token.value = '' user.value = null @@ -47,5 +64,5 @@ export const useUserStore = defineStore('user', () => { return !!token.value } - return { token, user, login, logout, isLoggedIn } + return { token, user, entryMode, login, logout, isLoggedIn, setEntryMode, isMonitorMode } }) diff --git a/src/views/elevator/ElevatorList.vue b/src/views/elevator/ElevatorList.vue index dc5bfe3..f4c7eca 100644 --- a/src/views/elevator/ElevatorList.vue +++ b/src/views/elevator/ElevatorList.vue @@ -5,7 +5,7 @@ - 提升机管理 + 提升机查看 刷新 @@ -23,7 +23,7 @@ {{ elevator.enable ? '已启用' : '已禁用' }} - + - 提升机管理 + 提升机查看 刷新 @@ -198,7 +198,7 @@ - + userStore.isMonitorMode()) const router = useRouter() const elevators = ref([]) const refreshing = ref(false) diff --git a/src/views/login/LoginView.vue b/src/views/login/LoginView.vue index 4dab548..3da1d73 100644 --- a/src/views/login/LoginView.vue +++ b/src/views/login/LoginView.vue @@ -30,6 +30,20 @@ :prefix-icon="Lock" /> + + + 进入端口 + + + {{ item.label }} + + + + import { useUserStore } from '@/stores/user' +import type { EntryMode } from '@/stores/user' import { Lock, User } from '@element-plus/icons-vue' import { ElMessage, type FormInstance, type FormRules } from 'element-plus' -import { reactive, ref } from 'vue' +import { computed, reactive, ref, watchEffect } from 'vue' import { useRouter } from 'vue-router' const router = useRouter() @@ -88,6 +103,23 @@ const rules: FormRules = { const tokenDialogVisible = ref(false) const manualToken = ref('') +const allEntryModes: Array<{ value: EntryMode; label: string }> = [ + { value: 'ops', label: '运维端' }, + { value: 'monitor', label: '监控端' }, +] +const envEntryModes = (import.meta.env.VITE_ENTRY_OPTIONS as string | undefined) || 'ops,monitor' +const parsedEntryModes = envEntryModes.split(',').map((item) => item.trim()).filter((item) => item === 'ops' || item === 'monitor') as EntryMode[] +const availableEntryModes = computed(() => { + const modeSet = new Set(parsedEntryModes.length ? parsedEntryModes : ['ops', 'monitor']) + return allEntryModes.filter((item) => modeSet.has(item.value)) +}) +const entryMode = ref(userStore.entryMode) + +watchEffect(() => { + if (!availableEntryModes.value.some((item) => item.value === entryMode.value)) { + entryMode.value = availableEntryModes.value[0]?.value ?? 'ops' + } +}) function handleSetToken() { const token = manualToken.value.trim() @@ -95,6 +127,7 @@ function handleSetToken() { ElMessage.warning('Token 不能为空') return } + userStore.setEntryMode(entryMode.value) localStorage.setItem('token', token) tokenDialogVisible.value = false ElMessage.success('Token 已设置') @@ -107,6 +140,7 @@ async function handleLogin() { loading.value = true try { + userStore.setEntryMode(entryMode.value) await userStore.login(form.username, form.password) ElMessage.success('登录成功') router.push('/') @@ -179,4 +213,15 @@ async function handleLogin() { width: 100%; font-size: 13px; } + +.entry-mode-wrap { + width: 100%; +} + +.entry-mode-label { + display: block; + margin-bottom: 8px; + font-size: 13px; + color: #606266; +} diff --git a/src/views/task/TaskChainDetail.vue b/src/views/task/TaskChainDetail.vue index cb7ecf6..5a98744 100644 --- a/src/views/task/TaskChainDetail.vue +++ b/src/views/task/TaskChainDetail.vue @@ -18,7 +18,7 @@ {{ chainStatusMap[chain.status] || chain.status }} - + 模拟回调 - + 删除 - handleForceStatus(task, cmd)"> + handleForceStatus(task, cmd)"> 变更状态 @@ -172,7 +172,7 @@ {{ typeMap[chain.type] || chain.type }} {{ chain.code }} - + {{ task.type }} {{ task.vendor }} - + {{ task.code }} @@ -320,6 +320,7 @@ defineOptions({ name: 'TaskChainDetail' }) -import { onMounted, ref } from 'vue' +import { computed, onMounted, ref } from 'vue' import { useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { ArrowLeft, MoreFilled, VideoPlay, Switch, Delete } from '@element-plus/icons-vue' import { useDevice } from '@/composables/useDevice' +import { useUserStore } from '@/stores/user' import { chainStatusMap, getChainStatusTag } from '@/constants/chainStatus' import { taskStatusMap, getTaskStatusTag } from '@/constants/taskStatus' import { @@ -456,6 +459,8 @@ import { } from '@/api/task' const { isMobile } = useDevice() +const userStore = useUserStore() +const isMonitorMode = computed(() => userStore.isMonitorMode()) const route = useRoute() const loading = ref(false) const executing = ref(false) diff --git a/src/views/task/TaskChainList.vue b/src/views/task/TaskChainList.vue index 1b963bc..066da85 100644 --- a/src/views/task/TaskChainList.vue +++ b/src/views/task/TaskChainList.vue @@ -23,6 +23,9 @@ + + + 任务链列表 - + 手动扫描执行 @@ -97,7 +100,7 @@ 详情 @@ -139,7 +142,7 @@ - + 手动扫描执行 @@ -201,7 +204,7 @@ {{ row.createTime }} + + +