material_manage_system/frontend/src/App.vue
文睿 张 ffcf7748fb fix: 补全公司切换的保存互斥与旧读请求中止
- 保存请求执行期间禁止切换:organization store 新增 saving/busy 与
  beginSaving/endSaving,切换守卫与切换器禁用条件均由 switching 改为 busy
- 切换提交时 abort 旧读请求:commitOrganizationId 内调用 abortReadRequests,
  不再只在 resetOrganizationContext 里取消
- validateCandidate 仅将 403 视为候选失效,网络异常/500 等向上抛出;
  initialize 置 loadError,App 据此区分“加载失败”与“请先选择公司”并支持重试
- 补竞态与中止测试;需要写请求时旧响应仍被版本丢弃的用例改用 POST 覆盖
2026-10-09 10:34:19 +08:00

102 lines
3.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="app">
<template v-if="authStore.isLoggedIn">
<AppHeader />
<div class="app-container">
<AppSidebar :active-menu="currentRoute" @menu-select="handleMenuSelect" />
<main class="app-main">
<!-- 公司尚未确定时用屏障占位,不挂载业务页面,避免组件 onMounted 发出缺少组织头的请求 -->
<div v-if="organizationBarrier" class="organization-barrier">
<el-empty v-if="organizationStore.loadError" description="公司信息加载失败,请重试">
<el-button type="primary" :loading="retrying" @click="retryInitialize">
重新加载
</el-button>
</el-empty>
<el-empty v-else description="请先选择要操作的公司" />
</div>
<router-view v-else :key="viewKey" />
</main>
</div>
</template>
<template v-else>
<router-view />
</template>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from './stores/auth'
import { useOrganizationStore } from './stores/organization'
import AppHeader from './components/layout/AppHeader.vue'
import AppSidebar from './components/layout/AppSidebar.vue'
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const organizationStore = useOrganizationStore()
// 加载失败后用户手动重试;重试本身不应被误认为公司切换,故用独立标记。
const retrying = ref(false)
const currentRoute = computed(() => route.path)
// 组织级页面在公司未确定时不挂载:由 AppHeader 的切换器引导用户先选公司。
const organizationBarrier = computed(
() => route.meta.organizationScoped === true && organizationStore.currentOrganizationId == null
)
// 组织级页面用“公司 ID + 上下文版本”作为挂载 key:切换公司即重新挂载,旧页面状态随之清空。
// 全局的用户、组织、角色管理页不作为公司数据页,key 固定、不随公司变化。
const viewKey = computed(() => {
if (route.meta.organizationScoped !== true) return 'global'
return `organization-${organizationStore.currentOrganizationId}-${organizationStore.contextVersion}`
})
function handleMenuSelect(path) {
router.push(path)
}
// 手动重试公司初始化:成功后 currentOrganizationId 变为非空,屏障自动撤下;
// 仍失败时保持“加载失败”提示,等待用户再次重试。
async function retryInitialize() {
retrying.value = true
try {
await organizationStore.initialize()
} catch {
// 错误已由 axios 拦截器提示,且 store 已置 loadError,这里不再重复弹提示。
} finally {
retrying.value = false
}
}
</script>
<style>
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
.app-container {
display: flex;
flex: 1;
overflow: hidden;
}
.app-sidebar {
width: 200px;
flex-shrink: 0;
}
.app-main {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.organization-barrier {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>