feat(stocktake): 欢迎页改为创建盘点任务表单
无活跃会话时不再只有一个孤零零的按钮,而是先配置再开始: - 盘点模式:明盘 / 盲盘,默认明盘 - 盘点范围:全仓盘点 / 活跃库位抽盘 有活跃会话时表单隐藏,显示绿色「加入当前盘点」,提示语带出该会话的 模式与范围(如「盲盘 · 全仓盘点,已扫 12 件,发起人:韩善龙」)。 次要入口「开启新一轮盘点」改为展开配置表单,并在表单内提示 「开启新一轮会把当前进行中的盘点置为已结束(历史数据仍保留)」, 提交时仍走原有的 10 秒确认弹窗。 「活跃库位抽盘」选项置灰并注明原因:范围过滤尚未实现,此时若放开, 结束盘点会把抽盘范围外的全部库存批量标成盘亏。后端同样以 400 拦截, 前后端一致,避免绕过 UI 直接调接口。 activeSession 类型与 checkServerDraft 同步补上 mode / scope_type。
This commit is contained in:
@ -46,8 +46,8 @@
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 场景 A:该公司已有进行中的盘点 → 绿色「加入」 -->
|
||||
<template v-else-if="activeSession">
|
||||
<!-- 场景 A:已有进行中的盘点,且未主动选择「开启新一轮」 → 绿色「加入」 -->
|
||||
<template v-else-if="activeSession && !creatingNew">
|
||||
<el-button
|
||||
type="success"
|
||||
size="large"
|
||||
@ -58,31 +58,47 @@
|
||||
加入当前盘点
|
||||
</el-button>
|
||||
<div class="active-session-tip">
|
||||
当前公司已有盘点在进行中,已扫 {{ activeSession.scanned }} 件<template v-if="activeSession.initiator">,发起人:{{ activeSession.initiator }}</template>
|
||||
{{ modeLabel(activeSession.mode) }} · {{ scopeLabel(activeSession.scope_type) }},已扫 {{ activeSession.scanned }} 件<template v-if="activeSession.initiator">,发起人:{{ activeSession.initiator }}</template>
|
||||
</div>
|
||||
<!-- 次要入口:缺了它,本司一旦有过草稿就永远开不了新一轮 -->
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
class="secondary-entry"
|
||||
@click="startNewSession"
|
||||
:loading="btnLoading"
|
||||
>
|
||||
<!-- 次要入口:缺了它,本司一旦有过会话就永远开不了新一轮 -->
|
||||
<el-button link type="primary" class="secondary-entry" @click="creatingNew = true">
|
||||
开启新一轮盘点
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 场景 B:该公司当前无盘点 → 蓝色「开始新盘点」 -->
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="large"
|
||||
class="action-btn-full"
|
||||
@click="startNewSession"
|
||||
:loading="btnLoading"
|
||||
>
|
||||
开始新盘点
|
||||
</el-button>
|
||||
<!-- 场景 B:创建盘点任务(无活跃会话,或主动开启新一轮) -->
|
||||
<template v-else>
|
||||
<div class="task-form">
|
||||
<div class="task-form-row">
|
||||
<span class="task-form-label">盘点模式</span>
|
||||
<el-radio-group v-model="newMode" size="large">
|
||||
<el-radio-button value="open">明盘</el-radio-button>
|
||||
<el-radio-button value="blind">盲盘</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="task-form-row">
|
||||
<span class="task-form-label">盘点范围</span>
|
||||
<el-radio-group v-model="newScopeType" size="large">
|
||||
<el-radio-button value="full">全仓盘点</el-radio-button>
|
||||
<!-- 抽盘的范围过滤尚未实现,此时开放会导致把范围外库存批量标成盘亏 -->
|
||||
<el-radio-button value="active" disabled>活跃库位抽盘</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="task-form-hint">
|
||||
{{ newMode === 'blind' ? '盲盘:作业时不显示账面数与差异' : '明盘:作业时可查看账面数与差异' }}
|
||||
</div>
|
||||
<div v-if="creatingNew" class="task-form-warn">
|
||||
开启新一轮会把当前进行中的盘点置为已结束(历史数据仍保留)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" size="large" class="action-btn-full" @click="startNewSession" :loading="btnLoading">
|
||||
开始新盘点
|
||||
</el-button>
|
||||
<el-button v-if="creatingNew" link class="secondary-entry" @click="creatingNew = false">
|
||||
返回(继续当前盘点)
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="safe-tip">
|
||||
@ -594,6 +610,8 @@ const companyLoading = ref(false)
|
||||
// 该公司的活跃会话;无进行中的盘点则为 null
|
||||
const activeSession = ref<{
|
||||
session_id: string
|
||||
mode: string | null // open(明盘) / blind(盲盘)
|
||||
scope_type: string | null // full(全仓) / active(抽盘)
|
||||
total: number // 该会话草稿行数
|
||||
scanned: number // 实际扫到的件数(按物料去重,不含系统自动漏盘)
|
||||
last_scan_time: string | null
|
||||
@ -621,6 +639,16 @@ const companyHint = computed(() => {
|
||||
return `已锁定为您所属公司:${selectedCompany.value}`
|
||||
})
|
||||
|
||||
// ★ 创建盘点任务的配置项(仅在无活跃会话、或主动选择「开启新一轮」时展示)
|
||||
const creatingNew = ref(false) // 已有活跃会话时,是否展开创建表单
|
||||
const newMode = ref<'open' | 'blind'>('open') // 盘点模式,默认明盘
|
||||
const newScopeType = ref<'full' | 'active'>('full') // 盘点范围,默认全仓
|
||||
|
||||
const MODE_LABELS: Record<string, string> = { open: '明盘', blind: '盲盘' }
|
||||
const SCOPE_LABELS: Record<string, string> = { full: '全仓盘点', active: '活跃库位抽盘' }
|
||||
const modeLabel = (m?: string | null) => (m ? MODE_LABELS[m] || m : '')
|
||||
const scopeLabel = (s?: string | null) => (s ? SCOPE_LABELS[s] || s : '')
|
||||
|
||||
const persistSession = (sessionId: string) => {
|
||||
currentSessionId.value = sessionId || ''
|
||||
if (sessionId) {
|
||||
@ -713,12 +741,12 @@ const api = {
|
||||
params: withCompany(), // ★ 公司走 query string,body 里的会被后端忽略
|
||||
data: { ...data, session_id: currentSessionId.value }
|
||||
}),
|
||||
// ★ 新增: 开始新会话
|
||||
startNewSession: () => request({
|
||||
// ★ 新增: 开始新会话(携带盘点模式与范围配置,由后端落库到 stocktake_session)
|
||||
startNewSession: (payload: { mode: string; scope_type: string }) => request({
|
||||
url: '/v1/inbound/stock/draft/start-new',
|
||||
method: 'post',
|
||||
params: withCompany(),
|
||||
data: {}
|
||||
data: payload
|
||||
}),
|
||||
// ★ 新增: 结束盘点
|
||||
finishStocktake: () => request({
|
||||
@ -818,6 +846,8 @@ const checkServerDraft = async () => {
|
||||
activeSession.value = data.session_id
|
||||
? {
|
||||
session_id: data.session_id,
|
||||
mode: data.mode || null,
|
||||
scope_type: data.scope_type || null,
|
||||
total: data.total || 0,
|
||||
scanned: data.scanned || 0,
|
||||
last_scan_time: data.last_scan_time || null,
|
||||
@ -835,6 +865,7 @@ const checkServerDraft = async () => {
|
||||
// 进入扫码页的公共收尾:落到 scanning 阶段并拉取盘点基数
|
||||
const enterScanning = async (message: string) => {
|
||||
isSessionActive.value = true
|
||||
creatingNew.value = false
|
||||
localStorage.setItem('stocktake_phase', 'scanning')
|
||||
// 最近扫码流水属于「本次作业」,换会话/重新加入都要清空,避免看到上一个会话的残留
|
||||
recentScans.value = []
|
||||
@ -872,13 +903,17 @@ const doStartNewSession = async () => {
|
||||
try {
|
||||
// ★ 先清掉旧会话,避免签发失败时残留上一个 session_id
|
||||
clearPersistedSession()
|
||||
// 后端只签发新 session_id,不删除任何历史数据
|
||||
const res: any = await api.startNewSession()
|
||||
// 后端签发新 session_id 并把盘点模式/范围落库到 stocktake_session,不删任何历史数据
|
||||
const res: any = await api.startNewSession({
|
||||
mode: newMode.value,
|
||||
scope_type: newScopeType.value
|
||||
})
|
||||
persistSession(res?.session_id || res?.data?.session_id || '')
|
||||
await enterScanning('新盘点会话已开始')
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') {
|
||||
ElMessage.error(e?.message || '操作失败')
|
||||
// 后端对尚未实现的范围会返回 400,把原因透传给用户而不是笼统说“操作失败”
|
||||
ElMessage.error(e?.response?.data?.message || e?.msg || e?.message || '操作失败')
|
||||
}
|
||||
} finally { btnLoading.value = false }
|
||||
}
|
||||
@ -1531,6 +1566,38 @@ const goToVarianceReview = () => {
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
/* ★ 创建盘点任务表单(模式 / 范围) */
|
||||
.task-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.task-form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.task-form-label {
|
||||
flex: 0 0 64px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
text-align: right;
|
||||
}
|
||||
.task-form-row :deep(.el-radio-group) { flex: 1 1 auto; }
|
||||
.task-form-hint {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
}
|
||||
.task-form-warn {
|
||||
font-size: 12px;
|
||||
color: #e6a23c;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 场景 A 的进度提示与次要入口 */
|
||||
.active-session-tip {
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user