358 lines
11 KiB
Vue
358 lines
11 KiB
Vue
<template>
|
|
<view class="db-page db-page--scroll">
|
|
<view class="db-header" :style="safeAreaStyle">
|
|
<view class="db-header__nav">
|
|
<view class="db-header__left">
|
|
<text class="db-header__back" @click="goBack">←</text>
|
|
<text class="db-header__title">计划管理</text>
|
|
</view>
|
|
<view /><!-- 胶囊占位 -->
|
|
</view>
|
|
</view>
|
|
|
|
<view class="db-search-wrap">
|
|
<db-search
|
|
v-model="keyword"
|
|
placeholder="搜索往来单位 / 计划号"
|
|
@change="onKeyword"
|
|
@search="onRefresh"
|
|
@clear="onClear"
|
|
/>
|
|
</view>
|
|
<view class="db-filter-bar">
|
|
<view
|
|
v-for="item in dateFilters"
|
|
:key="item.value"
|
|
class="db-filter-chip"
|
|
:class="{ 'db-filter-chip--active': dateFilter === item.value }"
|
|
@click="setFilter(item.value)"
|
|
>
|
|
{{ item.label }}
|
|
</view>
|
|
</view>
|
|
|
|
<scroll-view
|
|
class="db-list db-list-scroll"
|
|
scroll-y
|
|
:style="{ height: listHeight }"
|
|
refresher-enabled
|
|
:refresher-triggered="refreshing"
|
|
@refresherrefresh="onPullRefresh"
|
|
@scrolltolower="onLoadMore"
|
|
lower-threshold="80"
|
|
>
|
|
<view v-if="!loading && !list.length" class="db-empty">暂无数据</view>
|
|
<view v-for="item in list" :key="item.id" class="db-card" @click="viewItem(item)">
|
|
<view class="db-card__head">
|
|
<text class="db-card__title">{{ item.planNo || '未填计划号' }}</text>
|
|
<db-tag :type="item.businessType === '入库' ? 'success' : 'warning'">
|
|
{{ item.businessType }}
|
|
</db-tag>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">往来单位</text>
|
|
<text>{{ item.customerName || '未填' }}</text>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">产品</text>
|
|
<text>{{ item.product || '未填' }}</text>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">计划量</text>
|
|
<text>{{ item.plannedQuantity ?? '--' }} 吨</text>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">状态</text>
|
|
<db-tag :type="statusType(execStatus(item))">
|
|
{{ execStatus(item) }}
|
|
</db-tag>
|
|
</view>
|
|
<view class="db-card__foot" @click.stop>
|
|
<db-btn size="small" type="primary" plain @click="viewRelated(item)">查看</db-btn>
|
|
<db-btn size="small" type="primary" plain @click="editItem(item)">编辑</db-btn>
|
|
<db-btn size="small" type="primary" @click="genInventory(item)">生成出入库单</db-btn>
|
|
<db-btn size="small" type="danger" plain @click="removeItem(item)">删除</db-btn>
|
|
</view>
|
|
</view>
|
|
<view v-if="list.length" class="db-list-footer">
|
|
{{ finished ? '没有更多了' : loading ? '加载中...' : '上拉加载更多' }}
|
|
</view>
|
|
<view v-else-if="loading" class="db-empty">加载中...</view>
|
|
</scroll-view>
|
|
|
|
<db-popup :show="relatedOpen" height="75vh" @close="relatedOpen = false" @update:show="(v) => (relatedOpen = v)">
|
|
<view style="padding: 16px; max-height: 75vh; overflow: auto">
|
|
<view class="db-header__title" style="margin-bottom: 12px">关联数据</view>
|
|
<view v-if="relatedLoading" class="db-empty">加载中...</view>
|
|
<view v-else-if="!mergedRelated.length" class="db-empty">暂无关联数据</view>
|
|
<view v-for="(row, idx) in mergedRelated" :key="idx" class="db-card">
|
|
<db-tag :type="row.type === 'inventory' ? 'primary' : 'warning'">
|
|
{{ row.type === 'inventory' ? '出入库单' : '地磅单' }}
|
|
</db-tag>
|
|
<view class="db-card__row" style="margin-top: 8px">
|
|
<text class="db-card__label">业务</text>
|
|
<text>{{ row.businessType || '--' }}</text>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">产品</text>
|
|
<text>{{ row.product || row.cargoName || '--' }}</text>
|
|
</view>
|
|
<view class="db-card__row">
|
|
<text class="db-card__label">车牌</text>
|
|
<text>{{ row.vehicleNo || '--' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</db-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import { onShow } from '@dcloudio/uni-app'
|
|
import { PAGE_PATHS } from '@/config/api'
|
|
import { getSafeAreaStyle } from '@/utils/safeArea'
|
|
import { planApi } from '@/pages/db/api/db'
|
|
import { formatYmd, addDays, weekRange } from '@/pages/db/utils/dbDate'
|
|
import DbSearch from '@/components/db-ui/db-search.vue'
|
|
import DbTag from '@/components/db-ui/db-tag.vue'
|
|
import DbBtn from '@/components/db-ui/db-btn.vue'
|
|
import DbPopup from '@/components/db-ui/db-popup.vue'
|
|
|
|
const safeAreaStyle = getSafeAreaStyle()
|
|
const keyword = ref('')
|
|
const dateFilter = ref('all')
|
|
const loading = ref(false)
|
|
const finished = ref(false)
|
|
const refreshing = ref(false)
|
|
const list = ref([])
|
|
const currentPage = ref(1)
|
|
const pageSize = 20
|
|
const listHeight = ref('60vh')
|
|
const relatedOpen = ref(false)
|
|
const relatedLoading = ref(false)
|
|
const relatedData = ref({ inventories: [], weighings: [] })
|
|
let debounce = null
|
|
|
|
const dateFilters = [
|
|
{ label: '全部', value: 'all' },
|
|
{ label: '今日', value: 'today' },
|
|
{ label: '明日', value: 'tomorrow' },
|
|
{ label: '昨日', value: 'yesterday' },
|
|
{ label: '本周', value: 'week' }
|
|
]
|
|
|
|
const mergedRelated = computed(() => {
|
|
const items = []
|
|
;(relatedData.value.inventories || []).forEach((inv) => {
|
|
items.push({ ...inv, type: 'inventory', sortTime: inv.createdAt || inv.date || '' })
|
|
})
|
|
;(relatedData.value.weighings || []).forEach((w) => {
|
|
items.push({ ...w, type: 'weighing', sortTime: w.grossWeightTime || w.createdAt || '' })
|
|
})
|
|
return items.sort((a, b) => {
|
|
const ta = a.sortTime ? new Date(a.sortTime).getTime() : 0
|
|
const tb = b.sortTime ? new Date(b.sortTime).getTime() : 0
|
|
if (!ta && !tb) return 0
|
|
if (!ta) return 1
|
|
if (!tb) return -1
|
|
return tb - ta
|
|
})
|
|
})
|
|
|
|
function computeListHeight() {
|
|
try {
|
|
const sys = uni.getSystemInfoSync()
|
|
const h = (sys.windowHeight || 600) - 220
|
|
listHeight.value = `${Math.max(h, 320)}px`
|
|
} catch (e) {
|
|
listHeight.value = '60vh'
|
|
}
|
|
}
|
|
|
|
function execStatus(row) {
|
|
if (!row.executionStatus) return row.businessType === '入库' ? '待入库' : '待出库'
|
|
if (row.businessType === '入库') {
|
|
if (['待出库', '出库中', '已出库'].includes(row.executionStatus)) {
|
|
return (row.executedQuantity || 0) > 0 ? '入库中' : '待入库'
|
|
}
|
|
} else if (['待入库', '入库中', '已入库'].includes(row.executionStatus)) {
|
|
return (row.executedQuantity || 0) > 0 ? '出库中' : '待出库'
|
|
}
|
|
return row.executionStatus
|
|
}
|
|
|
|
function statusType(status) {
|
|
if (status === '已入库' || status === '已出库') return 'success'
|
|
if (status === '入库中' || status === '出库中') return 'primary'
|
|
if (status === '待入库' || status === '待出库') return 'warning'
|
|
return 'default'
|
|
}
|
|
|
|
function buildParams() {
|
|
const params = {
|
|
page: currentPage.value,
|
|
pageSize,
|
|
q: keyword.value || undefined
|
|
}
|
|
const today = new Date()
|
|
if (dateFilter.value === 'today') {
|
|
const d = formatYmd(today)
|
|
params.createdFrom = d
|
|
params.createdTo = d
|
|
} else if (dateFilter.value === 'tomorrow') {
|
|
const t = formatYmd(addDays(today, 1))
|
|
params.createdFrom = t
|
|
params.createdTo = t
|
|
} else if (dateFilter.value === 'yesterday') {
|
|
const y = formatYmd(addDays(today, -1))
|
|
params.createdFrom = y
|
|
params.createdTo = y
|
|
} else if (dateFilter.value === 'week') {
|
|
const w = weekRange(today)
|
|
params.createdFrom = w.from
|
|
params.createdTo = w.to
|
|
}
|
|
return params
|
|
}
|
|
|
|
async function fetchPage() {
|
|
if (loading.value && !refreshing.value) return
|
|
loading.value = true
|
|
try {
|
|
const data = await planApi.list(buildParams())
|
|
const items = Array.isArray(data) ? data : data?.items || []
|
|
const total = Array.isArray(data) ? data.length : Number(data?.total || 0)
|
|
if (currentPage.value === 1) {
|
|
list.value = items
|
|
} else {
|
|
list.value = list.value.concat(items)
|
|
}
|
|
currentPage.value += 1
|
|
finished.value = items.length < pageSize || list.value.length >= total
|
|
} catch (e) {
|
|
if (currentPage.value === 1) list.value = []
|
|
finished.value = true
|
|
} finally {
|
|
loading.value = false
|
|
refreshing.value = false
|
|
}
|
|
}
|
|
|
|
function onRefresh() {
|
|
currentPage.value = 1
|
|
finished.value = false
|
|
loading.value = false
|
|
list.value = []
|
|
fetchPage()
|
|
}
|
|
|
|
function onPullRefresh() {
|
|
refreshing.value = true
|
|
currentPage.value = 1
|
|
finished.value = false
|
|
fetchPage()
|
|
}
|
|
|
|
function onLoadMore() {
|
|
if (finished.value || loading.value) return
|
|
fetchPage()
|
|
}
|
|
|
|
function onKeyword(v) {
|
|
keyword.value = typeof v === 'string' ? v : keyword.value
|
|
if (debounce) clearTimeout(debounce)
|
|
debounce = setTimeout(onRefresh, 300)
|
|
}
|
|
|
|
function onClear() {
|
|
keyword.value = ''
|
|
onRefresh()
|
|
}
|
|
|
|
function setFilter(v) {
|
|
if (dateFilter.value === v) return
|
|
dateFilter.value = v
|
|
onRefresh()
|
|
}
|
|
|
|
function goBack() {
|
|
uni.navigateBack({ fail: () => uni.reLaunch({ url: PAGE_PATHS.workbench }) })
|
|
}
|
|
|
|
function editItem(item) {
|
|
uni.navigateTo({ url: `${PAGE_PATHS.dbPlansDetail}?id=${item.id}&mode=edit` })
|
|
}
|
|
|
|
function viewItem(item) {
|
|
uni.navigateTo({ url: `${PAGE_PATHS.dbPlansDetail}?id=${item.id}&mode=view` })
|
|
}
|
|
|
|
async function viewRelated(item) {
|
|
relatedOpen.value = true
|
|
relatedLoading.value = true
|
|
try {
|
|
relatedData.value = (await planApi.getRelated(item.id)) || { inventories: [], weighings: [] }
|
|
} catch (e) {
|
|
relatedOpen.value = false
|
|
} finally {
|
|
relatedLoading.value = false
|
|
}
|
|
}
|
|
|
|
function genInventory(item) {
|
|
const q = [
|
|
`planNo=${encodeURIComponent(item.planNo || '')}`,
|
|
`product=${encodeURIComponent(item.product || '')}`,
|
|
`productId=${encodeURIComponent(item.productId || '')}`,
|
|
`businessType=${encodeURIComponent(item.businessType || '')}`,
|
|
`customerName=${encodeURIComponent(item.customerName || '')}`,
|
|
`purchasePlanId=${encodeURIComponent(item.id)}`,
|
|
'from=plan',
|
|
'mode=create'
|
|
].join('&')
|
|
uni.navigateTo({ url: `${PAGE_PATHS.dbInventoryDetail}?${q}` })
|
|
}
|
|
|
|
function removeItem(item) {
|
|
uni.showModal({
|
|
title: '确认删除',
|
|
content: '确定要删除这个计划吗?',
|
|
success: async (res) => {
|
|
if (!res.confirm) return
|
|
try {
|
|
await planApi.delete(item.id)
|
|
uni.showToast({ title: '删除成功', icon: 'success' })
|
|
onRefresh()
|
|
} catch (e) {
|
|
// toast by request
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
onShow(() => {
|
|
computeListHeight()
|
|
onRefresh()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import '@/styles/db-theme.scss';
|
|
|
|
.db-page--scroll {
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.db-list-scroll {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.db-list-footer {
|
|
padding: 12px 0 24px;
|
|
text-align: center;
|
|
font-size: 12px;
|
|
color: #9ca3af;
|
|
}
|
|
</style>
|