|
|
|
|
@ -0,0 +1,627 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="page-container">
|
|
|
|
|
<AppTitleHeader :title="t('moldReturn.moduleName')" :subTitle="t('moldReturn.subTitle')" :showSubTitle="true" />
|
|
|
|
|
|
|
|
|
|
<!-- 列表查询区 -->
|
|
|
|
|
<view class="search-card">
|
|
|
|
|
<view class="search-row">
|
|
|
|
|
<view class="search-input-wrap">
|
|
|
|
|
<text class="iconfont icon-search search-icon"></text>
|
|
|
|
|
<input v-model="query.no" class="search-input" :placeholder="t('moldReturn.searchNo')" @confirm="handleSearch" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="search-btn" @click="handleSearch">{{ t('functionCommon.search') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="filter-row">
|
|
|
|
|
<picker mode="selector" :range="warehouseOptions" range-key="name" :value="warehouseIndex" @change="onWarehouseFilterChange">
|
|
|
|
|
<view class="filter-item">{{ warehouseFilterLabel }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
<picker mode="selector" :range="statusOptions" range-key="label" :value="statusIndex" @change="onStatusFilterChange">
|
|
|
|
|
<view class="filter-item">{{ statusFilterLabel }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 列表区 -->
|
|
|
|
|
<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="card" @click="openDetail(item)">
|
|
|
|
|
<view class="card-head">
|
|
|
|
|
<view>
|
|
|
|
|
<view class="card-title">{{ textValue(item.no) }}</view>
|
|
|
|
|
<view class="card-sub">{{ t('moldReturn.moldName') }}: {{ textValue(item.productNames) }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="status-chip" :class="statusClass(item.status)">{{ statusLabel(item.status) }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="card-body">
|
|
|
|
|
<view class="row">
|
|
|
|
|
<text class="label">{{ t('moldReturn.inTime') }}</text>
|
|
|
|
|
<text class="value">{{ dateTimeLabel(item.inTime || item.outTime) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="row">
|
|
|
|
|
<text class="label">{{ t('moldReturn.warehouse') }}</text>
|
|
|
|
|
<text class="value">{{ textValue(item.warehouseName) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="row">
|
|
|
|
|
<text class="label">{{ t('moldReturn.creator') }}</text>
|
|
|
|
|
<text class="value">{{ textValue(item.creatorName) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="card-actions">
|
|
|
|
|
<view v-if="String(item.status) === '10'" class="action-btn edit-btn" @click.stop="openEdit(item)">
|
|
|
|
|
<uni-icons type="compose" size="18" color="#096dd9"></uni-icons>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="String(item.status) === '10'" class="action-btn approve-btn" @click.stop="approve(item)">
|
|
|
|
|
<uni-icons type="checkmarkempty" size="18" color="#389e0d"></uni-icons>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="String(item.status) === '10'" class="action-btn delete-btn" @click.stop="removeItem(item)">
|
|
|
|
|
<uni-icons type="trash" size="18" color="#cf1322"></uni-icons>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="loading && pageNo === 1" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
|
|
<view v-else-if="!list.length" class="hint">{{ t('moldReturn.empty') }}</view>
|
|
|
|
|
<view v-else-if="loadingMore" class="hint">{{ t('functionCommon.loadingMore') }}</view>
|
|
|
|
|
<view v-else-if="finished" class="hint">{{ t('functionCommon.noMoreData') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
<!-- 新增按钮 -->
|
|
|
|
|
<view class="fab-btn" @click="openCreate">
|
|
|
|
|
<uni-icons type="plusempty" size="30" color="#fff"></uni-icons>
|
|
|
|
|
</view>
|
|
|
|
|
<!-- 返回顶部按钮 -->
|
|
|
|
|
<view v-if="showGoTop" class="go-top-btn" @click="goTop">
|
|
|
|
|
<uni-icons type="arrow-up" size="20" color="#1a3a5c"></uni-icons>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑弹框 -->
|
|
|
|
|
<uni-popup ref="formPopupRef" type="bottom">
|
|
|
|
|
<view class="form-popup">
|
|
|
|
|
<view class="popup-header">
|
|
|
|
|
<text class="popup-title">{{ formMode === 'create' ? t('moldReturn.createTitle') : t('moldReturn.editTitle') }}</text>
|
|
|
|
|
<text class="popup-close" @click="closeForm">×</text>
|
|
|
|
|
</view>
|
|
|
|
|
<scroll-view scroll-y class="popup-scroll">
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label"><text class="required">*</text>{{ t('moldReturn.inType') }}</text>
|
|
|
|
|
<input class="form-input disabled" :value="t('moldReturn.moduleName')" disabled />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label">{{ t('moldReturn.inNo') }}</text>
|
|
|
|
|
<input class="form-input disabled" :value="textValue(formData.no)" :placeholder="t('moldReturn.noAuto')" disabled />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label"><text class="required">*</text>{{ t('moldReturn.inTimeSingle') }}</text>
|
|
|
|
|
<picker mode="date" :value="formInDate" @change="onFormDateChange">
|
|
|
|
|
<view class="picker-view">{{ formInDate || t('moldReturn.inTimePlaceholder') }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label"><text class="required">*</text>{{ t('moldReturn.warehouse') }}</text>
|
|
|
|
|
<picker mode="selector" :range="warehouseOptions" range-key="name" :value="formWarehouseIndex" @change="onFormWarehouseChange">
|
|
|
|
|
<view class="picker-view">{{ formWarehouseLabel || t('moldReturn.warehousePlaceholder') }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label">{{ t('moldReturn.attachment') }}</text>
|
|
|
|
|
<input v-model="formData.fileUrl" class="form-input" type="text" :placeholder="t('moldReturn.fileUrlPlaceholder')" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
<text class="form-label">{{ t('moldReturn.remark') }}</text>
|
|
|
|
|
<textarea v-model="formData.remark" class="form-textarea" :placeholder="t('moldReturn.remarkPlaceholder')" maxlength="200" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="sub-title-row">
|
|
|
|
|
<text class="sub-title"><text class="required">*</text>{{ t('moldReturn.itemListTitle') }}</text>
|
|
|
|
|
<view class="sub-action" @click="openMoldPicker">{{ t('moldReturn.selectMold') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="!formData.items.length" class="item-empty">{{ t('moldReturn.noItems') }}</view>
|
|
|
|
|
<view v-for="(item, index) in formData.items" :key="item.productId" class="item-card">
|
|
|
|
|
<view class="item-head">
|
|
|
|
|
<text class="item-name">{{ textValue(item.productName) }}</text>
|
|
|
|
|
<text class="item-remove" @click="removeFormItem(index)">×</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="item-row">
|
|
|
|
|
<text class="item-label">{{ t('moldReturn.moldCode') }}</text>
|
|
|
|
|
<input class="item-input disabled" :value="textValue(item.productCode)" disabled />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="item-row">
|
|
|
|
|
<text class="item-label"><text class="required">*</text>{{ t('moldReturn.count') }}</text>
|
|
|
|
|
<input v-model="item.count" class="item-input" type="number" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="item-row">
|
|
|
|
|
<text class="item-label">{{ t('moldReturn.itemRemark') }}</text>
|
|
|
|
|
<input v-model="item.remark" class="item-input" type="text" />
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
<view class="popup-footer">
|
|
|
|
|
<view class="footer-btn cancel" @click="closeForm">{{ t('functionCommon.cancel') }}</view>
|
|
|
|
|
<view class="footer-btn confirm" @click="submitForm">{{ t('functionCommon.save') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</uni-popup>
|
|
|
|
|
|
|
|
|
|
<!-- 明细选择弹框 -->
|
|
|
|
|
<uni-popup ref="moldPickerRef" type="bottom">
|
|
|
|
|
<view class="picker-popup">
|
|
|
|
|
<view class="picker-header">
|
|
|
|
|
<text class="picker-title">{{ t('moldReturn.selectMold') }}</text>
|
|
|
|
|
<text class="picker-close" @click="closeMoldPicker">×</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="picker-search">
|
|
|
|
|
<view class="picker-search-row">
|
|
|
|
|
<input v-model="moldQuery.code" class="picker-search-input" :placeholder="t('moldReturn.searchCode')" @confirm="handleMoldSearch" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="picker-search-row">
|
|
|
|
|
<input v-model="moldQuery.name" class="picker-search-input" :placeholder="t('moldReturn.searchName')" @confirm="handleMoldSearch" />
|
|
|
|
|
</view>
|
|
|
|
|
<view class="picker-search-actions">
|
|
|
|
|
<view class="picker-action-btn" @click="handleMoldSearch">{{ t('functionCommon.search') }}</view>
|
|
|
|
|
<view class="picker-action-btn reset" @click="resetMoldSearch">{{ t('functionCommon.cancel') }}</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<scroll-view scroll-y class="picker-scroll">
|
|
|
|
|
<view v-for="m in moldPickerList" :key="m.id" class="picker-item" @click="toggleMold(m)">
|
|
|
|
|
<view>
|
|
|
|
|
<view class="picker-item-name">{{ textValue(m.name) }}</view>
|
|
|
|
|
<view class="picker-item-code">{{ textValue(m.code) }}</view>
|
|
|
|
|
<view class="picker-item-meta">
|
|
|
|
|
{{ t('moldReturn.moldStatus') }}: {{ moldStatusLabel(m.status) }} · {{ t('moldReturn.moldUseTime') }}: {{ textValue(m.useTime) }}
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="picker-check">{{ formItemIds.has(Number(m.id)) ? '✓' : '' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="moldLoading" class="picker-hint">{{ t('functionCommon.loading') }}</view>
|
|
|
|
|
<view v-else-if="!moldPickerList.length" class="picker-hint">{{ t('moldReturn.noMoldData') }}</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
</view>
|
|
|
|
|
</uni-popup>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { computed, reactive, ref } from 'vue'
|
|
|
|
|
import { onShow } from '@dcloudio/uni-app'
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
|
|
|
|
|
import { getMoldList } from '@/api/mes/mold'
|
|
|
|
|
import { getMoldReturnPage, getMoldReturnDetail, createMoldReturn, updateMoldReturn, updateMoldReturnStatus, deleteMoldReturn } from '@/api/mes/moldreturn'
|
|
|
|
|
import { getWarehouseSimpleList } from '@/api/mes/moldget'
|
|
|
|
|
import { useDict } from '@/utils/dict'
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
const { erp_audit_status, erp_mold_status } = useDict('erp_audit_status', 'erp_mold_status')
|
|
|
|
|
|
|
|
|
|
const list = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const loadingMore = ref(false)
|
|
|
|
|
const finished = ref(false)
|
|
|
|
|
const pageNo = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const scrollTop = ref(0)
|
|
|
|
|
const showGoTop = ref(false)
|
|
|
|
|
|
|
|
|
|
const query = reactive({
|
|
|
|
|
no: '',
|
|
|
|
|
warehouseId: undefined,
|
|
|
|
|
status: undefined
|
|
|
|
|
})
|
|
|
|
|
const warehouseOptions = ref([])
|
|
|
|
|
const warehouseIndex = ref(0)
|
|
|
|
|
const statusIndex = ref(0)
|
|
|
|
|
const statusOptions = computed(() => [{ label: t('moldReturn.allStatus'), value: undefined }, ...(erp_audit_status.value || [])])
|
|
|
|
|
const statusFilterLabel = computed(() => statusOptions.value[statusIndex.value]?.label || t('moldReturn.allStatus'))
|
|
|
|
|
const warehouseFilterLabel = computed(() => warehouseOptions.value[warehouseIndex.value]?.name || t('moldReturn.allWarehouse'))
|
|
|
|
|
|
|
|
|
|
const formPopupRef = ref(null)
|
|
|
|
|
const moldPickerRef = ref(null)
|
|
|
|
|
const formMode = ref('create')
|
|
|
|
|
const formData = reactive({
|
|
|
|
|
id: undefined,
|
|
|
|
|
no: '',
|
|
|
|
|
inType: '模具入库',
|
|
|
|
|
inTime: '',
|
|
|
|
|
warehouseId: undefined,
|
|
|
|
|
fileUrl: '',
|
|
|
|
|
remark: '',
|
|
|
|
|
items: []
|
|
|
|
|
})
|
|
|
|
|
const formItemIds = computed(() => new Set((formData.items || []).map((i) => Number(i.productId))))
|
|
|
|
|
const formWarehouseLabel = computed(() => warehouseOptions.value.find((w) => Number(w.id) === Number(formData.warehouseId))?.name || '')
|
|
|
|
|
const formWarehouseIndex = computed(() => {
|
|
|
|
|
const idx = warehouseOptions.value.findIndex((w) => Number(w.id) === Number(formData.warehouseId))
|
|
|
|
|
return idx < 0 ? 0 : idx
|
|
|
|
|
})
|
|
|
|
|
const formInDate = computed(() => formatDateOnly(formData.inTime))
|
|
|
|
|
|
|
|
|
|
const moldPickerList = ref([])
|
|
|
|
|
const moldLoading = ref(false)
|
|
|
|
|
const moldQuery = reactive({
|
|
|
|
|
code: '',
|
|
|
|
|
name: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function normalizePageData(res) {
|
|
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
|
|
const l = root?.list || root?.rows || root?.records || root?.data?.list || []
|
|
|
|
|
const total = root?.total ?? root?.data?.total ?? (Array.isArray(l) ? l.length : 0)
|
|
|
|
|
return { list: Array.isArray(l) ? l : [], total: Number(total || 0) }
|
|
|
|
|
}
|
|
|
|
|
function textValue(v) {
|
|
|
|
|
if (v === 0) return '0'
|
|
|
|
|
if (v === null || v === undefined) return '-'
|
|
|
|
|
const s = String(v).trim()
|
|
|
|
|
return s || '-'
|
|
|
|
|
}
|
|
|
|
|
function dateTimeLabel(v) {
|
|
|
|
|
if (!v) return '-'
|
|
|
|
|
const d = new Date(Number(v))
|
|
|
|
|
if (Number.isNaN(d.getTime())) return textValue(v)
|
|
|
|
|
const p = (n) => String(n).padStart(2, '0')
|
|
|
|
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
|
|
|
|
}
|
|
|
|
|
function formatDateOnly(v) {
|
|
|
|
|
if (!v) return ''
|
|
|
|
|
const d = new Date(Number(v))
|
|
|
|
|
if (Number.isNaN(d.getTime())) return ''
|
|
|
|
|
const p = (n) => String(n).padStart(2, '0')
|
|
|
|
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
|
|
|
|
}
|
|
|
|
|
function statusLabel(status) {
|
|
|
|
|
const hit = (erp_audit_status.value || []).find((s) => String(s.value) === String(status))
|
|
|
|
|
return hit ? hit.label : textValue(status)
|
|
|
|
|
}
|
|
|
|
|
function statusClass(status) {
|
|
|
|
|
if (String(status) === '10') return 'status-draft'
|
|
|
|
|
if (String(status) === '20') return 'status-approved'
|
|
|
|
|
return 'status-other'
|
|
|
|
|
}
|
|
|
|
|
function moldStatusLabel(status) {
|
|
|
|
|
const hit = (erp_mold_status.value || []).find((s) => String(s.value) === String(status))
|
|
|
|
|
return hit ? hit.label : textValue(status)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchList(reset = true) {
|
|
|
|
|
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: query.no.trim() || undefined,
|
|
|
|
|
inType: '模具入库',
|
|
|
|
|
warehouseId: query.warehouseId,
|
|
|
|
|
status: query.status
|
|
|
|
|
}
|
|
|
|
|
const res = await getMoldReturnPage(params)
|
|
|
|
|
const page = normalizePageData(res)
|
|
|
|
|
if (reset) list.value = page.list
|
|
|
|
|
else list.value = [...list.value, ...page.list]
|
|
|
|
|
finished.value = list.value.length >= page.total || page.list.length < pageSize.value
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (!reset) pageNo.value = Math.max(1, pageNo.value - 1)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
loadingMore.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function loadOptions() {
|
|
|
|
|
const warehouseRes = await getWarehouseSimpleList()
|
|
|
|
|
const warehouseData = warehouseRes?.data || []
|
|
|
|
|
warehouseOptions.value = [{ id: undefined, name: t('moldReturn.allWarehouse') }, ...warehouseData]
|
|
|
|
|
}
|
|
|
|
|
async function loadMolds() {
|
|
|
|
|
moldLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await getMoldList({
|
|
|
|
|
code: moldQuery.code.trim() || undefined,
|
|
|
|
|
name: moldQuery.name.trim() || undefined,
|
|
|
|
|
statuss: [3, 0]
|
|
|
|
|
})
|
|
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
|
|
const data = Array.isArray(root) ? root : Array.isArray(root?.list) ? root.list : []
|
|
|
|
|
moldPickerList.value = data
|
|
|
|
|
} finally {
|
|
|
|
|
moldLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSearch() { fetchList(true) }
|
|
|
|
|
function onWarehouseFilterChange(e) {
|
|
|
|
|
const idx = Number(e?.detail?.value || 0)
|
|
|
|
|
warehouseIndex.value = idx
|
|
|
|
|
const option = warehouseOptions.value[idx]
|
|
|
|
|
query.warehouseId = option ? option.id : undefined
|
|
|
|
|
fetchList(true)
|
|
|
|
|
}
|
|
|
|
|
function onStatusFilterChange(e) {
|
|
|
|
|
const idx = Number(e?.detail?.value || 0)
|
|
|
|
|
statusIndex.value = idx
|
|
|
|
|
const option = statusOptions.value[idx]
|
|
|
|
|
query.status = option ? option.value : undefined
|
|
|
|
|
fetchList(true)
|
|
|
|
|
}
|
|
|
|
|
function onScroll(e) {
|
|
|
|
|
const top = Number(e?.detail?.scrollTop || 0)
|
|
|
|
|
showGoTop.value = top > 600
|
|
|
|
|
}
|
|
|
|
|
function goTop() { scrollTop.value = 0 }
|
|
|
|
|
async function loadMore() {
|
|
|
|
|
if (loading.value || loadingMore.value || finished.value) return
|
|
|
|
|
pageNo.value += 1
|
|
|
|
|
await fetchList(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetFormData() {
|
|
|
|
|
formData.id = undefined
|
|
|
|
|
formData.no = ''
|
|
|
|
|
formData.inType = '模具入库'
|
|
|
|
|
formData.inTime = new Date().setHours(0, 0, 0, 0)
|
|
|
|
|
formData.warehouseId = warehouseOptions.value.find((w) => w.defaultStatus)?.id ?? warehouseOptions.value[1]?.id
|
|
|
|
|
formData.fileUrl = ''
|
|
|
|
|
formData.remark = ''
|
|
|
|
|
formData.items = []
|
|
|
|
|
}
|
|
|
|
|
function openCreate() {
|
|
|
|
|
formMode.value = 'create'
|
|
|
|
|
resetFormData()
|
|
|
|
|
formPopupRef.value?.open()
|
|
|
|
|
}
|
|
|
|
|
async function openEdit(row) {
|
|
|
|
|
if (!row?.id) {
|
|
|
|
|
uni.showToast({ title: t('functionCommon.noIdEdit'), icon: 'none' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
formMode.value = 'update'
|
|
|
|
|
resetFormData()
|
|
|
|
|
try {
|
|
|
|
|
const res = await getMoldReturnDetail(row.id)
|
|
|
|
|
const detail = res?.data || {}
|
|
|
|
|
formData.id = detail.id
|
|
|
|
|
formData.no = detail.no
|
|
|
|
|
formData.inType = detail.inType || '模具入库'
|
|
|
|
|
formData.inTime = detail.inTime || detail.outTime
|
|
|
|
|
formData.fileUrl = detail.fileUrl || ''
|
|
|
|
|
formData.remark = detail.remark || ''
|
|
|
|
|
formData.warehouseId = detail.items?.[0]?.warehouseId ?? detail.warehouseId ?? formData.warehouseId
|
|
|
|
|
formData.items = (detail.items || []).map((i) => ({
|
|
|
|
|
id: i.id,
|
|
|
|
|
productId: i.productId ?? i.id,
|
|
|
|
|
productName: i.productName,
|
|
|
|
|
productCode: i.productCode ?? i.productBarCode,
|
|
|
|
|
count: i.count ?? 1,
|
|
|
|
|
productPrice: i.productPrice ?? 0,
|
|
|
|
|
remark: i.remark || ''
|
|
|
|
|
}))
|
|
|
|
|
formPopupRef.value?.open()
|
|
|
|
|
} catch (e) {
|
|
|
|
|
uni.showToast({ title: t('moldReturn.loadEditFailed'), icon: 'none' })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function closeForm() { formPopupRef.value?.close() }
|
|
|
|
|
function onFormDateChange(e) {
|
|
|
|
|
const date = e?.detail?.value
|
|
|
|
|
if (!date) return
|
|
|
|
|
formData.inTime = new Date(`${date} 00:00:00`).getTime()
|
|
|
|
|
}
|
|
|
|
|
function onFormWarehouseChange(e) {
|
|
|
|
|
const idx = Number(e?.detail?.value || 0)
|
|
|
|
|
const option = warehouseOptions.value[idx]
|
|
|
|
|
formData.warehouseId = option ? option.id : undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openMoldPicker() {
|
|
|
|
|
moldQuery.code = ''
|
|
|
|
|
moldQuery.name = ''
|
|
|
|
|
loadMolds()
|
|
|
|
|
moldPickerRef.value?.open()
|
|
|
|
|
}
|
|
|
|
|
function closeMoldPicker() { moldPickerRef.value?.close() }
|
|
|
|
|
function handleMoldSearch() { loadMolds() }
|
|
|
|
|
function resetMoldSearch() {
|
|
|
|
|
moldQuery.code = ''
|
|
|
|
|
moldQuery.name = ''
|
|
|
|
|
loadMolds()
|
|
|
|
|
}
|
|
|
|
|
function toggleMold(mold) {
|
|
|
|
|
const id = Number(mold.id)
|
|
|
|
|
const idx = formData.items.findIndex((i) => Number(i.productId) === id)
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
formData.items.splice(idx, 1)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
formData.items.push({
|
|
|
|
|
productId: id,
|
|
|
|
|
productName: mold.name,
|
|
|
|
|
productCode: mold.code,
|
|
|
|
|
count: 1,
|
|
|
|
|
productPrice: 0,
|
|
|
|
|
remark: '',
|
|
|
|
|
warehouseId: formData.warehouseId
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
function removeFormItem(index) { formData.items.splice(index, 1) }
|
|
|
|
|
|
|
|
|
|
function validForm() {
|
|
|
|
|
if (!formData.inTime) {
|
|
|
|
|
uni.showToast({ title: t('moldReturn.validatorInTimeRequired'), icon: 'none' })
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (!formData.warehouseId) {
|
|
|
|
|
uni.showToast({ title: t('moldReturn.validatorWarehouseRequired'), icon: 'none' })
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if (!formData.items.length) {
|
|
|
|
|
uni.showToast({ title: t('moldReturn.validatorItemRequired'), icon: 'none' })
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
const invalidItem = formData.items.some((i) => !i.count || Number(i.count) <= 0)
|
|
|
|
|
if (invalidItem) {
|
|
|
|
|
uni.showToast({ title: t('moldReturn.validatorCountRequired'), icon: 'none' })
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
async function submitForm() {
|
|
|
|
|
if (!validForm()) return
|
|
|
|
|
const payload = {
|
|
|
|
|
id: formData.id,
|
|
|
|
|
no: formData.no || undefined,
|
|
|
|
|
inType: '模具入库',
|
|
|
|
|
inTime: formData.inTime,
|
|
|
|
|
warehouseId: formData.warehouseId,
|
|
|
|
|
fileUrl: formData.fileUrl || undefined,
|
|
|
|
|
remark: formData.remark || undefined,
|
|
|
|
|
items: formData.items.map((i) => ({
|
|
|
|
|
id: i.id,
|
|
|
|
|
warehouseId: formData.warehouseId,
|
|
|
|
|
productId: i.productId,
|
|
|
|
|
productPrice: Number(i.productPrice || 0),
|
|
|
|
|
count: Number(i.count || 1),
|
|
|
|
|
remark: i.remark || undefined
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (formMode.value === 'create') {
|
|
|
|
|
await createMoldReturn(payload)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.createSuccess'), icon: 'success' })
|
|
|
|
|
} else {
|
|
|
|
|
await updateMoldReturn(payload)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.updateSuccess'), icon: 'success' })
|
|
|
|
|
}
|
|
|
|
|
closeForm()
|
|
|
|
|
fetchList(true)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
uni.showToast({ title: t('functionCommon.saveFailed'), icon: 'none' })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openDetail(item) {
|
|
|
|
|
if (!item?.id) {
|
|
|
|
|
uni.showToast({ title: t('functionCommon.noIdView'), icon: 'none' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
url: `/pages_function/pages/moldreturn/detail?id=${encodeURIComponent(String(item.id))}`
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
function removeItem(item) {
|
|
|
|
|
if (!item?.id) {
|
|
|
|
|
uni.showToast({ title: t('functionCommon.noIdDelete'), icon: 'none' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: t('functionCommon.confirmDelete'),
|
|
|
|
|
content: t('moldReturn.confirmDelete', { no: textValue(item.no) }),
|
|
|
|
|
success: async ({ confirm }) => {
|
|
|
|
|
if (!confirm) return
|
|
|
|
|
try {
|
|
|
|
|
await deleteMoldReturn([item.id])
|
|
|
|
|
uni.showToast({ title: t('functionCommon.deleteSuccess'), icon: 'success' })
|
|
|
|
|
fetchList(true)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
uni.showToast({ title: t('functionCommon.deleteFailed'), icon: 'none' })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
function approve(item) {
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: t('moldReturn.approve'),
|
|
|
|
|
content: t('moldReturn.confirmApprove', { no: textValue(item.no) }),
|
|
|
|
|
success: async ({ confirm }) => {
|
|
|
|
|
if (!confirm) return
|
|
|
|
|
await updateMoldReturnStatus(item.id, 20)
|
|
|
|
|
uni.showToast({ title: t('moldReturn.approveSuccess'), icon: 'success' })
|
|
|
|
|
fetchList(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onShow(async () => {
|
|
|
|
|
await loadOptions()
|
|
|
|
|
await fetchList(true)
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.page-container { min-height: 100vh; background: #f0f2f5; }
|
|
|
|
|
.search-card { margin: 18rpx 24rpx; padding: 20rpx; border-radius: 16rpx; background: #fff; }
|
|
|
|
|
.search-row { display: flex; gap: 16rpx; }
|
|
|
|
|
.search-input-wrap { flex: 1; display: flex; align-items: center; background: #f5f7fa; border-radius: 40rpx; padding: 0 20rpx; }
|
|
|
|
|
.search-icon { color: #909399; }
|
|
|
|
|
.search-input { flex: 1; height: 72rpx; margin-left: 12rpx; }
|
|
|
|
|
.search-btn { min-width: 120rpx; height: 72rpx; border-radius: 36rpx; background: #1a3a5c; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 26rpx; }
|
|
|
|
|
.filter-row { margin-top: 16rpx; display: flex; gap: 14rpx; }
|
|
|
|
|
.filter-item { min-width: 220rpx; padding: 12rpx 18rpx; border-radius: 10rpx; background: #f5f7fa; color: #303133; font-size: 24rpx; }
|
|
|
|
|
.list-scroll { height: calc(100vh - 360rpx); }
|
|
|
|
|
.list-wrap { padding: 0 24rpx 130rpx; }
|
|
|
|
|
.card { background: #fff; border-radius: 16rpx; padding: 20rpx; margin-bottom: 16rpx; }
|
|
|
|
|
.card-head { display: flex; justify-content: space-between; gap: 10rpx; }
|
|
|
|
|
.card-title { font-size: 30rpx; font-weight: 700; color: #1a3a5c; }
|
|
|
|
|
.card-sub { margin-top: 8rpx; color: #606266; font-size: 24rpx; }
|
|
|
|
|
.status-chip { padding: 6rpx 16rpx; border-radius: 20rpx; font-size: 22rpx; }
|
|
|
|
|
.status-draft { background: rgba(250, 173, 20, 0.12); color: #ad6800; }
|
|
|
|
|
.status-approved { background: rgba(82, 196, 26, 0.12); color: #389e0d; }
|
|
|
|
|
.status-other { background: rgba(24, 144, 255, 0.12); color: #096dd9; }
|
|
|
|
|
.card-body { margin-top: 14rpx; }
|
|
|
|
|
.row { display: flex; justify-content: space-between; margin-bottom: 10rpx; }
|
|
|
|
|
.label { color: #909399; font-size: 24rpx; }
|
|
|
|
|
.value { color: #303133; font-size: 24rpx; max-width: 66%; text-align: right; }
|
|
|
|
|
.card-actions { margin-top: 8rpx; display: flex; justify-content: flex-end; gap: 10rpx; }
|
|
|
|
|
.action-btn { width: 56rpx; height: 56rpx; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; }
|
|
|
|
|
.edit-btn { background: rgba(24, 144, 255, 0.12); }
|
|
|
|
|
.approve-btn { background: rgba(82, 196, 26, 0.14); }
|
|
|
|
|
.delete-btn { background: rgba(245, 34, 45, 0.12); }
|
|
|
|
|
.hint { text-align: center; color: #909399; padding: 24rpx 0; }
|
|
|
|
|
.fab-btn { position: fixed; right: 34rpx; bottom: 120rpx; width: 96rpx; height: 96rpx; border-radius: 50%; background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%); display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 30rpx rgba(26, 58, 92, 0.3); z-index: 20; }
|
|
|
|
|
.go-top-btn { position: fixed; right: 34rpx; bottom: 240rpx; width: 80rpx; height: 80rpx; border-radius: 50%; background: #fff; box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12); display: flex; align-items: center; justify-content: center; }
|
|
|
|
|
.form-popup { background: #fff; border-radius: 22rpx 22rpx 0 0; max-height: 88vh; }
|
|
|
|
|
.popup-header { height: 96rpx; display: flex; align-items: center; justify-content: center; position: relative; border-bottom: 1rpx solid #f0f0f0; }
|
|
|
|
|
.popup-title { font-size: 30rpx; font-weight: 700; }
|
|
|
|
|
.popup-close { position: absolute; right: 28rpx; top: 20rpx; font-size: 44rpx; color: #909399; }
|
|
|
|
|
.popup-scroll { max-height: calc(88vh - 180rpx); padding: 20rpx 26rpx; }
|
|
|
|
|
.form-item { margin-bottom: 18rpx; }
|
|
|
|
|
.form-label { font-size: 24rpx; color: #606266; margin-bottom: 8rpx; display: block; }
|
|
|
|
|
.required { color: #f56c6c; margin-right: 6rpx; }
|
|
|
|
|
.form-input,.picker-view { min-height: 72rpx; border-radius: 12rpx; background: #f5f7fa; padding: 0 18rpx; display: flex; align-items: center; }
|
|
|
|
|
.disabled { color: #909399; }
|
|
|
|
|
.form-textarea { width: 100%; min-height: 120rpx; border-radius: 12rpx; background: #f5f7fa; padding: 14rpx 18rpx; }
|
|
|
|
|
.sub-title-row { margin-top: 20rpx; display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
|
.sub-title { font-size: 26rpx; font-weight: 600; }
|
|
|
|
|
.sub-action { color: #1a3a5c; font-size: 24rpx; }
|
|
|
|
|
.item-empty { padding: 20rpx; color: #909399; text-align: center; }
|
|
|
|
|
.item-card { margin-top: 14rpx; border-radius: 12rpx; background: #f8fafc; padding: 16rpx; }
|
|
|
|
|
.item-head { display: flex; justify-content: space-between; }
|
|
|
|
|
.item-name { color: #1a3a5c; font-size: 26rpx; font-weight: 600; }
|
|
|
|
|
.item-remove { color: #f56c6c; font-size: 34rpx; }
|
|
|
|
|
.item-row { margin-top: 10rpx; }
|
|
|
|
|
.item-label { color: #909399; font-size: 22rpx; }
|
|
|
|
|
.item-input { margin-top: 6rpx; min-height: 64rpx; border-radius: 10rpx; background: #fff; padding: 0 14rpx; }
|
|
|
|
|
.popup-footer { height: 88rpx; display: flex; }
|
|
|
|
|
.footer-btn { flex: 1; display: flex; align-items: center; justify-content: center; font-size: 28rpx; }
|
|
|
|
|
.cancel { background: #f5f7fa; color: #606266; }
|
|
|
|
|
.confirm { background: #1a3a5c; color: #fff; }
|
|
|
|
|
.picker-popup { background: #fff; border-radius: 22rpx 22rpx 0 0; height: 72vh; }
|
|
|
|
|
.picker-header { height: 90rpx; display: flex; justify-content: center; align-items: center; position: relative; }
|
|
|
|
|
.picker-title { font-size: 28rpx; font-weight: 700; }
|
|
|
|
|
.picker-close { position: absolute; right: 24rpx; top: 18rpx; font-size: 42rpx; }
|
|
|
|
|
.picker-search { padding: 0 24rpx 12rpx; }
|
|
|
|
|
.picker-search-row { margin-bottom: 10rpx; }
|
|
|
|
|
.picker-search-input { height: 68rpx; border-radius: 10rpx; background: #f5f7fa; padding: 0 14rpx; }
|
|
|
|
|
.picker-search-actions { margin-top: 6rpx; display: flex; gap: 12rpx; }
|
|
|
|
|
.picker-action-btn { min-width: 110rpx; height: 56rpx; border-radius: 10rpx; background: #1a3a5c; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 22rpx; }
|
|
|
|
|
.picker-action-btn.reset { background: #eef1f4; color: #606266; }
|
|
|
|
|
.picker-scroll { height: calc(72vh - 150rpx); }
|
|
|
|
|
.picker-item { margin: 0 24rpx 12rpx; padding: 16rpx; border-radius: 12rpx; background: #f8fafc; display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
|
.picker-item-name { color: #303133; font-size: 26rpx; }
|
|
|
|
|
.picker-item-code { color: #909399; font-size: 22rpx; margin-top: 6rpx; }
|
|
|
|
|
.picker-item-meta { color: #909399; font-size: 20rpx; margin-top: 4rpx; }
|
|
|
|
|
.picker-check { color: #1a3a5c; font-size: 34rpx; }
|
|
|
|
|
.picker-hint { text-align: center; color: #909399; padding: 16rpx 0 22rpx; }
|
|
|
|
|
</style>
|