|
|
|
|
@ -3,21 +3,56 @@
|
|
|
|
|
<NavBar :title="t('sparepartOutbound.moduleName')" />
|
|
|
|
|
|
|
|
|
|
<view class="filter-bar">
|
|
|
|
|
<view class="keyword-box">
|
|
|
|
|
<input v-model="searchKeyword" class="keyword-input" :placeholder="t('sparepartOutbound.searchPlaceholder')" confirm-type="search" @input="handleKeywordInput" @confirm="handleSearch" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="status-box-wrapper">
|
|
|
|
|
<view class="status-box" @click="toggleStatusDropdown"><text class="status-box-text">{{ currentStatusLabel }}</text><uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons></view>
|
|
|
|
|
<view v-if="showStatusDropdown" class="status-dropdown-panel">
|
|
|
|
|
<view v-for="(item, idx) in statusOptions" :key="idx" class="status-dropdown-item" :class="{ active: selectedStatus === item.value }" @click.stop="selectStatus(item, idx)">
|
|
|
|
|
<text class="status-dropdown-item-text">{{ item.label }}</text>
|
|
|
|
|
<text v-if="selectedStatus === item.value" class="status-dropdown-check">✓</text>
|
|
|
|
|
<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" :placeholder="t('sparepartOutbound.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="reset-filter-btn" @click="resetFilters">{{ t('functionCommon.reset') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<uni-popup ref="filterPopupRef" class="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('sparepartOutbound.sparepartInfo') }}</text></view>
|
|
|
|
|
<view class="drawer-grid">
|
|
|
|
|
<view class="drawer-field drawer-field-wide"><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>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="drawer-field drawer-field-wide">
|
|
|
|
|
<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">
|
|
|
|
|
<view class="drawer-action reset" @click="resetFilters">{{ t('functionCommon.reset') }}</view>
|
|
|
|
|
<view class="drawer-action confirm" @click="confirmFilterDrawer">{{ t('functionCommon.confirm') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</uni-popup>
|
|
|
|
|
|
|
|
|
|
<scroll-view scroll-y class="list-scroll" :scroll-top="scrollTop" @scroll="onScroll" @scrolltolower="loadMore" :lower-threshold="80">
|
|
|
|
|
<view class="list-wrap">
|
|
|
|
|
<view v-for="item in list" :key="item.id" class="task-card" @click="openDetail(item)">
|
|
|
|
|
@ -100,15 +135,22 @@ const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
const selectedStatus = ref('')
|
|
|
|
|
const searchKeyword = ref('')
|
|
|
|
|
const showStatusDropdown = ref(false)
|
|
|
|
|
const filterPopupRef = ref(null)
|
|
|
|
|
const selectedCreator = ref(null)
|
|
|
|
|
const inTimeFilter = ref([])
|
|
|
|
|
const creatorOptions = ref([])
|
|
|
|
|
const creatorPanelOpen = ref(false)
|
|
|
|
|
|
|
|
|
|
const statusOptions = computed(() => [
|
|
|
|
|
{ label: t('functionCommon.all'), value: '' },
|
|
|
|
|
{ label: t('sparepartOutbound.tabPending'), value: '0' },
|
|
|
|
|
{ label: t('sparepartOutbound.tabAuditing'), value: '10' },
|
|
|
|
|
{ label: t('sparepartOutbound.approve'), value: '20' },
|
|
|
|
|
{ label: '已驳回', value: '1' }
|
|
|
|
|
])
|
|
|
|
|
const currentStatusLabel = computed(() => { const cur = statusOptions.value.find(i => i.value === selectedStatus.value); return cur ? cur.label : t('functionCommon.all') })
|
|
|
|
|
const statusPickerLabels = computed(() => [t('sparepartOutbound.allStatus'), ...statusOptions.value.map(i => i.label)])
|
|
|
|
|
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 list = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const loadingMore = ref(false)
|
|
|
|
|
@ -149,7 +191,8 @@ async function fetchList(reset) {
|
|
|
|
|
if (reset) { pageNo.value = 1; finished.value = false }
|
|
|
|
|
if (pageNo.value === 1) loading.value = true; else loadingMore.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params = { pageNo: pageNo.value, pageSize: pageSize.value, no: searchKeyword.value.trim() || undefined, statusList: selectedStatus.value !== '' ? [Number(selectedStatus.value)] : undefined }
|
|
|
|
|
const inTimeRange = Array.isArray(inTimeFilter.value) ? inTimeFilter.value : []
|
|
|
|
|
const params = { pageNo: pageNo.value, pageSize: pageSize.value, no: searchKeyword.value.trim() || undefined, statusList: selectedStatus.value !== '' ? [Number(selectedStatus.value)] : undefined, creator: selectedCreator.value || undefined, outTime: inTimeRange.length === 2 ? [inTimeRange[0] + ' 00:00:00', inTimeRange[1] + ' 23:59:59'] : undefined }
|
|
|
|
|
const res = await getSparepartOutboundPage(params)
|
|
|
|
|
const page = normalizePageData(res)
|
|
|
|
|
list.value = reset ? page.list : [...list.value, ...page.list]
|
|
|
|
|
@ -158,11 +201,15 @@ async function fetchList(reset) {
|
|
|
|
|
finally { loading.value = false; loadingMore.value = false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleStatusDropdown() { showStatusDropdown.value = !showStatusDropdown.value }
|
|
|
|
|
function selectStatus(item, _idx) { selectedStatus.value = item.value; showStatusDropdown.value = false; fetchList(true) }
|
|
|
|
|
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 = ''; fetchList(true) }
|
|
|
|
|
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; selectedCreator.value = null; inTimeFilter.value = []; 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 }
|
|
|
|
|
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))}` }) }
|
|
|
|
|
|
|
|
|
|
@ -206,22 +253,56 @@ function clearSearchTimer() { if (searchTimer) { clearTimeout(searchTimer); sear
|
|
|
|
|
|
|
|
|
|
onShow(() => { fetchList(true) })
|
|
|
|
|
onUnload(() => { clearSearchTimer() })
|
|
|
|
|
onHide(() => { showStatusDropdown.value = false })
|
|
|
|
|
onHide(() => {})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.page-container { min-height: 100vh; background: #f4f5f7; }
|
|
|
|
|
.filter-bar { display: flex; align-items: center; gap: 12rpx; padding: 18rpx 24rpx; background: #fff; }
|
|
|
|
|
.keyword-box { flex: 1; min-width: 0; height: 66rpx; padding: 0 28rpx; background: #f4f5f7; border: 1rpx solid #e5e7eb; border-radius: 12rpx; display: flex; align-items: center; }
|
|
|
|
|
.keyword-input { width: 100%; font-size: 24rpx; color: #374151; }
|
|
|
|
|
.status-box-wrapper { position: relative; flex-shrink: 0; }
|
|
|
|
|
.status-box { 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; }
|
|
|
|
|
.status-dropdown-panel { position: absolute; top: 74rpx; left: 0; right: 0; z-index: 200; background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx; box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.12); overflow: hidden; }
|
|
|
|
|
.status-dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 18rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; &:last-child { border-bottom: 0; } &.active { background: #f0f5ff; } }
|
|
|
|
|
.status-dropdown-item-text { font-size: 26rpx; color: #374151; }
|
|
|
|
|
.status-dropdown-check { font-size: 26rpx; color: #2563eb; font-weight: 700; }
|
|
|
|
|
.status-box-text { font-size: 24rpx; color: #374151; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
|
.reset-filter-btn { height: 66rpx; line-height: 66rpx; padding: 0 28rpx; font-size: 24rpx; color: #4b5563; background: #fff; border: 1rpx solid #e5e7eb; border-radius: 12rpx; flex-shrink: 0; }
|
|
|
|
|
.filter-bar { padding: 18rpx 14rpx 20rpx; background: #f3f4f6; }
|
|
|
|
|
.filter-row { display: flex; align-items: center; gap: 18rpx; }
|
|
|
|
|
.search-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; }
|
|
|
|
|
.keyword-input { width: 100%; height: 64rpx; padding: 0 20rpx; font-size: 26rpx; color: #374151; }
|
|
|
|
|
.status-filter { min-width: 0; flex: 1; display: flex; align-items: center; justify-content: space-between; padding: 0 18rpx 0 26rpx; }
|
|
|
|
|
.status-filter-text { min-width: 0rpx; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 26rpx; color: #374151; }
|
|
|
|
|
.status-filter-text.placeholder { color: #a8adb7; }
|
|
|
|
|
.icon-filter-btn { width: 66rpx; flex: 0 0 66rpx; display: flex; align-items: center; justify-content: center; border-color: transparent; background: transparent; }
|
|
|
|
|
|
|
|
|
|
::deep(.outbound-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: 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; }
|
|
|
|
|
|
|
|
|
|
.list-scroll { height: calc(100vh - 194rpx); }
|
|
|
|
|
.list-wrap { padding: 0 24rpx 60rpx; }
|
|
|
|
|
|