feat(material): add global floating helper to track latest specification codes with smart grouping
This commit is contained in:
@ -69,4 +69,12 @@ export function batchSetInspection(data: { ids: number[], isInspectionRequired:
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 7. 获取智能分组规格最大连号
|
||||
export function getLatestSpecs() {
|
||||
return request({
|
||||
url: '/inbound/base/spec-latest',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
200
inventory-web/src/components/SpecHelper/index.vue
Normal file
200
inventory-web/src/components/SpecHelper/index.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="spec-helper" :class="{ expanded }">
|
||||
<!-- 触发按钮 -->
|
||||
<div class="trigger-btn" @click="toggle">
|
||||
<span class="arrow">{{ expanded ? '>' : '<' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 面板内容 -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="title">规格连号助手</span>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
placeholder="搜索分组或规格..."
|
||||
clearable
|
||||
size="small"
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-scrollbar class="data-list">
|
||||
<div
|
||||
v-for="item in filteredData"
|
||||
:key="item.group"
|
||||
class="data-item"
|
||||
>
|
||||
<span class="group-tag">{{ item.group }}</span>
|
||||
<span class="latest-spec">{{ item.latest }}</span>
|
||||
</div>
|
||||
<el-empty v-if="filteredData.length === 0" description="暂无数据" :image-size="60" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { getLatestSpecs } from '@/api/material_base'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface SpecItem {
|
||||
group: string
|
||||
latest: string
|
||||
}
|
||||
|
||||
const expanded = ref(false)
|
||||
const filterText = ref('')
|
||||
const specData = ref<SpecItem[]([])
|
||||
|
||||
const toggle = () => {
|
||||
expanded.value = !expanded.value
|
||||
if (expanded.value && specData.value.length === 0) {
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await getLatestSpecs()
|
||||
if (res.data.code === 200) {
|
||||
specData.value = res.data.data || []
|
||||
} else {
|
||||
ElMessage.error(res.data.msg || '获取规格数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取规格数据失败:', error)
|
||||
ElMessage.error('获取规格数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = computed(() => {
|
||||
if (!filterText.value) {
|
||||
return specData.value
|
||||
}
|
||||
const keyword = filterText.value.toLowerCase()
|
||||
return specData.value.filter(
|
||||
item =>
|
||||
item.group.toLowerCase().includes(keyword) ||
|
||||
item.latest.toLowerCase().includes(keyword)
|
||||
)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 默认不加载,展开时再加载
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.spec-helper {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.spec-helper:not(.expanded) {
|
||||
transform: translateY(-50%) translateX(calc(100% - 24px));
|
||||
}
|
||||
|
||||
.trigger-btn {
|
||||
width: 24px;
|
||||
height: 60px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px 0 0 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.trigger-btn:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel {
|
||||
width: 280px;
|
||||
height: 400px;
|
||||
background: white;
|
||||
border-radius: 4px 0 0 4px;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.data-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.data-item:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.group-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
max-width: 80px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.latest-spec {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
font-family: monospace;
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@ -5,12 +5,16 @@
|
||||
<div class="main-container">
|
||||
<AppMain />
|
||||
</div>
|
||||
|
||||
<!-- 全局规格连号助手 -->
|
||||
<SpecHelper />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import AppMain from './components/AppMain.vue'
|
||||
import SpecHelper from '@/components/SpecHelper/index.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user