Files
crm_uiapp/src/pages-erp/stock-move/form/index.vue
2026-05-15 15:00:41 +08:00

417 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
:title="getTitle"
left-arrow placeholder safe-area-inset-top fixed
@click-left="handleBack"
/>
<!-- 表单区域 -->
<view class="pb-180rpx">
<wd-form ref="formRef" :model="formData" :rules="formRules">
<wd-cell-group title="基本信息" border>
<wd-cell title="调拨单号" :value="formData.no || '保存时自动生成'" />
<wd-cell
title="调拨时间"
title-width="180rpx"
is-link
:value="formData.moveTime ? formatDate(formData.moveTime) : '请选择'"
@click="datePickerVisible = true"
/>
<wd-textarea
v-model="formData.remark"
label="备注"
label-width="180rpx"
placeholder="请输入备注"
:maxlength="200"
show-word-limit
clearable
/>
</wd-cell-group>
<!-- 调拨产品清单 -->
<wd-cell-group title="调拨产品清单" border>
<view v-if="formData.items.length === 0" class="py-40rpx text-center text-[#999]">
暂无产品请点击下方按钮添加
</view>
<view v-else>
<view
v-for="(item, index) in formData.items"
:key="index"
class="mx-24rpx mb-20rpx rounded-12rpx bg-[#f9f9f9] p-24rpx"
>
<view class="mb-12rpx flex items-center justify-between">
<view class="text-28rpx text-[#333] font-semibold">
{{ item.productName || '请选择产品' }}
</view>
<wd-icon name="delete" size="40rpx" color="#f56c6c" @click="removeItem(index)" />
</view>
<view class="mb-12rpx">
<wd-cell
title="调出仓库"
title-width="140rpx"
is-link
:value="item.fromWarehouseName || '请选择'"
@click="openFromWarehousePicker(index)"
/>
</view>
<view class="mb-12rpx">
<wd-cell
title="调入仓库"
title-width="140rpx"
is-link
:value="item.toWarehouseName || '请选择'"
@click="openToWarehousePicker(index)"
/>
</view>
<view class="mb-12rpx">
<wd-cell
title="产品"
title-width="140rpx"
is-link
:value="item.productName || '请选择'"
@click="openProductPicker(index)"
/>
</view>
<view class="mb-12rpx">
<wd-input
v-model="item.count"
label="数量"
label-width="140rpx"
type="number"
placeholder="请输入数量"
@change="calcItemTotal(item)"
/>
</view>
<view class="mb-12rpx">
<wd-input
v-model="item.productPrice"
label="单价"
label-width="140rpx"
type="digit"
placeholder="请输入单价"
@change="calcItemTotal(item)"
/>
</view>
<view class="flex items-center justify-between text-26rpx">
<text class="text-[#999]">金额</text>
<text class="text-[#1890ff] font-semibold">{{ formatPrice(item.totalPrice) }}</text>
</view>
</view>
</view>
<view class="px-24rpx pb-24rpx">
<wd-button type="primary" plain block @click="addItem">
<wd-icon name="add" class="mr-8rpx" />
添加产品
</wd-button>
</view>
</wd-cell-group>
<!-- 合计信息 -->
<wd-cell-group title="合计信息" border>
<wd-cell title="调拨数量" :value="formatCount(totalCount)" />
<wd-cell title="调拨金额" :value="formatPrice(totalPrice)" />
</wd-cell-group>
</wd-form>
</view>
<!-- 底部保存按钮 -->
<view class="yd-detail-footer">
<wd-button
type="primary"
block
:loading="formLoading"
@click="handleSubmit"
>
保存
</wd-button>
</view>
<!-- 日期选择器 -->
<wd-datetime-picker
v-model="datePickerVisible"
type="date"
:value="formData.moveTime"
@confirm="onDateConfirm"
/>
<!-- 调出仓库选择器 -->
<wd-picker
v-model="fromWarehousePickerVisible"
:columns="warehouseColumns"
@confirm="onFromWarehouseConfirm"
/>
<!-- 调入仓库选择器 -->
<wd-picker
v-model="toWarehousePickerVisible"
:columns="warehouseColumns"
@confirm="onToWarehouseConfirm"
/>
<!-- 产品选择器 -->
<wd-picker
v-model="productPickerVisible"
:columns="productColumns"
@confirm="onProductConfirm"
/>
</view>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
import type { StockMove, StockMoveItem } from '@/api/erp/stock-move'
import { computed, onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { createStockMove, getStockMove, updateStockMove } from '@/api/erp/stock-move'
import { getWarehouseSimpleList } from '@/api/erp/warehouse'
import { formatDate as formatDateValue } from '@/utils/date'
import { navigateBackPlus } from '@/utils'
const props = defineProps<{
id?: number | any
}>()
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const getTitle = computed(() => props.id ? '编辑调拨单' : '新增调拨单')
const formLoading = ref(false)
const formData = ref<StockMove>({
id: undefined,
no: undefined,
moveTime: undefined,
remark: undefined,
items: [],
})
const formRules = {
moveTime: [{ required: true, message: '调拨时间不能为空' }],
}
const formRef = ref<FormInstance>()
// 仓库列表
const warehouseList = ref<{ id: number, name: string }[]>([])
const fromWarehousePickerVisible = ref(false)
const toWarehousePickerVisible = ref(false)
const warehouseColumns = computed(() => [
warehouseList.value.map(v => ({ value: v.id, label: v.name })),
])
// 产品列表
const productList = ref<{ id: number, name: string, unitName?: string }[]>([])
const productPickerVisible = ref(false)
const productColumns = computed(() => [
productList.value.map(v => ({ value: v.id, label: v.name })),
])
// 日期选择器
const datePickerVisible = ref(false)
// 当前编辑的产品项索引
const currentItemIndex = ref(-1)
/** 合计数量 */
const totalCount = computed(() => {
return formData.value.items?.reduce((sum, item) => sum + (Number(item.count) || 0), 0) || 0
})
/** 合计金额 */
const totalPrice = computed(() => {
return formData.value.items?.reduce((sum, item) => sum + (Number(item.totalPrice) || 0), 0) || 0
})
/** 格式化数量 */
function formatCount(count?: number) {
if (count === undefined || count === null) return '-'
return count.toFixed(2)
}
/** 格式化金额 */
function formatPrice(price?: number) {
if (price === undefined || price === null) return '-'
return `¥${price.toFixed(2)}`
}
/** 格式化日期 */
function formatDate(dateStr?: string | number | Date) {
return formatDateValue(dateStr) || '-'
}
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages-erp/stock-move/index')
}
/** 加载详情 */
async function getDetail() {
if (!props.id) return
try {
toast.loading('加载中...')
formData.value = await getStockMove(props.id)
} finally {
toast.close()
}
}
/** 日期确认 */
function onDateConfirm({ value }: any) {
formData.value.moveTime = value
datePickerVisible.value = false
}
/** 打开调出仓库选择器 */
function openFromWarehousePicker(index: number) {
currentItemIndex.value = index
fromWarehousePickerVisible.value = true
}
/** 调出仓库确认 */
function onFromWarehouseConfirm({ value }: any) {
if (currentItemIndex.value >= 0 && formData.value.items) {
const item = formData.value.items[currentItemIndex.value]
item.fromWarehouseId = value?.[0]
const warehouse = warehouseList.value.find(w => w.id === item.fromWarehouseId)
item.fromWarehouseName = warehouse?.name
}
fromWarehousePickerVisible.value = false
}
/** 打开调入仓库选择器 */
function openToWarehousePicker(index: number) {
currentItemIndex.value = index
toWarehousePickerVisible.value = true
}
/** 调入仓库确认 */
function onToWarehouseConfirm({ value }: any) {
if (currentItemIndex.value >= 0 && formData.value.items) {
const item = formData.value.items[currentItemIndex.value]
item.toWarehouseId = value?.[0]
const warehouse = warehouseList.value.find(w => w.id === item.toWarehouseId)
item.toWarehouseName = warehouse?.name
}
toWarehousePickerVisible.value = false
}
/** 打开产品选择器 */
function openProductPicker(index: number) {
currentItemIndex.value = index
productPickerVisible.value = true
}
/** 产品确认 */
function onProductConfirm({ value }: any) {
if (currentItemIndex.value >= 0 && formData.value.items) {
const item = formData.value.items[currentItemIndex.value]
item.productId = value?.[0]
const product = productList.value.find(p => p.id === item.productId)
item.productName = product?.name
item.productUnitName = product?.unitName
}
productPickerVisible.value = false
}
/** 添加产品项 */
function addItem() {
if (!formData.value.items) {
formData.value.items = []
}
formData.value.items.push({
fromWarehouseId: undefined,
fromWarehouseName: undefined,
toWarehouseId: undefined,
toWarehouseName: undefined,
productId: undefined,
productName: undefined,
productUnitName: undefined,
count: undefined,
productPrice: undefined,
totalPrice: 0,
})
}
/** 移除产品项 */
function removeItem(index: number) {
formData.value.items?.splice(index, 1)
}
/** 计算产品项金额 */
function calcItemTotal(item: StockMoveItem) {
const count = Number(item.count) || 0
const price = Number(item.productPrice) || 0
item.totalPrice = count * price
}
/** 提交表单 */
async function handleSubmit() {
const { valid } = await formRef.value!.validate()
if (!valid) return
// 校验产品清单
if (!formData.value.items || formData.value.items.length === 0) {
toast.error('请添加调拨产品')
return
}
for (const item of formData.value.items) {
if (!item.fromWarehouseId) {
toast.error('请选择调出仓库')
return
}
if (!item.toWarehouseId) {
toast.error('请选择调入仓库')
return
}
if (item.fromWarehouseId === item.toWarehouseId) {
toast.error('调出仓库和调入仓库不能相同')
return
}
if (!item.productId) {
toast.error('请选择产品')
return
}
if (!item.count || Number(item.count) <= 0) {
toast.error('请输入有效的数量')
return
}
}
formLoading.value = true
try {
// 设置合计
formData.value.totalCount = totalCount.value
formData.value.totalPrice = totalPrice.value
if (props.id) {
await updateStockMove(formData.value)
toast.success('修改成功')
} else {
await createStockMove(formData.value)
toast.success('新增成功')
}
setTimeout(() => {
handleBack()
}, 500)
} finally {
formLoading.value = false
}
}
/** 初始化 */
onMounted(async () => {
// 加载仓库列表
warehouseList.value = await getWarehouseSimpleList()
// 加载详情
getDetail()
})
</script>
<style lang="scss" scoped>
</style>