Merge remote-tracking branch 'origin/master'

# Conflicts:
#	src/pages_function/pages/materialMove/index.vue
master
黄伟杰 1 day ago
commit c4ff5f807c

@ -3,23 +3,32 @@
<NavBar :title="'物料盘点'" />
<view class="filter-bar">
<view class="keyword-box">
<input
v-model="searchKeyword"
class="keyword-input"
placeholder="搜索单号"
confirm-type="search"
@input="handleKeywordInput"
@confirm="handleSearch"
/>
</view>
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-box">
<text class="status-box-text">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
v-model="searchKeyword"
class="keyword-input"
placeholder="搜索单号"
confirm-type="search"
@input="handleKeywordInput"
@confirm="handleSearch"
/>
</view>
<view class="icon-filter-btn" @click="resetFilters">
<uni-icons type="refresh" size="24" color="#7b8491"></uni-icons>
</view>
<view class="icon-filter-btn" @click="openFilterDrawer">
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</picker>
<view class="reset-filter-btn" @click="resetFilters"></view>
</view>
<view class="filter-row quick-row">
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<scroll-view
@ -94,6 +103,30 @@
</view>
</scroll-view>
<!-- 筛选抽屉 -->
<uni-popup ref="filterPopupRef" class="material-check-filter-popup" type="right" background-color="transparent" :animation="false">
<view class="filter-drawer">
<view class="drawer-header">
<text class="drawer-title">更多筛选</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">盘点时间</text>
<view class="drawer-date">
<uni-datetime-picker v-model="checkTimeFilter" type="daterange" :clear-icon="true"
start-placeholder="开始时间" end-placeholder="结束时间" />
</view>
</view>
</view>
</scroll-view>
<view class="drawer-actions">
<view class="drawer-action reset" @click="resetFilters"></view>
<view class="drawer-action confirm" @click="confirmFilterDrawer"></view>
</view>
</view>
</uni-popup>
<view v-if="showGoTop" class="go-top-btn" @click="goTop">
<uni-icons type="arrow-up" size="20" color="#1f4b79"></uni-icons>
</view>
@ -152,14 +185,15 @@ import { DICT_TYPE, useDict } from '@/utils/dict'
const selectedStatus = ref('')
const searchKeyword = ref('')
const materialWarehouseId = ref('')
const checkTimeFilter = ref([])
const statusOptions = computed(() => {
const dictOptions = (erp_audit_status.value || [])
.filter((item) => item?.label !== undefined && item?.value !== undefined)
.map((item) => ({ ...item, value: String(item.value) }))
if (dictOptions.length) return [{ label: '全部', value: '' }, ...dictOptions]
if (dictOptions.length) return [{ label: '盘点状态', value: '' }, ...dictOptions]
return [
{ label: '全部', value: '' },
{ label: '盘点状态', value: '' },
{ label: '待提交', value: '0' },
{ label: '审核中', value: '10' },
{ label: '已通过', value: '20' },
@ -285,13 +319,15 @@ async function fetchList(reset) {
loadingMore.value = true
}
try {
const checkTimeRange = Array.isArray(checkTimeFilter.value) ? checkTimeFilter.value : []
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
no: searchKeyword.value.trim() || undefined,
status: selectedStatus.value || undefined,
warehouseId: materialWarehouseId.value || undefined,
categoryType: 2
categoryType: 2,
checkTime: checkTimeRange.length === 2 ? [checkTimeRange[0] + ' 00:00:00', checkTimeRange[1] + ' 23:59:59'] : undefined
}
const res = await getMaterialCheckPage(params)
const page = normalizePageData(res)
@ -325,10 +361,27 @@ function handleKeywordInput() {
}, 300)
}
const filterPopupRef = ref(null)
function openFilterDrawer() {
filterPopupRef.value?.open()
}
function closeFilterDrawer() {
filterPopupRef.value?.close()
}
function confirmFilterDrawer() {
closeFilterDrawer()
fetchList(true)
}
function resetFilters() {
clearSearchTimer()
searchKeyword.value = ''
selectedStatus.value = ''
checkTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
@ -588,63 +641,236 @@ onUnload(() => {
}
.filter-bar {
padding: 18rpx 14rpx 20rpx;
background: #f3f4f6;
}
.filter-row {
display: flex;
align-items: center;
gap: 12rpx;
padding: 18rpx 24rpx;
background: #fff;
gap: 18rpx;
}
.keyword-box {
flex: 1;
.quick-row {
margin-top: 18rpx;
}
.quick-row > picker {
min-width: 0;
flex: 1;
}
.keyword-wrap,
.status-filter,
.icon-filter-btn {
height: 66rpx;
padding: 0 28rpx;
background: #f4f5f7;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
border: 1rpx solid #d9dde5;
background: #ffffff;
box-sizing: border-box;
}
.keyword-wrap {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
}
.keyword-input {
width: 100%;
font-size: 24rpx;
height: 64rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #374151;
}
.status-box {
flex-shrink: 0;
.status-filter {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 66rpx;
padding: 0 28rpx;
min-width: 160rpx;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 0 18rpx 0 26rpx;
}
.status-box-text {
font-size: 24rpx;
color: #374151;
min-width: 0;
.icon-filter-btn {
width: 66rpx;
flex: 0 0 66rpx;
display: flex;
align-items: center;
justify-content: center;
border-color: transparent;
background: transparent;
}
.status-filter-text {
min-width: 0rpx;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #374151;
}
.reset-filter-btn {
height: 66rpx;
line-height: 66rpx;
padding: 0 28rpx;
.status-filter-text.placeholder {
color: #a8adb7;
}
/* ====== 筛选抽屉 ====== */
::deep(.material-check-filter-popup.right .uni-popup__content-transition) {
transform: none !important;
}
.filter-drawer {
width: 630rpx;
height: calc(100vh - var(--status-bar-height));
margin-top: var(--status-bar-height);
background: #f5f5f7;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 28rpx 0 0 28rpx;
}
.drawer-header {
height: 104rpx;
padding: 18rpx 34rpx 0;
background: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
}
.drawer-title {
color: #1f2937;
font-size: 34rpx;
line-height: 1.3;
font-weight: 700;
}
.drawer-body {
flex: 1;
min-height: 0;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.drawer-section {
margin-bottom: 18rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-fields {
display: flex;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
flex-shrink: 0;
font-weight: 500;
}
.drawer-date {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 12rpx;
}
.drawer-date :deep(.uni-date),
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
width: 100%;
}
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
min-height: 74rpx;
}
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
border: 0;
padding: 0;
background: transparent;
}
.drawer-date :deep(.uni-date-range) {
display: flex;
align-items: center;
}
.drawer-date :deep(.uni-date__x-input) {
text-align: center;
font-size: 26rpx;
color: #111827;
}
.drawer-actions {
height: 126rpx;
padding: 18rpx 28rpx 24rpx;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 0;
background: #ffffff;
box-shadow: 0 -8rpx 24rpx rgba(17, 24, 39, 0.06);
}
.drawer-action {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
border: 2rpx solid #174b78;
box-sizing: border-box;
}
.drawer-action.reset {
border-radius: 12rpx 0 0 12rpx;
background: #ffffff;
color: #174b78;
}
.drawer-action.confirm {
border-radius: 0 12rpx 12rpx 0;
background: #174b78;
color: #ffffff;
}
.list-scroll {

@ -4,14 +4,6 @@
<!-- 搜索栏 -->
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -31,6 +23,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<!-- 筛选抽屉 -->
@ -40,37 +40,23 @@
<text class="drawer-title">更多筛选</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">物料信息</text>
</view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide">
<text class="drawer-label">创建人</text>
<view class="drawer-picker" @click="toggleCreatorPanel">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="creatorPanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in creatorOptions"
:key="item.value"
:class="['drawer-option-item', selectedCreator === item.value ? 'active' : '']"
@click="selectCreator(item)"
>
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedCreator === item.value" class="drawer-option-check"></text>
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">创建人</text>
<picker class="drawer-picker-host" :range="creatorLabels" :value="creatorIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</scroll-view>
</picker>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">入库时间</text>
<view class="drawer-date">
<uni-datetime-picker v-model="inTimeFilter" type="daterange" :clear-icon="true"
start-placeholder="开始时间"
end-placeholder="结束时间" />
</view>
</view>
</view>
</view>
</scroll-view>
@ -213,7 +199,6 @@ const filterPopupRef = ref(null)
const selectedCreator = ref(null)
const inTimeFilter = ref([])
const creatorOptions = ref([])
const creatorPanelOpen = ref(false)
const statusOptions = computed(() => [
{ label: '待入库', value: '0' },
@ -241,6 +226,12 @@ const selectedCreatorLabel = computed(() => {
const found = creatorOptions.value.find((u) => u.value === selectedCreator.value)
return found ? found.label : '创建人'
})
const creatorLabels = computed(() => ['创建人', ...creatorOptions.value.map((o) => o.label)])
const creatorIndex = computed(() => {
if (!selectedCreator.value) return 0
const idx = creatorOptions.value.findIndex((o) => o.value === selectedCreator.value)
return idx >= 0 ? idx + 1 : 0
})
const list = ref([])
const loading = ref(false)
@ -353,13 +344,14 @@ function confirmFilterDrawer() {
fetchList(true)
}
function toggleCreatorPanel() {
creatorPanelOpen.value = !creatorPanelOpen.value
}
function selectCreator(item) {
selectedCreator.value = selectedCreator.value === item.value ? null : item.value
creatorPanelOpen.value = false
function onCreatorPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedCreator.value = null
} else {
const item = creatorOptions.value[index - 1]
selectedCreator.value = item ? item.value : null
}
}
async function loadCreatorOptions() {
@ -395,6 +387,7 @@ function resetFilters() {
selectedStatus.value = ''
selectedCreator.value = null
inTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
@ -574,7 +567,7 @@ onHide(() => {})
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -715,169 +708,21 @@ onHide(() => {})
color: #ffffff;
}
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
}
.drawer-field {
min-width: 0;
}
.drawer-field-wide {
grid-column: 1 / -1;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker {
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
}
.drawer-picker-text.placeholder {
color: #9ca3af;
}
.drawer-option-panel {
max-height: 360rpx;
margin-top: 12rpx;
border-radius: 12rpx;
background: #f7f8fb;
overflow: hidden;
}
.drawer-option-item {
min-height: 72rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-option-item:last-child {
border-bottom: 0;
}
.drawer-option-item.active {
background: rgba(23, 75, 120, 0.1);
}
.drawer-option-item.active .drawer-option-text {
color: #174b78;
font-weight: 600;
}
.drawer-option-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #1f2937;
}
.drawer-option-check {
flex-shrink: 0;
font-size: 28rpx;
color: #174b78;
margin-left: 16rpx;
}
.drawer-date {
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 12rpx;
}
.drawer-date :deep(.uni-date),
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
width: 100%;
}
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
min-height: 74rpx;
}
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
border: 0;
padding: 0;
background: transparent;
}
.drawer-date :deep(.uni-date-range) {
display: flex;
align-items: center;
}
.drawer-date :deep(.uni-date__x-input) {
text-align: center;
font-size: 26rpx;
color: #111827;
}
.drawer-section { margin-bottom: 18rpx; padding: 8rpx 28rpx; border-radius: 24rpx; background: #ffffff; box-sizing: border-box; }
.drawer-fields { display: flex; flex-direction: column; }
.drawer-field { min-width: 0; min-height: 98rpx; display: flex; align-items: center; gap: 20rpx; border-bottom: 1rpx solid #eceff3; box-sizing: border-box; }
.drawer-field:last-child { border-bottom: 0; }
.drawer-label { width: 160rpx; flex: 0 0 160rpx; font-size: 24rpx; line-height: 1.3; color: #4b5563; font-weight: 500; }
.drawer-picker-host { min-width: 0; flex: 1; display: block; }
.drawer-picker { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; gap: 8rpx; }
.drawer-picker-text { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #111827; text-align: right; }
.drawer-picker-text.placeholder { color: #9ca3af; }
.drawer-date { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; padding: 0 12rpx; }
.drawer-date :deep(.uni-date), .drawer-date :deep(.uni-date-editor), .drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { width: 100%; }
.drawer-date :deep(.uni-date-editor), .drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { min-height: 74rpx; }
.drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { border: 0; padding: 0; background: transparent; }
.drawer-date :deep(.uni-date-range) { display: flex; align-items: center; }
.drawer-date :deep(.uni-date__x-input) { text-align: center; font-size: 26rpx; color: #111827; }
/* ====== 列表 ====== */
.list-scroll {

@ -3,14 +3,6 @@
<NavBar :title="pageTitle" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -30,6 +22,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<uni-popup ref="filterPopupRef" class="move-filter-popup" type="right" background-color="transparent" :animation="false">
@ -41,24 +41,24 @@
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">创建人</text>
<picker class="drawer-picker-host" :range="creatorPickerLabels" :value="creatorPickerIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">调拨时间</text>
<view class="drawer-date">
<uni-datetime-picker
v-model="moveTimeFilter"
type="daterange"
:clear-icon="true"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
<picker class="drawer-picker-host" :range="creatorLabels" :value="creatorIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">调拨时间</text>
<view class="drawer-date">
<uni-datetime-picker
v-model="moveTimeFilter"
type="daterange"
:clear-icon="true"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</view>
</view>
</view>
</scroll-view>
@ -182,16 +182,13 @@ const selectedCreatorLabel = computed(() => {
const found = creatorOptions.value.find((item) => item.value === selectedCreator.value)
return found ? found.label : '创建人'
})
const creatorPickerOptions = computed(() => [
{ label: '全部创建人', value: null },
...creatorOptions.value
])
const creatorPickerLabels = computed(() => creatorPickerOptions.value.map((item) => item.label))
const creatorPickerIndex = computed(() => {
const creatorLabels = computed(() => ['创建人', ...creatorOptions.value.map((o) => o.label)])
const creatorIndex = computed(() => {
if (!selectedCreator.value) return 0
const index = creatorPickerOptions.value.findIndex((item) => String(item.value) === String(selectedCreator.value))
return index >= 0 ? index : 0
const idx = creatorOptions.value.findIndex((o) => o.value === selectedCreator.value)
return idx >= 0 ? idx + 1 : 0
})
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
@ -311,10 +308,13 @@ function confirmFilterDrawer() {
}
function onCreatorPickerChange(event) {
const index = Number(event?.detail?.value || 0)
const option = creatorPickerOptions.value[index]
selectedCreator.value = option?.value || null
if (index === 0) {
selectedCreator.value = null
} else {
const item = creatorOptions.value[index - 1]
selectedCreator.value = item ? item.value : null
}
}
async function loadCreatorOptions() {
if (creatorOptions.value.length) return
try {
@ -332,6 +332,7 @@ function resetFilters() {
selectedStatus.value = ''
selectedCreator.value = null
moveTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
async function loadMore() {
@ -414,7 +415,7 @@ onUnload(() => {
.page-container { min-height: 100vh; background: #f4f5f7; }
.filter-bar { padding: 18rpx 14rpx 20rpx; background: #f3f4f6; }
.filter-row { display: flex; align-items: center; gap: 18rpx; }
.search-row { margin-top: 18rpx; }
.quick-row { margin-top: 18rpx; }
.quick-row > picker { min-width: 0; flex: 1; }
.keyword-wrap, .status-filter, .icon-filter-btn { height: 66rpx; border: 1rpx solid #d9dde5; background: #ffffff; box-sizing: border-box; }
.keyword-wrap { min-width: 0; flex: 1; display: flex; align-items: center; }
@ -437,14 +438,12 @@ onUnload(() => {
.drawer-fields { display: flex; flex-direction: column; }
.drawer-field { min-width: 0; min-height: 98rpx; display: flex; align-items: center; gap: 20rpx; border-bottom: 1rpx solid #eceff3; box-sizing: border-box; }
.drawer-field:last-child { border-bottom: 0; }
.drawer-field-wide { grid-column: auto; }
.drawer-label { width: 160rpx; flex: 0 0 160rpx; font-size: 24rpx; line-height: 1.3; color: #4b5563; font-weight: 500; }
.drawer-picker-host { min-width: 0; flex: 1; display: block; }
.drawer-picker, .drawer-date { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; }
.drawer-picker { display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; gap: 8rpx; }
.drawer-picker { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; gap: 8rpx; }
.drawer-picker-text { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #111827; text-align: right; }
.drawer-picker-text.placeholder { color: #9ca3af; }
.drawer-date { display: flex; align-items: center; padding: 0 12rpx; }
.drawer-date { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; padding: 0 12rpx; }
.drawer-date :deep(.uni-date), .drawer-date :deep(.uni-date-editor), .drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { width: 100%; }
.drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { border: 0; padding: 0; background: transparent; }

@ -3,6 +3,13 @@
<NavBar :title="t('materialOutbound.moduleName')" />
<view class="filter-bar">
<view class="filter-row search-row">
<view class="keyword-wrap">
<input v-model="searchKeyword" class="keyword-input" type="text" :placeholder="t('materialOutbound.searchPlaceholder')" confirm-type="search" @input="handleKeywordInput" @confirm="handleSearch" />
</view>
<view class="icon-filter-btn" @click="resetFilters"><uni-icons type="refresh" size="24" color="#7b8491"></uni-icons></view>
<view class="icon-filter-btn" @click="openFilterDrawer"><uni-icons type="settings" size="24" color="#7b8491"></uni-icons></view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
@ -11,38 +18,24 @@
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input v-model="searchKeyword" class="keyword-input" type="text" :placeholder="t('materialOutbound.searchPlaceholder')" confirm-type="search" @input="handleKeywordInput" @confirm="handleSearch" />
</view>
<view class="icon-filter-btn" @click="resetFilters"><uni-icons type="refresh" size="24" color="#7b8491"></uni-icons></view>
<view class="icon-filter-btn" @click="openFilterDrawer"><uni-icons type="settings" size="24" color="#7b8491"></uni-icons></view>
</view>
</view>
<uni-popup ref="filterPopupRef" class="material-outbound-filter-popup" type="right" background-color="transparent" :animation="false">
<view class="filter-drawer">
<view class="drawer-header"><text class="drawer-title">{{ t('functionCommon.moreFilter') }}</text></view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head"><text class="drawer-section-title">{{ t('materialOutbound.materialInfo') }}</text></view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide"><text class="drawer-label">{{ t('materialOutbound.creator') }}</text>
<view class="drawer-picker" @click="toggleCreatorPanel">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="creatorPanelOpen" scroll-y class="drawer-option-panel">
<view v-for="item in creatorOptions" :key="item.value" :class="['drawer-option-item', selectedCreator === item.value ? 'active' : '']" @click="selectCreator(item)">
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedCreator === item.value" class="drawer-option-check"></text>
<view class="drawer-section drawer-fields">
<view class="drawer-field"><text class="drawer-label">{{ t('materialOutbound.creator') }}</text>
<picker class="drawer-picker-host" :range="creatorLabels" :value="creatorIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</scroll-view>
</picker>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">{{ t('materialOutbound.outboundTime') }}</text>
<view class="drawer-date"><uni-datetime-picker v-model="inTimeFilter" type="daterange" :clear-icon="true" :start-placeholder="t('materialOutbound.startTime')" :end-placeholder="t('materialOutbound.endTime')" /></view>
</view>
</view>
</view>
</scroll-view>
@ -139,7 +132,6 @@ const filterPopupRef = ref(null)
const selectedCreator = ref(null)
const inTimeFilter = ref([])
const creatorOptions = ref([])
const creatorPanelOpen = ref(false)
const statusOptions = computed(() => [
{ label: t('materialOutbound.tabPending'), value: '0' },
@ -151,6 +143,8 @@ const statusPickerLabels = computed(() => [t('materialOutbound.allStatus'), ...s
const statusPickerIndex = computed(() => { if (selectedStatus.value === '') return 0; const idx = statusOptions.value.findIndex(i => i.value === selectedStatus.value); return idx >= 0 ? idx + 1 : 0 })
const currentStatusLabel = computed(() => { if (selectedStatus.value === '') return t('materialOutbound.allStatus'); const cur = statusOptions.value.find(i => i.value === selectedStatus.value); return cur ? cur.label : t('materialOutbound.allStatus') })
const selectedCreatorLabel = computed(() => { if (!selectedCreator.value) return t('materialOutbound.creator'); const found = creatorOptions.value.find(u => u.value === selectedCreator.value); return found ? found.label : t('materialOutbound.creator') })
const creatorLabels = computed(() => [t('materialOutbound.creator'), ...creatorOptions.value.map(o => o.label)])
const creatorIndex = computed(() => { if (!selectedCreator.value) return 0; const idx = creatorOptions.value.findIndex(o => o.value === selectedCreator.value); return idx >= 0 ? idx + 1 : 0 })
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
@ -204,11 +198,10 @@ async function fetchList(reset) {
function onStatusFilterChange(event) { const index = Number(event?.detail?.value || 0); if (index === 0) { selectedStatus.value = '' } else { const item = statusOptions.value[index - 1]; selectedStatus.value = item ? item.value : '' }; fetchList(true) }
function handleSearch() { clearSearchTimer(); uni.hideKeyboard(); fetchList(true) }
function handleKeywordInput() { clearSearchTimer(); searchTimer = setTimeout(() => fetchList(true), 300) }
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; selectedCreator.value = null; inTimeFilter.value = []; fetchList(true) }
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; selectedCreator.value = null; inTimeFilter.value = []; filterPopupRef.value?.close(); fetchList(true) }
function openFilterDrawer() { loadCreatorOptions(); filterPopupRef.value?.open() }
function confirmFilterDrawer() { filterPopupRef.value?.close(); fetchList(true) }
function toggleCreatorPanel() { creatorPanelOpen.value = !creatorPanelOpen.value }
function selectCreator(item) { selectedCreator.value = selectedCreator.value === item.value ? null : item.value; creatorPanelOpen.value = false }
function onCreatorPickerChange(event) { const index = Number(event?.detail?.value || 0); if (index === 0) { selectedCreator.value = null } else { const item = creatorOptions.value[index - 1]; selectedCreator.value = item ? item.value : null } }
async function loadCreatorOptions() { if (creatorOptions.value.length) return; try { const res = await getSimpleUserList(); const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : []); creatorOptions.value = data.map(u => ({ value: u.id || u.userId, label: u.nickname || u.userName || u.name || String(u.id || '') })) } catch (e) {} }
async function loadMore() { if (loading.value || loadingMore.value || finished.value) return; pageNo.value += 1; await fetchList(false) }
function openDetail(item) { if (!item?.id) { uni.showToast({ title: t('functionCommon.noIdView'), icon: 'none' }); return }; uni.navigateTo({ url: `/pages_function/pages/materialOutbound/detail?id=${encodeURIComponent(String(item.id))}` }) }
@ -260,7 +253,7 @@ onHide(() => {})
.page-container { min-height: 100vh; background: #f4f5f7; }
.filter-bar { padding: 18rpx 14rpx 20rpx; background: #f3f4f6; }
.filter-row { display: flex; align-items: center; gap: 18rpx; }
.search-row { margin-top: 18rpx; }
.quick-row { margin-top: 18rpx; }
.quick-row > picker { min-width: 0; flex: 1; }
.keyword-wrap, .status-filter, .icon-filter-btn { height: 66rpx; border: 1rpx solid #d9dde5; background: #ffffff; box-sizing: border-box; }
.keyword-wrap { min-width: 0; flex: 1; display: flex; align-items: center; }
@ -280,24 +273,16 @@ onHide(() => {})
.drawer-action.reset { border-radius: 12rpx 0 0 12rpx; background: #ffffff; color: #174b78; }
.drawer-action.confirm { border-radius: 0 12rpx 12rpx 0; background: #174b78; color: #ffffff; }
.drawer-section { margin-bottom: 18rpx; padding: 28rpx 28rpx 30rpx; border-radius: 24rpx; background: #ffffff; box-sizing: border-box; }
.drawer-section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
.drawer-section-title { font-size: 32rpx; line-height: 1.3; color: #1f2937; font-weight: 700; }
.drawer-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22rpx 20rpx; }
.drawer-field { min-width: 0; }
.drawer-field-wide { grid-column: 1 / -1; }
.drawer-label { display: block; margin-bottom: 12rpx; font-size: 24rpx; line-height: 1.3; color: #4b5563; font-weight: 500; }
.drawer-picker { width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; justify-content: center; padding: 0 18rpx; gap: 8rpx; }
.drawer-picker-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #111827; text-align: center; }
.drawer-section { margin-bottom: 18rpx; padding: 8rpx 28rpx; border-radius: 24rpx; background: #ffffff; box-sizing: border-box; }
.drawer-fields { display: flex; flex-direction: column; }
.drawer-field { min-width: 0; min-height: 98rpx; display: flex; align-items: center; gap: 20rpx; border-bottom: 1rpx solid #eceff3; box-sizing: border-box; }
.drawer-field:last-child { border-bottom: 0; }
.drawer-label { width: 160rpx; flex: 0 0 160rpx; font-size: 24rpx; line-height: 1.3; color: #4b5563; font-weight: 500; }
.drawer-picker-host { min-width: 0; flex: 1; display: block; }
.drawer-picker { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; justify-content: flex-end; padding: 0 18rpx; gap: 8rpx; }
.drawer-picker-text { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #111827; text-align: right; }
.drawer-picker-text.placeholder { color: #9ca3af; }
.drawer-option-panel { max-height: 360rpx; margin-top: 12rpx; border-radius: 12rpx; background: #f7f8fb; overflow: hidden; }
.drawer-option-item { min-height: 72rpx; padding: 0 24rpx; display: flex; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #eceff3; box-sizing: border-box; }
.drawer-option-item:last-child { border-bottom: 0; }
.drawer-option-item.active { background: rgba(23, 75, 120, 0.1); }
.drawer-option-item.active .drawer-option-text { color: #174b78; font-weight: 600; }
.drawer-option-text { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #1f2937; }
.drawer-option-check { flex-shrink: 0; font-size: 28rpx; color: #174b78; margin-left: 16rpx; }
.drawer-date { width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; padding: 0 12rpx; }
.drawer-date { min-width: 0; flex: 1; width: 100%; min-height: 74rpx; border: 0; border-radius: 8rpx; background: #f7f8fb; box-sizing: border-box; display: flex; align-items: center; padding: 0 12rpx; }
.drawer-date :deep(.uni-date), .drawer-date :deep(.uni-date-editor), .drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { width: 100%; }
.drawer-date :deep(.uni-date-editor), .drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { min-height: 74rpx; }
.drawer-date :deep(.uni-date-editor--x), .drawer-date :deep(.uni-date-x) { border: 0; padding: 0; background: transparent; }

@ -3,14 +3,6 @@
<NavBar :title="t('moldCheck.moduleName')" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="jobStatusLabels" :value="jobStatusIndex" @change="onJobStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedJobStatus === '' ? 'placeholder' : '']">{{ currentJobStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -30,6 +22,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="jobStatusLabels" :value="jobStatusIndex" @change="onJobStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedJobStatus === '' ? 'placeholder' : '']">{{ currentJobStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<scroll-view scroll-y class="list-scroll" :scroll-top="scrollTop" @scroll="onScroll" @scrolltolower="loadMore"
@ -96,29 +96,15 @@
<text class="drawer-title">{{ t('moldCheck.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldCheck.basicInfo') }}</text>
</view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide">
<text class="drawer-label">{{ t('moldCheck.jobResult') }}</text>
<view class="drawer-picker" @click="toggleJobResultPanel">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldCheck.jobResult') }}</text>
<picker class="drawer-picker-host" :range="jobResultLabels" :value="jobResultIndex" @change="onJobResultPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedJobResult ? 'placeholder' : '']">{{ selectedJobResultLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="jobResultPanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in jobResultOptions"
:key="item.value"
:class="['drawer-option-item', selectedJobResult === item.value ? 'active' : '']"
@click="selectJobResult(item.value)"
>
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedJobResult === item.value" class="drawer-option-check"></text>
</view>
</scroll-view>
</view>
</picker>
</view>
</view>
</scroll-view>
@ -144,7 +130,6 @@ const { t } = useI18n()
const searchKeyword = ref('')
const selectedJobStatus = ref('')
const selectedJobResult = ref('')
const jobResultPanelOpen = ref(false)
const filterPopupRef = ref(null)
const focusNoKeyboardRef = ref(null)
const keywordInputSelector = '.keyword-input input, input.keyword-input'
@ -192,6 +177,13 @@ const selectedJobResultLabel = computed(() => {
return opt ? opt.label : t('moldCheck.jobResult')
})
const jobResultLabels = computed(() => [t('moldCheck.jobResult'), ...jobResultOptions.value.map((o) => o.label)])
const jobResultIndex = computed(() => {
if (!selectedJobResult.value) return 0
const idx = jobResultOptions.value.findIndex((o) => o.value === selectedJobResult.value)
return idx >= 0 ? idx + 1 : 0
})
onLoad(async () => {
await initAllDict()
await fetchList(true)
@ -281,13 +273,14 @@ function openFilterDrawer() {
filterPopupRef.value?.open()
}
function toggleJobResultPanel() {
jobResultPanelOpen.value = !jobResultPanelOpen.value
}
function selectJobResult(value) {
selectedJobResult.value = value
jobResultPanelOpen.value = false
function onJobResultPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedJobResult.value = ''
} else {
const item = jobResultOptions.value[index - 1]
selectedJobResult.value = item ? item.value : ''
}
}
function confirmFilterDrawer() {
@ -300,6 +293,7 @@ async function resetFilters() {
searchKeyword.value = ''
selectedJobStatus.value = ''
selectedJobResult.value = ''
filterPopupRef.value?.close()
await fetchList(true)
}
@ -420,7 +414,7 @@ function formatDateTime(value) {
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -528,51 +522,54 @@ function formatDateTime(value) {
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-field-wide {
grid-column: 1 / -1;
grid-column: auto;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-input,
.drawer-picker {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
@ -582,29 +579,29 @@ function formatDateTime(value) {
}
.drawer-input {
height: 74rpx;
padding: 0 18rpx;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker {
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker-text.placeholder {

@ -3,14 +3,6 @@
<NavBar :title="t('moldLedger.moduleName')" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ selectedStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -30,6 +22,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ selectedStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<scroll-view
@ -86,47 +86,24 @@
<text class="drawer-title">{{ t('moldLedger.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldLedger.basicInfo') }}</text>
</view>
<view class="drawer-grid">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldLedger.productModel') }}</text>
<view class="drawer-picker" @click="toggleProductPanel">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldLedger.productModel') }}</text>
<picker class="drawer-picker-host" :range="productPickerLabels" :value="productPickerIndex" @change="onProductPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', selectedProductId == null ? 'placeholder' : '']">{{ selectedProductLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="productPanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in productOptions"
:key="item.id"
:class="['drawer-option-item', selectedProductId === item.id ? 'active' : '']"
@click="selectProduct(item)"
>
<text class="drawer-option-text">{{ item.name }}</text>
<text v-if="selectedProductId === item.id" class="drawer-option-check"></text>
</view>
</scroll-view>
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldLedger.currentDevice') }}</text>
<view class="drawer-picker" @click="toggleDevicePanel">
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldLedger.currentDevice') }}</text>
<picker class="drawer-picker-host" :range="devicePickerLabels" :value="devicePickerIndex" @change="onDevicePickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCurrentDevice ? 'placeholder' : '']">{{ selectedDeviceLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="devicePanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in deviceOptions"
:key="item.id"
:class="['drawer-option-item', selectedCurrentDevice === item.deviceName ? 'active' : '']"
@click="selectDevice(item)"
>
<text class="drawer-option-text">{{ item.deviceName }}{{ item.deviceCode }}</text>
<text v-if="selectedCurrentDevice === item.deviceName" class="drawer-option-check"></text>
</view>
</scroll-view>
</view>
</picker>
</view>
</view>
</scroll-view>
@ -165,9 +142,6 @@ const productOptions = ref([])
const deviceOptions = ref([])
const selectedProductId = ref(null)
const selectedCurrentDevice = ref('')
//
const productPanelOpen = ref(false)
const devicePanelOpen = ref(false)
const scrollTop = ref(0)
const showGoTop = ref(false)
const focusNoKeyboardRef = ref(null)
@ -207,6 +181,20 @@ const selectedDeviceLabel = computed(() => {
return found ? `${found.deviceName}${found.deviceCode}` : selectedCurrentDevice.value
})
// picker range & index
const productPickerLabels = computed(() => [t('moldLedger.productModel'), ...productOptions.value.map((p) => p.name)])
const productPickerIndex = computed(() => {
if (selectedProductId.value == null) return 0
const idx = productOptions.value.findIndex((p) => p.id === selectedProductId.value)
return idx >= 0 ? idx + 1 : 0
})
const devicePickerLabels = computed(() => [t('moldLedger.currentDevice'), ...deviceOptions.value.map((d) => `${d.deviceName}${d.deviceCode}`)])
const devicePickerIndex = computed(() => {
if (!selectedCurrentDevice.value) return 0
const idx = deviceOptions.value.findIndex((d) => d.deviceName === selectedCurrentDevice.value)
return idx >= 0 ? idx + 1 : 0
})
onLoad(async () => {
await initAllDict()
await Promise.all([fetchProductOptions(), fetchDeviceOptions()])
@ -248,24 +236,24 @@ function openFilterDrawer() {
filterPopupRef.value?.open()
}
function toggleProductPanel() {
productPanelOpen.value = !productPanelOpen.value
devicePanelOpen.value = false
}
function toggleDevicePanel() {
devicePanelOpen.value = !devicePanelOpen.value
productPanelOpen.value = false
}
function selectProduct(item) {
selectedProductId.value = selectedProductId.value === item.id ? null : item.id
productPanelOpen.value = false
function onProductPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedProductId.value = null
} else {
const item = productOptions.value[index - 1]
selectedProductId.value = item ? item.id : null
}
}
function selectDevice(item) {
selectedCurrentDevice.value = item.deviceName
devicePanelOpen.value = false
function onDevicePickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedCurrentDevice.value = ''
} else {
const item = deviceOptions.value[index - 1]
selectedCurrentDevice.value = item ? item.deviceName : ''
}
}
function confirmFilterDrawer() {
@ -278,6 +266,7 @@ async function resetFilters() {
selectedStatus.value = ''
selectedProductId.value = null
selectedCurrentDevice.value = ''
filterPopupRef.value?.close()
await fetchList(true)
}
@ -445,7 +434,7 @@ function formatDateTime(value) {
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -697,51 +686,54 @@ function formatDateTime(value) {
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-field-wide {
grid-column: 1 / -1;
grid-column: auto;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-input,
.drawer-picker {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
@ -751,29 +743,29 @@ function formatDateTime(value) {
}
.drawer-input {
height: 74rpx;
padding: 0 18rpx;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker {
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker-text.placeholder {

@ -3,14 +3,6 @@
<NavBar :title="t('moldMaintain.moduleName')" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="jobStatusLabels" :value="jobStatusIndex" @change="onJobStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedJobStatus === '' ? 'placeholder' : '']">{{ currentJobStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -30,6 +22,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="jobStatusLabels" :value="jobStatusIndex" @change="onJobStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedJobStatus === '' ? 'placeholder' : '']">{{ currentJobStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<scroll-view scroll-y class="list-scroll" :scroll-top="scrollTop" @scroll="onScroll" @scrolltolower="loadMore"
@ -96,29 +96,15 @@
<text class="drawer-title">{{ t('moldMaintain.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldMaintain.basicInfo') }}</text>
</view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide">
<text class="drawer-label">{{ t('moldMaintain.jobResult') }}</text>
<view class="drawer-picker" @click="toggleJobResultPanel">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldMaintain.jobResult') }}</text>
<picker class="drawer-picker-host" :range="jobResultLabels" :value="jobResultIndex" @change="onJobResultPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedJobResult ? 'placeholder' : '']">{{ selectedJobResultLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="jobResultPanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in jobResultOptions"
:key="item.value"
:class="['drawer-option-item', selectedJobResult === item.value ? 'active' : '']"
@click="selectJobResult(item.value)"
>
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedJobResult === item.value" class="drawer-option-check"></text>
</view>
</scroll-view>
</view>
</picker>
</view>
</view>
</scroll-view>
@ -144,7 +130,6 @@ const { t } = useI18n()
const searchKeyword = ref('')
const selectedJobStatus = ref('')
const selectedJobResult = ref('')
const jobResultPanelOpen = ref(false)
const filterPopupRef = ref(null)
const focusNoKeyboardRef = ref(null)
const keywordInputSelector = '.keyword-input input, input.keyword-input'
@ -191,6 +176,13 @@ const selectedJobResultLabel = computed(() => {
return opt ? opt.label : t('moldMaintain.jobResult')
})
const jobResultLabels = computed(() => [t('moldMaintain.jobResult'), ...jobResultOptions.value.map((o) => o.label)])
const jobResultIndex = computed(() => {
if (!selectedJobResult.value) return 0
const idx = jobResultOptions.value.findIndex((o) => o.value === selectedJobResult.value)
return idx >= 0 ? idx + 1 : 0
})
onLoad(async () => {
await initAllDict()
await fetchList(true)
@ -280,13 +272,14 @@ function openFilterDrawer() {
filterPopupRef.value?.open()
}
function toggleJobResultPanel() {
jobResultPanelOpen.value = !jobResultPanelOpen.value
}
function selectJobResult(value) {
selectedJobResult.value = value
jobResultPanelOpen.value = false
function onJobResultPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedJobResult.value = ''
} else {
const item = jobResultOptions.value[index - 1]
selectedJobResult.value = item ? item.value : ''
}
}
function confirmFilterDrawer() {
@ -299,6 +292,7 @@ async function resetFilters() {
searchKeyword.value = ''
selectedJobStatus.value = ''
selectedJobResult.value = ''
filterPopupRef.value?.close()
await fetchList(true)
}
@ -419,7 +413,7 @@ function formatDateTime(value) {
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -527,71 +521,79 @@ function formatDateTime(value) {
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-field-wide {
grid-column: 1 / -1;
grid-column: auto;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-input,
.drawer-picker {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
}
.drawer-picker {
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker-text.placeholder {

@ -3,21 +3,23 @@
<NavBar :title="t('moldPressureNet.historyTitle')" />
<view class="filter-bar">
<view class="keyword-box">
<input
v-model="brandNameKeyword"
class="keyword-input"
:placeholder="t('moldPressureNet.searchBrandName')"
confirm-type="search"
@confirm="fetchList(true)"
/>
</view>
<view class="keyword-box time-box" @click="openTimePicker">
<text :class="pressureNetTimeFilter ? 'time-text' : 'time-placeholder'">{{ pressureNetTimeFilter || t('moldPressureNet.pressureNetTime') }}</text>
<uni-icons type="calendar" size="18" color="#9ca3af"></uni-icons>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
v-model="brandNameKeyword"
class="keyword-input"
:placeholder="t('moldPressureNet.searchBrandName')"
confirm-type="search"
@confirm="fetchList(true)"
/>
</view>
<view class="icon-filter-btn" @click="resetFilter">
<uni-icons type="refresh" size="24" color="#7b8491"></uni-icons>
</view>
<view class="icon-filter-btn" @click="openFilterDrawer">
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-btn" @click="fetchList(true)">{{ t('functionCommon.search') }}</view>
<view class="filter-btn" @click="resetFilter">{{ t('functionCommon.reset') }}</view>
</view>
<scroll-view scroll-y class="list-scroll" @scrolltolower="loadMore" :lower-threshold="80">
@ -50,26 +52,29 @@
</view>
</scroll-view>
<!-- 压网时间筛选弹窗 -->
<view v-if="showTimePicker" class="picker-mask" @click="closeTimePicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
<text class="picker-title">{{ t('moldPressureNet.pressureNetTime') }}</text>
<view class="picker-close" @click="closeTimePicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
<!-- 筛选抽屉 -->
<uni-popup ref="filterPopupRef" class="pressure-net-filter-popup" type="right" background-color="transparent" :animation="false">
<view class="filter-drawer">
<view class="drawer-header">
<text class="drawer-title">更多筛选</text>
</view>
<view class="picker-body">
<picker mode="date" :value="tempDate" @change="onDateChange">
<view class="time-field">{{ tempDate || $t('moldPressureNet.selectDate') }}</view>
</picker>
</view>
<view class="picker-footer-bar">
<view class="picker-footer-btn secondary" @click="clearTimeFilter">{{ $t('functionCommon.clear') }}</view>
<view class="picker-footer-btn primary" @click="confirmTimeFilter">{{ $t('common.complete') }}</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldPressureNet.pressureNetTime') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="pressureNetTimeFilter" type="daterange" :clear-icon="true"
start-placeholder="开始时间" end-placeholder="结束时间" />
</view>
</view>
</view>
</scroll-view>
<view class="drawer-actions">
<view class="drawer-action reset" @click="resetFilter">{{ t('functionCommon.reset') }}</view>
<view class="drawer-action confirm" @click="confirmFilterDrawer">{{ t('functionCommon.confirm') }}</view>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
@ -89,11 +94,8 @@ const finished = ref(false)
const pageNo = ref(1)
const pageSize = ref(10)
const brandNameKeyword = ref('')
const pressureNetTimeFilter = ref('')
//
const showTimePicker = ref(false)
const tempDate = ref('')
const pressureNetTimeFilter = ref([])
const filterPopupRef = ref(null)
onLoad(() => {
fetchList(true)
@ -124,14 +126,17 @@ async function fetchList(reset) {
loadingMore.value = true
}
try {
const timeRange = Array.isArray(pressureNetTimeFilter.value) ? pressureNetTimeFilter.value : []
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value
}
const brandName = brandNameKeyword.value.trim()
if (brandName) params.moldBrandName = brandName
const timeVal = pressureNetTimeFilter.value.trim()
if (timeVal) params.pressureNetTime = timeVal
if (timeRange.length === 2) {
params['pressureNetTime[0]'] = timeRange[0] + ' 00:00:00'
params['pressureNetTime[1]'] = timeRange[1] + ' 23:59:59'
}
const res = await getPressureNetRecordPage(params)
const page = normalizePageData(res)
list.value = reset ? page.list : [...list.value, ...page.list]
@ -151,36 +156,23 @@ async function loadMore() {
await fetchList(false)
}
function resetFilter() {
brandNameKeyword.value = ''
pressureNetTimeFilter.value = ''
fetchList(true)
function openFilterDrawer() {
filterPopupRef.value?.open()
}
//
function openTimePicker() {
tempDate.value = pressureNetTimeFilter.value || ''
showTimePicker.value = true
function closeFilterDrawer() {
filterPopupRef.value?.close()
}
function closeTimePicker() {
showTimePicker.value = false
}
function onDateChange(event) {
tempDate.value = event?.detail?.value || ''
}
function confirmTimeFilter() {
pressureNetTimeFilter.value = tempDate.value
closeTimePicker()
function confirmFilterDrawer() {
closeFilterDrawer()
fetchList(true)
}
function clearTimeFilter() {
pressureNetTimeFilter.value = ''
tempDate.value = ''
closeTimePicker()
function resetFilter() {
brandNameKeyword.value = ''
pressureNetTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
@ -200,14 +192,206 @@ function formatDateTime(value) {
<style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f7fb; }
.filter-bar { display: flex; align-items: center; gap: 12rpx; padding: 18rpx 24rpx 0; flex-wrap: wrap; }
.keyword-box, .filter-btn { height: 66rpx; background: #ffffff; border: 1rpx solid #d9dde5; box-sizing: border-box; display: flex; align-items: center; }
.keyword-box { flex: 1; min-width: 200rpx; padding: 0 20rpx; }
.keyword-input { width: 100%; font-size: 24rpx; color: #374151; }
.time-box { justify-content: space-between; min-width: 220rpx; }
.time-text { font-size: 24rpx; color: #374151; }
.time-placeholder { font-size: 24rpx; color: #9ca3af; }
.filter-btn { flex-shrink: 0; justify-content: center; min-width: 104rpx; padding: 0 18rpx; font-size: 24rpx; color: #4b5563; }
.filter-bar {
padding: 18rpx 14rpx 20rpx;
background: #f3f4f6;
}
.filter-row {
display: flex;
align-items: center;
gap: 18rpx;
}
.keyword-wrap,
.icon-filter-btn {
height: 66rpx;
border: 1rpx solid #d9dde5;
background: #ffffff;
box-sizing: border-box;
}
.keyword-wrap {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
}
.keyword-input {
width: 100%;
height: 64rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #374151;
}
.icon-filter-btn {
width: 66rpx;
flex: 0 0 66rpx;
display: flex;
align-items: center;
justify-content: center;
border-color: transparent;
background: transparent;
}
::deep(.pressure-net-filter-popup.right .uni-popup__content-transition) {
transform: none !important;
}
.filter-drawer {
width: 630rpx;
height: calc(100vh - var(--status-bar-height));
margin-top: var(--status-bar-height);
background: #f5f5f7;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 28rpx 0 0 28rpx;
}
.drawer-header {
height: 104rpx;
padding: 18rpx 34rpx 0;
background: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
}
.drawer-title {
color: #1f2937;
font-size: 34rpx;
line-height: 1.3;
font-weight: 700;
}
.drawer-body {
flex: 1;
min-height: 0;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.drawer-section {
margin-bottom: 18rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-fields {
display: flex;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-date {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 12rpx;
}
.drawer-date :deep(.uni-date),
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
width: 100%;
}
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
min-height: 74rpx;
}
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
border: 0;
padding: 0;
background: transparent;
}
.drawer-date :deep(.uni-date-range) {
display: flex;
align-items: center;
}
.drawer-date :deep(.uni-date__x-input) {
text-align: center;
font-size: 26rpx;
color: #111827;
}
.drawer-actions {
height: 126rpx;
padding: 18rpx 28rpx 24rpx;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 0;
background: #ffffff;
box-shadow: 0 -8rpx 24rpx rgba(17, 24, 39, 0.06);
}
.drawer-action {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
border: 2rpx solid #174b78;
box-sizing: border-box;
}
.drawer-action.reset {
border-radius: 12rpx 0 0 12rpx;
background: #ffffff;
color: #174b78;
}
.drawer-action.confirm {
border-radius: 0 12rpx 12rpx 0;
background: #174b78;
color: #ffffff;
}
.list-scroll { height: calc(100vh - 170rpx); }
.list-wrap { padding: 4rpx 24rpx 40rpx; }
.record-card { margin-top: 20rpx; padding: 24rpx; border-radius: 16rpx; background: #ffffff; box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06); }
@ -219,15 +403,5 @@ function formatDateTime(value) {
.record-label { width: 150rpx; flex-shrink: 0; font-size: 26rpx; color: #8a94a6; }
.record-value { flex: 1; text-align: right; font-size: 27rpx; color: #374151; line-height: 1.5; word-break: break-all; }
.hint { padding: 40rpx 0; text-align: center; color: #9ca3af; font-size: 26rpx; }
.picker-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 100; display: flex; align-items: flex-end; }
.picker-popup { width: 100%; background: #ffffff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom); }
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 1rpx solid #e5e7eb; }
.picker-title { font-size: 34rpx; font-weight: 600; color: #1f2937; }
.picker-close { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; }
.picker-body { padding: 32rpx; }
.time-field { height: 80rpx; display: flex; align-items: center; justify-content: center; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; font-size: 28rpx; color: #374151; }
.picker-footer-bar { display: flex; gap: 16rpx; padding: 16rpx 32rpx 24rpx; }
.picker-footer-btn { flex: 1; height: 80rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; }
.picker-footer-btn.secondary { background: #f3f4f6; color: #6b7280; }
.picker-footer-btn.primary { background: linear-gradient(135deg, #1f7cff, #3b82f6); color: #ffffff; }
</style>

@ -3,14 +3,6 @@
<NavBar :title="t('moldRepair.moduleName')" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ selectedStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -34,6 +26,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="statusLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ selectedStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<uni-popup ref="filterPopupRef" class="repair-filter-popup" type="right" background-color="transparent" :animation="false">
@ -42,70 +42,54 @@
<text class="drawer-title">{{ t('functionCommon.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldRepair.basicInfo') }}</text>
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.repairCode') }}</text>
<input v-model="filterRepairCode" class="drawer-input" type="text" :placeholder="t('moldRepair.repairCode')" />
</view>
<view class="drawer-grid">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.repairCode') }}</text>
<input v-model="filterRepairCode" class="drawer-input" type="text" :placeholder="t('moldRepair.repairCode')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.repairName') }}</text>
<input v-model="filterRepairName" class="drawer-input" type="text" :placeholder="t('moldRepair.repairName')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.moldCode') }}</text>
<input v-model="filterMoldCode" class="drawer-input" type="text" :placeholder="t('moldRepair.moldCode')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.moldName') }}</text>
<input v-model="filterMoldName" class="drawer-input" type="text" :placeholder="t('moldRepair.moldName')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.acceptedBy') }}</text>
<input v-model="filterAcceptedBy" class="drawer-input" type="text" :placeholder="t('moldRepair.acceptedBy')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.status') }}</text>
<picker :range="drawerStatusLabels" :value="drawerStatusIndex" @change="onDrawerStatusChange">
<view class="drawer-input" style="display:flex;align-items:center;justify-content:center;">
<text :class="filterDrawerStatus === '' ? 'drawer-input-text placeholder' : 'drawer-input-text'">{{ drawerStatusLabel }}</text>
</view>
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.repairName') }}</text>
<input v-model="filterRepairName" class="drawer-input" type="text" :placeholder="t('moldRepair.repairName')" />
</view>
</view>
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldRepair.requireDate') }}</text>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.moldCode') }}</text>
<input v-model="filterMoldCode" class="drawer-input" type="text" :placeholder="t('moldRepair.moldCode')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.moldName') }}</text>
<input v-model="filterMoldName" class="drawer-input" type="text" :placeholder="t('moldRepair.moldName')" />
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.acceptedBy') }}</text>
<input v-model="filterAcceptedBy" class="drawer-input" type="text" :placeholder="t('moldRepair.acceptedBy')" />
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.status') }}</text>
<picker class="drawer-picker-host" :range="drawerStatusLabels" :value="drawerStatusIndex" @change="onDrawerStatusChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', filterDrawerStatus === '' ? 'placeholder' : '']">{{ drawerStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.requireDate') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="filterRequireDate" type="daterange" :clear-icon="true"
:start-placeholder="t('moldRepair.startTime')"
:end-placeholder="t('moldRepair.endTime')" />
</view>
</view>
</view>
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldRepair.finishDate') }}</text>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.finishDate') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="filterFinishDate" type="daterange" :clear-icon="true"
:start-placeholder="t('moldRepair.startTime')"
:end-placeholder="t('moldRepair.endTime')" />
</view>
</view>
</view>
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldRepair.confirmDate') }}</text>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldRepair.confirmDate') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="filterConfirmDate" type="daterange" :clear-icon="true"
:start-placeholder="t('moldRepair.startTime')"
@ -370,6 +354,7 @@ async function resetFilters() {
filterRequireDate.value = []
filterFinishDate.value = []
filterConfirmDate.value = []
filterPopupRef.value?.close()
activateKeywordFocus()
await fetchList(true)
}
@ -541,7 +526,7 @@ function textValue(value) {
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -688,60 +673,68 @@ function textValue(value) {
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-field-wide {
grid-column: 1 / -1;
grid-column: auto;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-input {
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-input,
.drawer-picker,
.drawer-date {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
}
.drawer-input {
padding: 0 18rpx;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-input-text {
@ -753,13 +746,30 @@ function textValue(value) {
color: #a8adb7;
}
.drawer-picker {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: right;
}
.drawer-picker-text.placeholder {
color: #9ca3af;
}
.drawer-date {
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 12rpx;

@ -122,11 +122,8 @@
<text class="drawer-title">{{ t('moldOperate.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('moldOperate.dateFilter') }}</text>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('moldOperate.operateTime') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="operateTimeFilter" type="daterange" :clear-icon="true"
@ -723,43 +720,43 @@ onShow(async () => {
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
color: #1f2937;
font-weight: 700;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field-wide {
grid-column: 1 / -1;
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-date {
width: 100%;
min-width: 0;
flex: 1;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;

@ -2,25 +2,33 @@
<view class="page-container">
<NavBar :title="'备件盘点'" />
<!-- 搜索栏 -->
<view class="filter-bar">
<view class="keyword-box">
<input
v-model="searchKeyword"
class="keyword-input"
placeholder="搜索单号"
confirm-type="search"
@input="handleKeywordInput"
@confirm="handleSearch"
/>
</view>
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-box">
<text class="status-box-text">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
v-model="searchKeyword"
class="keyword-input"
placeholder="搜索单号"
confirm-type="search"
@input="handleKeywordInput"
@confirm="handleSearch"
/>
</view>
<view class="icon-filter-btn" @click="resetFilters">
<uni-icons type="refresh" size="24" color="#7b8491"></uni-icons>
</view>
<view class="icon-filter-btn" @click="openFilterDrawer">
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</picker>
<view class="reset-filter-btn" @click="resetFilters"></view>
</view>
<view class="filter-row quick-row">
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<!-- 列表 -->
@ -101,6 +109,30 @@
</view>
</scroll-view>
<!-- 筛选抽屉 -->
<uni-popup ref="filterPopupRef" class="sparepart-check-filter-popup" type="right" background-color="transparent" :animation="false">
<view class="filter-drawer">
<view class="drawer-header">
<text class="drawer-title">更多筛选</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">盘点时间</text>
<view class="drawer-date">
<uni-datetime-picker v-model="checkTimeFilter" type="daterange" :clear-icon="true"
start-placeholder="开始时间" end-placeholder="结束时间" />
</view>
</view>
</view>
</scroll-view>
<view class="drawer-actions">
<view class="drawer-action reset" @click="resetFilters"></view>
<view class="drawer-action confirm" @click="confirmFilterDrawer"></view>
</view>
</view>
</uni-popup>
<!-- 回顶部 -->
<view v-if="showGoTop" class="go-top-btn" @click="goTop">
<uni-icons type="arrow-up" size="20" color="#1f4b79"></uni-icons>
@ -161,14 +193,15 @@ import { DICT_TYPE, useDict } from '@/utils/dict'
const selectedStatus = ref('')
const searchKeyword = ref('')
const sparepartWarehouseId = ref('')
const checkTimeFilter = ref([])
const statusOptions = computed(() => {
const dictOptions = (erp_audit_status.value || [])
.filter((item) => item?.label !== undefined && item?.value !== undefined)
.map((item) => ({ ...item, value: String(item.value) }))
if (dictOptions.length) return [{ label: '全部', value: '' }, ...dictOptions]
if (dictOptions.length) return [{ label: '盘点状态', value: '' }, ...dictOptions]
return [
{ label: '全部', value: '' },
{ label: '盘点状态', value: '' },
{ label: '待提交', value: '0' },
{ label: '审核中', value: '10' },
{ label: '已通过', value: '20' },
@ -294,12 +327,14 @@ async function fetchList(reset) {
loadingMore.value = true
}
try {
const checkTimeRange = Array.isArray(checkTimeFilter.value) ? checkTimeFilter.value : []
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
no: searchKeyword.value.trim() || undefined,
status: selectedStatus.value || undefined,
warehouseId: sparepartWarehouseId.value || undefined
warehouseId: sparepartWarehouseId.value || undefined,
checkTime: checkTimeRange.length === 2 ? [checkTimeRange[0] + ' 00:00:00', checkTimeRange[1] + ' 23:59:59'] : undefined
}
const res = await getSparepartCheckPage(params)
const page = normalizePageData(res)
@ -333,10 +368,27 @@ function handleKeywordInput() {
}, 300)
}
const filterPopupRef = ref(null)
function openFilterDrawer() {
filterPopupRef.value?.open()
}
function closeFilterDrawer() {
filterPopupRef.value?.close()
}
function confirmFilterDrawer() {
closeFilterDrawer()
fetchList(true)
}
function resetFilters() {
clearSearchTimer()
searchKeyword.value = ''
selectedStatus.value = ''
checkTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
@ -621,63 +673,236 @@ onUnload(() => {
/* ====== 搜索栏 ====== */
.filter-bar {
padding: 18rpx 14rpx 20rpx;
background: #f3f4f6;
}
.filter-row {
display: flex;
align-items: center;
gap: 12rpx;
padding: 18rpx 24rpx;
background: #fff;
gap: 18rpx;
}
.keyword-box {
flex: 1;
.quick-row {
margin-top: 18rpx;
}
.quick-row > picker {
min-width: 0;
flex: 1;
}
.keyword-wrap,
.status-filter,
.icon-filter-btn {
height: 66rpx;
padding: 0 28rpx;
background: #f4f5f7;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
border: 1rpx solid #d9dde5;
background: #ffffff;
box-sizing: border-box;
}
.keyword-wrap {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
}
.keyword-input {
width: 100%;
font-size: 24rpx;
height: 64rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #374151;
}
.status-box {
flex-shrink: 0;
.status-filter {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
height: 66rpx;
padding: 0 28rpx;
min-width: 160rpx;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 0 18rpx 0 26rpx;
}
.status-box-text {
font-size: 24rpx;
color: #374151;
min-width: 0;
.icon-filter-btn {
width: 66rpx;
flex: 0 0 66rpx;
display: flex;
align-items: center;
justify-content: center;
border-color: transparent;
background: transparent;
}
.status-filter-text {
min-width: 0rpx;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #374151;
}
.reset-filter-btn {
height: 66rpx;
line-height: 66rpx;
padding: 0 28rpx;
.status-filter-text.placeholder {
color: #a8adb7;
}
/* ====== 筛选抽屉 ====== */
::deep(.sparepart-check-filter-popup.right .uni-popup__content-transition) {
transform: none !important;
}
.filter-drawer {
width: 630rpx;
height: calc(100vh - var(--status-bar-height));
margin-top: var(--status-bar-height);
background: #f5f5f7;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 28rpx 0 0 28rpx;
}
.drawer-header {
height: 104rpx;
padding: 18rpx 34rpx 0;
background: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
}
.drawer-title {
color: #1f2937;
font-size: 34rpx;
line-height: 1.3;
font-weight: 700;
}
.drawer-body {
flex: 1;
min-height: 0;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.drawer-actions {
height: 126rpx;
padding: 18rpx 28rpx 24rpx;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 0;
background: #ffffff;
box-shadow: 0 -8rpx 24rpx rgba(17, 24, 39, 0.06);
}
.drawer-action {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
border: 2rpx solid #174b78;
box-sizing: border-box;
}
.drawer-action.reset {
border-radius: 12rpx 0 0 12rpx;
background: #ffffff;
color: #174b78;
}
.drawer-action.confirm {
border-radius: 0 12rpx 12rpx 0;
background: #174b78;
color: #ffffff;
}
.drawer-section {
margin-bottom: 18rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-fields {
display: flex;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
flex-shrink: 0;
font-weight: 500;
}
.drawer-date {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
border-radius: 8rpx;
background: #f7f8fb;
box-sizing: border-box;
display: flex;
align-items: center;
padding: 0 12rpx;
}
.drawer-date :deep(.uni-date),
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
width: 100%;
}
.drawer-date :deep(.uni-date-editor),
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
min-height: 74rpx;
}
.drawer-date :deep(.uni-date-editor--x),
.drawer-date :deep(.uni-date-x) {
border: 0;
padding: 0;
background: transparent;
}
.drawer-date :deep(.uni-date-range) {
display: flex;
align-items: center;
}
.drawer-date :deep(.uni-date__x-input) {
text-align: center;
font-size: 26rpx;
color: #111827;
}
/* ====== 列表 ====== */

@ -4,14 +4,6 @@
<!-- 搜索栏 -->
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input
@ -31,6 +23,14 @@
<uni-icons type="settings" size="24" color="#7b8491"></uni-icons>
</view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<!-- 筛选抽屉 -->
@ -40,36 +40,22 @@
<text class="drawer-title">{{ t('functionCommon.moreFilter') }}</text>
</view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head">
<text class="drawer-section-title">{{ t('sparepartInbound.sparepartInfo') }}</text>
</view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide">
<text class="drawer-label">{{ t('sparepartInbound.creator') }}</text>
<view class="drawer-picker" @click="toggleCreatorPanel">
<view class="drawer-section drawer-fields">
<view class="drawer-field">
<text class="drawer-label">{{ t('sparepartInbound.creator') }}</text>
<picker class="drawer-picker-host" :range="creatorLabels" :value="creatorIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="creatorPanelOpen" scroll-y class="drawer-option-panel">
<view
v-for="item in creatorOptions"
:key="item.value"
:class="['drawer-option-item', selectedCreator === item.value ? 'active' : '']"
@click="selectCreator(item)"
>
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedCreator === item.value" class="drawer-option-check"></text>
</view>
</scroll-view>
</view>
<view class="drawer-field drawer-field-wide">
<text class="drawer-label">{{ t('sparepartInbound.inboundTime') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="inTimeFilter" type="daterange" :clear-icon="true"
:start-placeholder="t('sparepartInbound.startTime')"
:end-placeholder="t('sparepartInbound.endTime')" />
</view>
</picker>
</view>
<view class="drawer-field">
<text class="drawer-label">{{ t('sparepartInbound.inboundTime') }}</text>
<view class="drawer-date">
<uni-datetime-picker v-model="inTimeFilter" type="daterange" :clear-icon="true"
:start-placeholder="t('sparepartInbound.startTime')"
:end-placeholder="t('sparepartInbound.endTime')" />
</view>
</view>
</view>
@ -216,7 +202,6 @@ const filterPopupRef = ref(null)
const selectedCreator = ref(null)
const inTimeFilter = ref([])
const creatorOptions = ref([])
const creatorPanelOpen = ref(false)
const statusOptions = computed(() => [
{ label: t('sparepartInbound.tabPending'), value: '0' },
@ -245,6 +230,13 @@ const selectedCreatorLabel = computed(() => {
return found ? found.label : t('sparepartInbound.creator')
})
const creatorLabels = computed(() => [t('sparepartInbound.creator'), ...creatorOptions.value.map((o) => o.label)])
const creatorIndex = computed(() => {
if (!selectedCreator.value) return 0
const idx = creatorOptions.value.findIndex((o) => o.value === selectedCreator.value)
return idx >= 0 ? idx + 1 : 0
})
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
@ -356,13 +348,14 @@ function confirmFilterDrawer() {
fetchList(true)
}
function toggleCreatorPanel() {
creatorPanelOpen.value = !creatorPanelOpen.value
}
function selectCreator(item) {
selectedCreator.value = selectedCreator.value === item.value ? null : item.value
creatorPanelOpen.value = false
function onCreatorPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedCreator.value = null
} else {
const item = creatorOptions.value[index - 1]
selectedCreator.value = item ? item.value : null
}
}
async function loadCreatorOptions() {
@ -398,6 +391,7 @@ function resetFilters() {
selectedStatus.value = ''
selectedCreator.value = null
inTimeFilter.value = []
filterPopupRef.value?.close()
fetchList(true)
}
@ -576,7 +570,7 @@ onHide(() => {})
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -719,46 +713,49 @@ onHide(() => {})
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-picker {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
@ -767,71 +764,29 @@ onHide(() => {})
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker-text.placeholder {
color: #9ca3af;
}
.drawer-option-panel {
max-height: 360rpx;
margin-top: 12rpx;
border-radius: 12rpx;
background: #f7f8fb;
overflow: hidden;
}
.drawer-option-item {
min-height: 72rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-option-item:last-child {
border-bottom: 0;
}
.drawer-option-item.active {
background: rgba(23, 75, 120, 0.1);
}
.drawer-option-item.active .drawer-option-text {
color: #174b78;
font-weight: 600;
}
.drawer-option-text {
.drawer-date {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #1f2937;
}
.drawer-field-wide {
grid-column: 1 / -1;
}
.drawer-date {
width: 100%;
min-height: 74rpx;
border: 0;

@ -3,15 +3,6 @@
<NavBar :title="t('sparepartOutbound.moduleName')" />
<view class="filter-bar">
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel
}}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
<view class="filter-row search-row">
<view class="keyword-wrap">
<input v-model="searchKeyword" class="keyword-input" type="text"
@ -23,6 +14,15 @@
<view class="icon-filter-btn" @click="openFilterDrawer"><uni-icons type="settings" size="24"
color="#7b8491"></uni-icons></view>
</view>
<view class="filter-row quick-row">
<picker :range="statusPickerLabels" :value="statusPickerIndex" @change="onStatusFilterChange">
<view class="status-filter">
<text :class="['status-filter-text', selectedStatus === '' ? 'placeholder' : '']">{{ currentStatusLabel
}}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
</picker>
</view>
</view>
<uni-popup ref="filterPopupRef" class="outbound-filter-popup" type="right" background-color="transparent"
@ -30,33 +30,23 @@
<view class="filter-drawer">
<view class="drawer-header"><text class="drawer-title">{{ t('functionCommon.moreFilter') }}</text></view>
<scroll-view scroll-y class="drawer-body">
<view class="drawer-section">
<view class="drawer-section-head"><text class="drawer-section-title">{{ t('sparepartOutbound.sparepartInfo')
}}</text></view>
<view class="drawer-grid">
<view class="drawer-field drawer-field-wide"><text class="drawer-label">{{ t('sparepartOutbound.creator')
<view class="drawer-section drawer-fields">
<view class="drawer-field"><text class="drawer-label">{{ t('sparepartOutbound.creator')
}}</text>
<view class="drawer-picker" @click="toggleCreatorPanel">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel
}}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
<scroll-view v-if="creatorPanelOpen" scroll-y class="drawer-option-panel">
<view v-for="item in creatorOptions" :key="item.value"
:class="['drawer-option-item', selectedCreator === item.value ? 'active' : '']"
@click="selectCreator(item)">
<text class="drawer-option-text">{{ item.label }}</text>
<text v-if="selectedCreator === item.value" class="drawer-option-check"></text>
<picker class="drawer-picker-host" :range="creatorLabels" :value="creatorIndex" @change="onCreatorPickerChange">
<view class="drawer-picker">
<text :class="['drawer-picker-text', !selectedCreator ? 'placeholder' : '']">{{ selectedCreatorLabel
}}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</scroll-view>
</picker>
</view>
<view class="drawer-field drawer-field-wide">
<view class="drawer-field">
<text class="drawer-label">{{ t('sparepartOutbound.outboundTime') }}</text>
<view class="drawer-date"><uni-datetime-picker v-model="inTimeFilter" type="daterange"
:clear-icon="true" :start-placeholder="t('sparepartOutbound.startTime')"
:end-placeholder="t('sparepartOutbound.endTime')" /></view>
</view>
</view>
</view>
</scroll-view>
<view class="drawer-actions">
@ -165,7 +155,6 @@ const filterPopupRef = ref(null)
const selectedCreator = ref(null)
const inTimeFilter = ref([])
const creatorOptions = ref([])
const creatorPanelOpen = ref(false)
const statusOptions = computed(() => [
{ label: t('sparepartOutbound.tabPending'), value: '0' },
@ -177,6 +166,8 @@ const statusPickerLabels = computed(() => [t('sparepartOutbound.allStatus'), ...
const statusPickerIndex = computed(() => { if (selectedStatus.value === '') return 0; const idx = statusOptions.value.findIndex(i => i.value === selectedStatus.value); return idx >= 0 ? idx + 1 : 0 })
const currentStatusLabel = computed(() => { if (selectedStatus.value === '') return t('sparepartOutbound.allStatus'); const cur = statusOptions.value.find(i => i.value === selectedStatus.value); return cur ? cur.label : t('sparepartOutbound.allStatus') })
const selectedCreatorLabel = computed(() => { if (!selectedCreator.value) return t('sparepartOutbound.creator'); const found = creatorOptions.value.find(u => u.value === selectedCreator.value); return found ? found.label : t('sparepartOutbound.creator') })
const creatorLabels = computed(() => [t('sparepartOutbound.creator'), ...creatorOptions.value.map(o => o.label)])
const creatorIndex = computed(() => { if (!selectedCreator.value) return 0; const idx = creatorOptions.value.findIndex(o => o.value === selectedCreator.value); return idx >= 0 ? idx + 1 : 0 })
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
@ -230,11 +221,18 @@ async function fetchList(reset) {
function onStatusFilterChange(event) { const index = Number(event?.detail?.value || 0); if (index === 0) { selectedStatus.value = '' } else { const item = statusOptions.value[index - 1]; selectedStatus.value = item ? item.value : '' }; fetchList(true) }
function handleSearch() { clearSearchTimer(); uni.hideKeyboard(); fetchList(true) }
function handleKeywordInput() { clearSearchTimer(); searchTimer = setTimeout(() => fetchList(true), 300) }
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; selectedCreator.value = null; inTimeFilter.value = []; fetchList(true) }
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; selectedCreator.value = null; inTimeFilter.value = []; filterPopupRef.value?.close(); fetchList(true) }
function openFilterDrawer() { loadCreatorOptions(); filterPopupRef.value?.open() }
function confirmFilterDrawer() { filterPopupRef.value?.close(); fetchList(true) }
function toggleCreatorPanel() { creatorPanelOpen.value = !creatorPanelOpen.value }
function selectCreator(item) { selectedCreator.value = selectedCreator.value === item.value ? null : item.value; creatorPanelOpen.value = false }
function onCreatorPickerChange(event) {
const index = Number(event?.detail?.value || 0)
if (index === 0) {
selectedCreator.value = null
} else {
const item = creatorOptions.value[index - 1]
selectedCreator.value = item ? item.value : null
}
}
async function loadCreatorOptions() { if (creatorOptions.value.length) return; try { const res = await getSimpleUserList(); const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : []); creatorOptions.value = data.map(u => ({ value: u.id || u.userId, label: u.nickname || u.userName || u.name || String(u.id || '') })) } catch (e) { } }
async function loadMore() { if (loading.value || loadingMore.value || finished.value) return; pageNo.value += 1; await fetchList(false) }
function openDetail(item) { if (!item?.id) { uni.showToast({ title: t('functionCommon.noIdView'), icon: 'none' }); return }; uni.navigateTo({ url: `/pages_function/pages/sparepartOutbound/detail?id=${encodeURIComponent(String(item.id))}` }) }
@ -301,7 +299,7 @@ onHide(() => { })
gap: 18rpx;
}
.search-row {
.quick-row {
margin-top: 18rpx;
}
@ -443,50 +441,49 @@ onHide(() => { })
.drawer-section {
margin-bottom: 18rpx;
padding: 28rpx 28rpx 30rpx;
padding: 8rpx 28rpx;
border-radius: 24rpx;
background: #ffffff;
box-sizing: border-box;
}
.drawer-section-head {
.drawer-fields {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.drawer-section-title {
font-size: 32rpx;
line-height: 1.3;
color: #1f2937;
font-weight: 700;
}
.drawer-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 22rpx 20rpx;
flex-direction: column;
}
.drawer-field {
min-width: 0;
min-height: 98rpx;
display: flex;
align-items: center;
gap: 20rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-field-wide {
grid-column: 1 / -1;
.drawer-field:last-child {
border-bottom: 0;
}
.drawer-label {
display: block;
margin-bottom: 12rpx;
width: 160rpx;
flex: 0 0 160rpx;
font-size: 24rpx;
line-height: 1.3;
color: #4b5563;
font-weight: 500;
}
.drawer-picker-host {
min-width: 0;
flex: 1;
display: block;
}
.drawer-picker {
min-width: 0;
flex: 1;
width: 100%;
min-height: 74rpx;
border: 0;
@ -495,74 +492,29 @@ onHide(() => { })
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-end;
padding: 0 18rpx;
gap: 8rpx;
}
.drawer-picker-text {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #111827;
text-align: center;
text-align: right;
}
.drawer-picker-text.placeholder {
color: #9ca3af;
}
.drawer-option-panel {
max-height: 360rpx;
margin-top: 12rpx;
border-radius: 12rpx;
background: #f7f8fb;
overflow: hidden;
}
.drawer-option-item {
min-height: 72rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.drawer-option-item:last-child {
border-bottom: 0;
}
.drawer-option-item.active {
background: rgba(23, 75, 120, 0.1);
}
.drawer-option-item.active .drawer-option-text {
color: #174b78;
font-weight: 600;
}
.drawer-option-text {
.drawer-date {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 26rpx;
color: #1f2937;
}
.drawer-option-check {
flex-shrink: 0;
font-size: 28rpx;
color: #174b78;
margin-left: 16rpx;
}
.drawer-date {
width: 100%;
min-height: 74rpx;
border: 0;

Loading…
Cancel
Save