Feat: 前端改版 Web 端界面与业务作业流程

按设计稿改版登录后的全部页面,登录页与 Logo 资源未改动。

- 全局外壳:浅色侧边栏与分组菜单、左下角用户卡片、Ctrl/⌘K 搜索面板、
  测试模式提示条、深色模式配色、窄屏适配
- 共用组件:页头 PageHeader、托盘流程条 FlowStepper、扫码反馈 ScanFeedback、
  缩短单号 DocNo、托盘档案链接 PalletCodeLink、图标 SfIcon、上级资料选择
  MasterRefSelect,以及编号段解析等工具
- 工作台重做;新增托盘档案页;托盘追溯增加单托盘入口,报表托盘号可跳转档案
- 基础资料维护:各页加指标条、名称代替 ID、详情面板;三步式批量导入预览;
  分配用户对话框标注增减;7 个新增/编辑对话框统一布局
- 业务管理:发货出库、仓库入库、工厂装填、客户现场使用、退回仓库改为
  列表 + 详情面板 + 全屏作业区;写入流程、幂等键与防串单逻辑保持不变
- 修复入库页扫码反馈条因同名状态变量遮蔽组件而不显示的问题,并新增契约检查
- 新增入库粘贴解析、退回、装填的行为测试与现场使用页新流程测试,
  同步更新业务与工作台契约脚本

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 05:03:22 -07:00
co-authored by Claude Opus 5.5
parent 3050ccdc4e
commit 167df4adb8
71 changed files with 18396 additions and 2428 deletions
@@ -9,13 +9,18 @@
</template>
<div class="right-menu">
<button type="button" class="right-menu-item search-trigger" aria-label="搜索或扫码(Ctrl K)" @click="openSearch">
<sf-icon name="search" :size="15" />
<span class="search-trigger__text">搜索或扫码</span>
<kbd class="search-trigger__kbd">Ctrl K</kbd>
</button>
<test-batch-mode-switch class="right-menu-item test-batch-mode" aria-label="测试数据模式" />
<template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" />
<el-tooltip content="全屏" effect="dark" placement="bottom">
<screenfull id="screenfull" class="right-menu-item hover-effect" />
</el-tooltip>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="主题模式" effect="dark" placement="bottom">
<el-tooltip :content="settingsStore.isDark ? '切换浅色模式' : '切换深色模式'" effect="dark" placement="bottom">
<div class="right-menu-item hover-effect theme-switch-wrapper" @click="toggleTheme">
<svg-icon v-if="settingsStore.isDark" icon-class="sunny" />
<svg-icon v-if="!settingsStore.isDark" icon-class="moon" />
@@ -27,9 +32,9 @@
</el-tooltip>
</template>
<el-dropdown @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover">
<el-dropdown v-if="!sidebarShowsUser" @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<img :src="userStore.avatar" class="user-avatar" alt="" />
<span class="user-nickname"> {{ userStore.nickName }} </span>
</div>
<template #dropdown>
@@ -59,7 +64,7 @@ import Logo from './Sidebar/Logo'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import HeaderSearch from '@/components/HeaderSearch'
import SfIcon from '@/components/SfIcon'
import TestBatchModeSwitch from '@/components/TestBatchModeSwitch'
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
@@ -68,6 +73,8 @@ import useSettingsStore from '@/store/modules/settings'
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
// 左侧导航底部已展示当前用户时,顶部不再重复显示头像
const sidebarShowsUser = computed(() => appStore.device !== 'mobile' && !appStore.sidebar.hide && settingsStore.navType == 1)
function toggleSideBar() {
appStore.toggleSideBar()
@@ -98,11 +105,15 @@ function logout() {
}).catch(() => { })
}
const emits = defineEmits(['setLayout'])
const emits = defineEmits(['setLayout', 'openSearch'])
function setLayout() {
emits('setLayout')
}
function openSearch() {
emits('openSearch')
}
async function toggleTheme(event) {
const x = event?.clientX || window.innerWidth / 2
const y = event?.clientY || window.innerHeight / 2
@@ -152,35 +163,46 @@ async function toggleTheme(event) {
}
.navbar {
height: 50px;
height: 56px;
overflow: hidden;
position: relative;
background: var(--navbar-bg);
border-bottom: 1px solid var(--el-border-color-lighter, #e4e7ed);
box-shadow: 0 1px 4px rgba(15, 34, 58, 0.04);
border-bottom: 1px solid var(--sf-border-soft, #eef0f2);
display: flex;
align-items: center;
// padding: 0 8px;
gap: 4px;
padding: 0 12px 0 8px;
box-sizing: border-box;
.hamburger-container {
line-height: 46px;
height: 100%;
height: 34px;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s;
color: var(--sf-text-3, #5b616e);
transition: background 0.15s;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
margin-right: 4px;
&:hover {
background: rgba(0, 0, 0, 0.025);
background: var(--sf-hover, rgba(15, 17, 21, 0.045));
}
:deep(div) {
padding: 0 8px !important;
}
:deep(.hamburger) {
width: 18px;
height: 18px;
}
}
.breadcrumb-container {
flex-shrink: 0;
margin-left: 4px;
}
.topmenu-container {
@@ -197,16 +219,11 @@ async function toggleTheme(event) {
margin-left: 8px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
height: 100%;
line-height: 50px;
display: flex;
align-items: center;
gap: 2px;
margin-left: auto;
min-width: 0;
flex-shrink: 1;
@@ -220,75 +237,94 @@ async function toggleTheme(event) {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 34px;
height: 34px;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: var(--el-text-color-regular, #5a5e66);
border-radius: 8px;
font-size: 17px;
color: var(--sf-text-3, #5b616e);
vertical-align: middle;
&.hover-effect {
cursor: pointer;
transition: background 0.3s, color 0.2s;
transition: background 0.15s, color 0.15s;
&:hover {
background: rgba(0, 0, 0, 0.035);
color: var(--el-color-primary, #008596);
background: var(--sf-hover, rgba(15, 17, 21, 0.045));
color: var(--sf-text-1, #0f1115);
}
}
&.theme-switch-wrapper {
display: flex;
align-items: center;
}
}
svg {
transition: transform 0.3s;
&:hover {
transform: scale(1.15);
}
}
.search-trigger {
gap: 8px;
width: 248px;
height: 34px;
margin-right: 8px;
padding: 0 8px 0 10px;
border: 1px solid var(--sf-border-strong, #dde0e4);
background: var(--sf-surface, #fff);
color: var(--sf-text-4, #6b717d);
font: inherit;
font-size: 13px;
cursor: pointer;
justify-content: flex-start;
transition: border-color 0.15s;
&:hover {
border-color: #c3c8cf;
}
.search-trigger__text {
flex: 1;
text-align: left;
}
.search-trigger__kbd {
padding: 1px 6px;
border: 1px solid var(--sf-border, #e7e9ec);
border-radius: 5px;
font-family: var(--sf-font-mono);
font-size: 11px;
color: var(--sf-text-4, #6b717d);
}
}
.avatar-container {
margin-right: 4px;
padding: 0 4px;
margin-left: 4px;
padding: 0;
.avatar-wrapper {
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 20px;
transition: background 0.25s ease;
padding: 2px 8px 2px 2px;
border-radius: 18px;
transition: background 0.15s ease;
&:hover {
background: rgba(0, 0, 0, 0.04);
background: var(--sf-hover, rgba(15, 17, 21, 0.045));
}
.user-avatar {
cursor: pointer;
width: 32px;
height: 32px;
width: 28px;
height: 28px;
border-radius: 50%;
object-fit: cover;
border: 1.5px solid var(--el-color-primary-light-7, #b2e0e6);
box-shadow: 0 2px 6px rgba(0, 133, 150, 0.12);
border: 1px solid var(--sf-border, #e7e9ec);
}
.user-nickname {
font-size: 13px;
font-weight: 500;
color: var(--el-text-color-primary, #303133);
}
i {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
color: var(--sf-text-1, #0f1115);
}
}
}
@@ -297,7 +333,20 @@ async function toggleTheme(event) {
display: flex;
align-items: center;
min-width: 0;
margin-right: 6px;
font-size: 14px;
color: inherit;
}
}
}
@media screen and (max-width: 1200px) {
.navbar .right-menu .search-trigger {
width: auto;
.search-trigger__text,
.search-trigger__kbd {
display: none;
}
}
}
@@ -315,6 +364,11 @@ async function toggleTheme(event) {
padding: 0;
}
.search-trigger {
margin-right: 2px;
border: 0;
}
.avatar-container .avatar-wrapper .user-nickname {
display: none;
}