feat(stocktake): 欢迎页识别跨域角色,完善场景 A 提示
- 公司选择器原先只对 SUPER_ADMIN 解锁,漏掉了同样持有 crossDomain
权限的 WAREHOUSE_MGR —— 它同样不受后端公司隔离约束,盘点范围完全
由所选公司决定,必须一并要求显式选择。
注意 SUPER_ADMIN 的权限是通配符 ['*'],permissions.includes('crossDomain')
对其恒为 false,故角色与权限码两个条件都要判。
- 场景 A 提示改为「当前公司已有盘点在进行中,已扫 XXX 件,发起人:XXX」,
使用后端新增的 scanned / initiator 字段(scanned 已排除系统自动漏盘记录)。
按钮文案统一为「加入当前盘点」。
- 清理 serverDraftCount:提示语改用 activeSession.scanned 后它只写不读,
属死状态,一并移除。
- 确认弹窗文案原先指向已不存在的「继续上次盘点」入口,改为说明开新会话后
「加入当前盘点」只会进入最新会话。
This commit is contained in:
@ -16,7 +16,7 @@
|
|||||||
placeholder="请选择盘点公司"
|
placeholder="请选择盘点公司"
|
||||||
size="large"
|
size="large"
|
||||||
class="company-select"
|
class="company-select"
|
||||||
:disabled="!isSuperAdmin"
|
:disabled="!hasCrossDomain"
|
||||||
:loading="companyLoading"
|
:loading="companyLoading"
|
||||||
@change="handleCompanyChange"
|
@change="handleCompanyChange"
|
||||||
>
|
>
|
||||||
@ -55,10 +55,10 @@
|
|||||||
@click="joinActiveSession"
|
@click="joinActiveSession"
|
||||||
:loading="btnLoading"
|
:loading="btnLoading"
|
||||||
>
|
>
|
||||||
加入正在进行的盘点
|
加入当前盘点
|
||||||
</el-button>
|
</el-button>
|
||||||
<div class="active-session-tip">
|
<div class="active-session-tip">
|
||||||
当前已有未完成的盘点进度({{ serverDraftCount }} 项)
|
当前公司已有盘点在进行中,已扫 {{ activeSession.scanned }} 件<template v-if="activeSession.initiator">,发起人:{{ activeSession.initiator }}</template>
|
||||||
</div>
|
</div>
|
||||||
<!-- 次要入口:缺了它,本司一旦有过草稿就永远开不了新一轮 -->
|
<!-- 次要入口:缺了它,本司一旦有过草稿就永远开不了新一轮 -->
|
||||||
<el-button
|
<el-button
|
||||||
@ -187,7 +187,7 @@
|
|||||||
>
|
>
|
||||||
<div class="confirm-content">
|
<div class="confirm-content">
|
||||||
<el-icon :size="48" color="#E6A23C"><WarningFilled /></el-icon>
|
<el-icon :size="48" color="#E6A23C"><WarningFilled /></el-icon>
|
||||||
<p class="confirm-text">存在未完成的盘点记录。开启新一轮盘点会生成新的会话,<strong>原有数据将保留</strong>,之后仍可通过「继续上次盘点」回到之前的会话。确定开始吗?</p>
|
<p class="confirm-text">当前公司已有盘点在进行中。开启新一轮会生成新的会话,<strong>原有数据将保留</strong>,但之后「加入当前盘点」只会进入最新的这个会话。确定开始吗?</p>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="cancelConfirm">取消</el-button>
|
<el-button @click="cancelConfirm">取消</el-button>
|
||||||
@ -509,7 +509,6 @@ const loading = ref(false)
|
|||||||
const btnLoading = ref(false)
|
const btnLoading = ref(false)
|
||||||
const printing = ref(false)
|
const printing = ref(false)
|
||||||
const isSessionActive = ref(false)
|
const isSessionActive = ref(false)
|
||||||
const serverDraftCount = ref(0)
|
|
||||||
const syncStatus = ref<'success' | 'syncing' | 'failed'>('success')
|
const syncStatus = ref<'success' | 'syncing' | 'failed'>('success')
|
||||||
|
|
||||||
// 新增:扫码相关状态
|
// 新增:扫码相关状态
|
||||||
@ -562,17 +561,30 @@ const COMPANY_STORAGE_KEY = 'stocktake_company'
|
|||||||
const selectedCompany = ref<string>('')
|
const selectedCompany = ref<string>('')
|
||||||
const companyOptions = ref<string[]>([])
|
const companyOptions = ref<string[]>([])
|
||||||
const companyLoading = ref(false)
|
const companyLoading = ref(false)
|
||||||
// 该公司的活跃会话:{ session_id, total, last_scan_time };无进行中的盘点则为 null
|
// 该公司的活跃会话;无进行中的盘点则为 null
|
||||||
const activeSession = ref<{ session_id: string; total: number; last_scan_time: string | null } | null>(null)
|
const activeSession = ref<{
|
||||||
|
session_id: string
|
||||||
|
total: number // 该会话草稿行数
|
||||||
|
scanned: number // 实际扫到的件数(按物料去重,不含系统自动漏盘)
|
||||||
|
last_scan_time: string | null
|
||||||
|
initiator: string | null
|
||||||
|
} | null>(null)
|
||||||
// ★ 探测失败标记:网络抖动时绝不能把「没查到」当成「没有盘点」——
|
// ★ 探测失败标记:网络抖动时绝不能把「没查到」当成「没有盘点」——
|
||||||
// 否则界面会退回「开始新盘点」,库管一点就新建会话,把多人协同拆开
|
// 否则界面会退回「开始新盘点」,库管一点就新建会话,把多人协同拆开
|
||||||
const probeFailed = ref(false)
|
const probeFailed = ref(false)
|
||||||
|
|
||||||
const isSuperAdmin = computed(() => userStore.role === 'SUPER_ADMIN')
|
// ★ 跨域角色:超管,或持有 crossDomain 权限的角色(当前为 WAREHOUSE_MGR)。
|
||||||
|
// 后端 get_current_company_filter() 对它们返回 None(不隔离),所以盘点范围
|
||||||
|
// 完全取决于用户选哪个公司 —— 这类用户必须显式选择,不能有默认值。
|
||||||
|
// 注意:SUPER_ADMIN 的权限是通配符 ['*'],permissions.includes('crossDomain')
|
||||||
|
// 对它恒为 false,因此角色与权限码两个条件都要判。
|
||||||
|
const hasCrossDomain = computed(() =>
|
||||||
|
userStore.role === 'SUPER_ADMIN' || userStore.permissions.includes('crossDomain')
|
||||||
|
)
|
||||||
|
|
||||||
// 选择器下方的说明文字
|
// 选择器下方的说明文字
|
||||||
const companyHint = computed(() => {
|
const companyHint = computed(() => {
|
||||||
if (isSuperAdmin.value) {
|
if (hasCrossDomain.value) {
|
||||||
return selectedCompany.value ? `当前盘点范围:${selectedCompany.value}` : '请先选择要盘点的公司'
|
return selectedCompany.value ? `当前盘点范围:${selectedCompany.value}` : '请先选择要盘点的公司'
|
||||||
}
|
}
|
||||||
if (!selectedCompany.value) return '您的账号未绑定公司,请联系管理员'
|
if (!selectedCompany.value) return '您的账号未绑定公司,请联系管理员'
|
||||||
@ -658,7 +670,7 @@ const api = {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
params: withCompany({ session_id: sessionId })
|
params: withCompany({ session_id: sessionId })
|
||||||
}),
|
}),
|
||||||
// ★ 该公司最近一次活跃的盘点会话(多人协同:用于「加入正在进行的盘点」)
|
// ★ 该公司最近一次活跃的盘点会话(多人协同:用于「加入当前盘点」)
|
||||||
activeSession: () => request({
|
activeSession: () => request({
|
||||||
url: '/v1/inbound/stock/draft/active-session',
|
url: '/v1/inbound/stock/draft/active-session',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -723,8 +735,8 @@ onMounted(async () => {
|
|||||||
const initEntry = async () => {
|
const initEntry = async () => {
|
||||||
await loadCompanies()
|
await loadCompanies()
|
||||||
|
|
||||||
if (!isSuperAdmin.value) {
|
if (!hasCrossDomain.value) {
|
||||||
// 普通用户锁定本公司(后端也会强制隔离,这里只是让选择器显示正确)
|
// 非跨域用户:后端会强制隔离到本公司,这里只是让选择器显示正确
|
||||||
const myCompany = userStore.companyName
|
const myCompany = userStore.companyName
|
||||||
if (myCompany) {
|
if (myCompany) {
|
||||||
// 本公司可能尚无物料、不在列表中,补进去否则 el-select 显示为空
|
// 本公司可能尚无物料、不在列表中,补进去否则 el-select 显示为空
|
||||||
@ -732,7 +744,7 @@ const initEntry = async () => {
|
|||||||
persistCompany(myCompany)
|
persistCompany(myCompany)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 超管沿用上次选择;必须显式选一个公司,否则无法确定盘点范围
|
// 跨域角色沿用上次选择;必须显式选一个公司,否则盘点范围无从确定
|
||||||
const saved = localStorage.getItem(COMPANY_STORAGE_KEY)
|
const saved = localStorage.getItem(COMPANY_STORAGE_KEY)
|
||||||
if (saved && companyOptions.value.includes(saved)) persistCompany(saved)
|
if (saved && companyOptions.value.includes(saved)) persistCompany(saved)
|
||||||
}
|
}
|
||||||
@ -757,27 +769,30 @@ const loadCompanies = async () => {
|
|||||||
const handleCompanyChange = async (val: string) => {
|
const handleCompanyChange = async (val: string) => {
|
||||||
clearPersistedSession()
|
clearPersistedSession()
|
||||||
activeSession.value = null
|
activeSession.value = null
|
||||||
serverDraftCount.value = 0
|
|
||||||
probeFailed.value = false
|
probeFailed.value = false
|
||||||
persistCompany(val)
|
persistCompany(val)
|
||||||
await checkServerDraft()
|
await checkServerDraft()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 探测该公司是否有进行中的盘点 —— 服务端是唯一事实来源,
|
// ★ 探测该公司是否有进行中的盘点 —— 服务端是唯一事实来源,
|
||||||
// 这样第二台 PDA 选同一公司后就能看到「加入正在进行的盘点」。
|
// 这样第二台 PDA 选同一公司后就能看到「加入当前盘点」。
|
||||||
const checkServerDraft = async () => {
|
const checkServerDraft = async () => {
|
||||||
if (!selectedCompany.value) {
|
if (!selectedCompany.value) {
|
||||||
activeSession.value = null
|
activeSession.value = null
|
||||||
serverDraftCount.value = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res: any = await api.activeSession()
|
const res: any = await api.activeSession()
|
||||||
const data = res?.data || {}
|
const data = res?.data || {}
|
||||||
activeSession.value = data.session_id
|
activeSession.value = data.session_id
|
||||||
? { session_id: data.session_id, total: data.total || 0, last_scan_time: data.last_scan_time || null }
|
? {
|
||||||
|
session_id: data.session_id,
|
||||||
|
total: data.total || 0,
|
||||||
|
scanned: data.scanned || 0,
|
||||||
|
last_scan_time: data.last_scan_time || null,
|
||||||
|
initiator: data.initiator || null
|
||||||
|
}
|
||||||
: null
|
: null
|
||||||
serverDraftCount.value = data.total || 0
|
|
||||||
probeFailed.value = false
|
probeFailed.value = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ★ 探测失败 ≠ 没有盘点。保持 activeSession 不变并把界面切到「重试」,
|
// ★ 探测失败 ≠ 没有盘点。保持 activeSession 不变并把界面切到「重试」,
|
||||||
@ -845,7 +860,7 @@ const joinActiveSession = async () => {
|
|||||||
btnLoading.value = true
|
btnLoading.value = true
|
||||||
try {
|
try {
|
||||||
persistSession(activeSession.value.session_id)
|
persistSession(activeSession.value.session_id)
|
||||||
await enterScanning('已加入正在进行的盘点')
|
await enterScanning('已加入当前盘点')
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.message || '加入失败')
|
ElMessage.error(e?.message || '加入失败')
|
||||||
} finally { btnLoading.value = false }
|
} finally { btnLoading.value = false }
|
||||||
|
|||||||
Reference in New Issue
Block a user