Files
shuyuanuinapp/src/addon/erp/pages/outbound/outPending/add.vue
郑彪辉 73b9a68605 refactor(erp): 更新产品分类和单位选择逻辑
- 优化库存和采购相关页面布局
- 修复供应商和仓库选择功能
- 调整统计页面显示内容
- 优化表单验证逻辑
2025-04-29 23:41:03 +08:00

563 lines
20 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="bg-[var(--page-bg-color)] min-h-[100vh] overflow-hidden form-edit" :style="themeColor()">
<up-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"
labelWidth="150rpx">
<view class="sidebar-margin card-template mt-[var(--top-m)] py-[20rpx]">
<view>
<up-form-item :label="'单据编号'" prop="code" required borderBottom>
<u-input fontSize="28rpx" v-model.trim="formData.code" disabled clearable
placeholderStyle="color: #888" :placeholder="'请输入单据编号'" />
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'食堂名称'" prop="customer_id" required borderBottom>
<!-- #ifdef H5 -->
<zxz-uni-data-select v-model="formData.customer_id" :localdata="customerList" dataKey="name" dataValue="id"
:clear="false" placeholder="请选择食堂" @change="customerIdChange"/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<wht-select v-model="formData.customer_id" :options="customerList" dataKey="name" dataValue="id" placeholder="请选择食堂" :height="38" @change="customerIdChange"/>
<!-- #endif -->
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'联系人'" prop="contact" borderBottom>
<u-input fontSize="28rpx" v-model.trim="formData.contact" clearable
placeholderStyle="color: #888" :placeholder="'请输入联系人'" />
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'联系电话'" prop="phone" borderBottom>
<u-input fontSize="28rpx" v-model.trim="formData.phone" clearable
placeholderStyle="color: #888" :placeholder="'请输入联系电话'" />
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'详细地址'" prop="address" borderBottom>
<u-input fontSize="28rpx" v-model.trim="formData.address" clearable
placeholderStyle="color: #888" :placeholder="'请输入详细地址'" />
</up-form-item>
</view>
<!-- <view class="mssst-[16rpx]"> -->
<!-- <up-form-item :label="'供应商'" prop="supplier_id" borderBottom> -->
<!-- #ifdef H5 -->
<!-- <zxz-uni-data-select v-model="formData.supplier_id" :localdata="supplierList" dataKey="name" dataValue="id"
:clear="false" placeholder="请选择供应商" @change="supplierIdChange"/> -->
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<!-- <wht-select v-model="formData.supplier_id" :options="supplierList" dataKey="name" dataValue="id" placeholder="请选择供应商" :height="38" @change="supplierIdChange"/> -->
<!-- #endif -->
<!-- </up-form-item> -->
<!-- </view> -->
<view class="mt-[16rpx]">
<up-form-item :label="'交货方式'" prop="type" required borderBottom>
<!-- #ifdef H5 -->
<zxz-uni-data-select v-model="formData.type" :localdata="typeList" dataKey="name" dataValue="name"
:clear="false" placeholder="请选择交货方式" @change="updateType"/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<wht-select v-model="formData.type" :options="typeList" dataKey="name" dataValue="name" :defaultVal="formData.type" placeholder="请选择交货方式" :height="38" @change="updateType"/>
<!-- #endif -->
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'仓库名称'" prop="warehouse_id" required borderBottom>
<!-- <u-input fontSize="28rpx" v-model.trim="warehouseName" clearable
placeholderStyle="color: #888" disabled/> -->
<!-- #ifdef H5 -->
<zxz-uni-data-select v-model="formData.warehouse_id" :localdata="warehouseList" dataKey="name" dataValue="id"
:clear="false" placeholder="请选择仓库" @change="warehouseIdChange"/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<wht-select v-model="formData.warehouse_id" :options="warehouseList" dataKey="name" dataValue="id" placeholder="请选择仓库" :height="38" @change="warehouseIdChange"/>
<!-- #endif -->
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'送货日期'" prop="delivery_time" required borderBottom @click="deliveryTimeShow = true">
<!-- #ifdef H5 -->
<up-datetime-picker hasInput :show="deliveryTimeShow" v-model="formData.delivery_time" mode="date" :placeholder="'请选择送货日期'" @confirm="deliveryTimeConfirm" @cancel="deliveryTimeShow = false"></up-datetime-picker>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<u-input fontSize="28rpx" v-model="formData.delivery_time" readonly
placeholderStyle="color: #888" placeholder="请选择送货日期" />
<up-datetime-picker :show="deliveryTimeShow" v-model="delivery_time_new" mode="date" :placeholder="'请选择送货日期'" @confirm="deliveryTimeConfirm" @cancel="deliveryTimeShow = false"></up-datetime-picker>
<!-- #endif -->
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="'制单日期'" prop="billing_time" required borderBottom @click="billingTimeShow = true">
<!-- #ifdef H5 -->
<up-datetime-picker hasInput :show="billingTimeShow" v-model="formData.billing_time" mode="date" :placeholder="'请选择制单日期'" @confirm="billingTimeConfirm" @cancel="billingTimeShow = false"></up-datetime-picker>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<u-input fontSize="28rpx" v-model="formData.billing_time" readonly
placeholderStyle="color: #888" placeholder="请选择制单日期" />
<up-datetime-picker :show="billingTimeShow" v-model="billing_time_new" mode="date" :placeholder="'请选择制单日期'" @confirm="billingTimeConfirm" @cancel="billingTimeShow = false"></up-datetime-picker>
<!-- #endif -->
</up-form-item>
</view>
<view class="mt-[16rpx]">
<up-form-item :label="t('remark')" prop="remark">
<u-textarea fontSize="28rpx" v-model="formData.remark" :placeholder="t('remarkPlaceholder')"
></u-textarea>
</up-form-item>
</view>
<!-- <view class="mt-[16rpx]">
<u-form-item :label="'图片'" prop="outbound_image" borderBottom>
<up-upload
:fileList="imageFileList"
@afterRead="afterRead"
multiple
:maxCount="5"
accept="image"
:capture="['camera']"
></up-upload>
</u-form-item>
</view> -->
</view>
<view class="mx-[var(--sidebar-m)] bg-[#ffffff] rounded mt-[20rpx]">
<view class="flex justify-between py-[10rpx] px-[20rpx] box-border border-0 border-b-1 border-color-[#dadbde] border-solid
">
<view class="text-base font-bold">{{t('Enter product')}}</view>
<view><u-button @click="goProductSelect" icon="plus" type="primary" :plain="true" size="mini"
:text="t('Add product')"></u-button>
</view>
</view>
<view v-if="formData.product.length > 0" class="sidebar-margin mt-[20rpx] pb-[20rpx] body-bottom">
<view class="mb-[15rpx]" v-for="(item, index) in formData.product" :key="index">
<view class="bg-[#ffffff] rounded-10rpx">
<view class="head border-0 border-b-1 border-solid border-[#ddd]">
<view class="flex justify-between p-[20rpx]">
<view class="u-body-item-title u-line-2">{{item.product?.name}}</view>
<view><u-button :plain="true" type="error" @click="hanldeDelete(index)"
:text="t('delete')" size="mini"></u-button></view>
</view>
</view>
<view class="body">
<view class="flex justify-start flex-col p-[20rpx]">
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('productCode') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.product?.code}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('productTypeId') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.product?.productType?.name}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('productSpec') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.product?.spec}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('productUnitId') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.product?.productUnit?.name}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ '批次号' }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.batch_number}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ '供应商' }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.supplier.name}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('inventory') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{item.inventory}}</text>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
<view class="content-center">
{{ t('sellPrice') }}:
</view>
<view class="ml-[20rpx]">
<u-number-box min="0" v-model="item.price" :integer="true"
inputWidth="80"></u-number-box>
</view>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
<view class="content-center">
{{ t('quantity') }}:
</view>
<view class="ml-[20rpx]">
<u-number-box min="1" v-model="item.num" :max="item.inventory"
:integer="true" inputWidth="80"></u-number-box>
</view>
</view>
<view class="flex justify-start mb-[18rpx] text-[12px] text-[#6a6a6a]">
{{ t('totalPrice') }}:<text
class="ml-[20rpx] text-[var(--primary-color)]">{{ item.num * item.price }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-else class="sidebar-margin mt-[20rpx] body-bottom text-center">
<text class="text-sm text-color-[#848484]">暂未选择食材</text>
</view>
</view>
</up-form>
<view v-if="hasPermission('erp_manual_outbound_add')" class="w-full footer">
<view
class="py-[var(--top-m)] px-[var(--sidebar-m)] footer w-full fixed bottom-0 left-0 right-0 box-border">
<button hover-class="none"
class="!bg-[var(--primary-color)] text-[#fff] h-[80rpx] leading-[80rpx] rounded-[100rpx] text-[26rpx] font-500"
@click="handSave" :disabled="loading" :class="{'opacity-50': loading}">{{t('save')}}</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { t } from '@/locale'
import { onLoad, onReady } from '@dcloudio/uni-app';
import { getWarehouseSelect,getCustomerSelect,getSupplierSelect} from '@/addon/erp/api/base';
import { addManual } from '@/addon/erp/api/sell';
import { generatedCode } from '@/addon/erp/utils/common';
import {uploadImage } from '@/app/api/system'
import { redirect } from '@/utils/common';
import dayjs from 'dayjs'
import useMemberStore from '@/stores/member'
import usePermission from '@/utils/usePermission'
const { hasPermission } = usePermission()
interface FileItem {
url: string;
status?: string;
message?: string;
}
const memberStore = useMemberStore()
const formRef : any = ref(null)
const loading = ref(false)
const rules = reactive({
// 'supplier_id': {
// required: true,
// message: t('supplierIdPlaceholder'),
// trigger: ['blur', 'change'],
// },
// 'warehouse_id': {
// required: true,
// message: t('warehouseIdPlaceholder'),
// trigger: ['blur', 'change'],
// }
})
const imageFileList=ref<FileItem[]>([])
const formData = ref({
id: "",
code: generatedCode("CK"),
supplier_id: '',
supplier_name:"",
customer_id: "",
customer_name: "",
contact: "",
phone: "",
address: "",
type: "送货上门",
billing_time: dayjs().format('YYYY-MM-DD'),
delivery_time: dayjs().format('YYYY-MM-DD'),
remark: "",
product: [] as any[],
warehouse_id:"",
warehouse_name:"",
outbound_image:"",
total_amount:0,
total_num:0,
})
const warehouseName=ref('')
const delivery_time_new=ref(dayjs().format('YYYY-MM-DD'))
const billing_time_new=ref(dayjs().format('YYYY-MM-DD'))
const typeList = ref([
{
name: '送货上门'
},
{
name: '到店自提'
},
{
name: '邮寄配送'
}
])
onReady(() => {
formRef.value.setRules(rules);
})
// 页面加载
onLoad((option : any) => {
getWarehouseList()
getCustomerList();
getSupplierList();
// 接收子页面传值
uni.$on('choose_product-out', res => {
const uniqueItems : any = {};
formData.value.product.forEach(item => {
uniqueItems[item.product.code] = item;
});
res.forEach((item:any) => {
if (!uniqueItems[item.product.code]) {
uniqueItems[item.product.code] = item;
}
});
const uniqueArray = Object.values(uniqueItems);
formData.value.product = uniqueArray.map((item:any) => {
item.num = 1;
item.unit=item!.product.productUnit ? item!.product.productUnit!.name : ''
item.available_inventory= Number(item.inventory) - Number(item.freeze)
item.price= item.price || 0
return item;
});
})
})
const afterRead = async (event: any) => {
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
let lists:FileItem[] = [].concat(event.file) ;
let fileListLen = imageFileList.value.length;
lists = lists.filter((item:FileItem) => item.url);
lists.map((item: any) => {
imageFileList.value.push({
url: item.url,
status: 'uploading',
message: '上传中',
});
})
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url);
let item = imageFileList.value[fileListLen];
imageFileList.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: result as string,
});
fileListLen++;
}
}
const uploadFilePromise = (url: any) => {
return new Promise((resolve, reject) => {
let a = uploadImage({
filePath:url,
name: 'file'
}).then((res:any)=>{
setTimeout(() => {
resolve(res.data.url as string);
}, 1000);
})
});
}
/**
* 供应商
*/
const supplierList = ref<any[]>([]);
const supplierIdChange = (e : any) => {
formData.value.supplier_id = e?.id ?? '';
formData.value.supplier_name = e?.name ?? '';
if (formRef.value) {
formRef.value.validateField('supplier_id');
}
}
// 获取供应商
const getSupplierList = () => {
getSupplierSelect({}).then((res : any) => {
supplierList.value = res.data;
}).catch(err => {
// 如果是4001没有绑定企业账号强制跳转绑定
if (err.code == 4001) {
redirect({ url: '/addon/erp/pages/member/bind' })
}
})
}
/**
* 仓库
*/
const warehouseList = ref<any[]>([]);
const warehouseIdChange = (e : any) => {
formData.value.warehouse_id = e.id;
formData.value.warehouse_name = e.name;
if (formRef.value) {
formRef.value.validateField('warehouse_id');
}
}
// 获取仓库
const getWarehouseList = () => {
getWarehouseSelect({}).then((res : any) => {
warehouseList.value = res.data;
}).catch(err => {
// 如果是4001没有绑定企业账号强制跳转绑定
if (err.code == 4001) {
redirect({ url: '/addon/erp/pages/member/bind' })
}
})
}
/**
* 食堂
*/
const customerList = ref<any[]>([]);
const customerIdChange = (e : any) => {
formData.value.customer_id = e.id;
formData.value.customer_name = e.name;
formData.value.contact = e.contact;
formData.value.phone = e.phone;
formData.value.address = e.address;
formData.value.warehouse_id = e.warehouse_id;
if(e.warehouse_id){
const warehouseitem = warehouseList.value.find(item=>e.warehouse_id === item.id) || null
warehouseName.value = warehouseitem?.name || ''
}else{
warehouseName.value=''
}
if (formRef.value) {
formRef.value.validateField('customer_id');
}
}
// 获取食堂
const getCustomerList = () => {
getCustomerSelect({}).then((res : any) => {
customerList.value = res.data;
}).catch(err => {
// 如果是4001没有绑定企业账号强制跳转绑定
if (err.code == 4001) {
redirect({ url: '/addon/erp/pages/member/bind' })
}
})
}
const updateType = (e : any) => {
formData.value.type = e.name;
}
// 跳转到食材选择页
const goProductSelect = () => {
if (!formData.value.warehouse_id) {
uni.showToast({ icon: 'none', title: t('warehousePlaceholder') })
return false;
}
return redirect({
url: '/addon/erp/pages/product/product/out-product-select',
param: {
warehouse_id: formData.value.warehouse_id
},
})
}
/**
* 交货日期
*/
const deliveryTimeShow = ref(false);
const deliveryTimeConfirm = (e : any) => {
formData.value.delivery_time = uni.$u.date(e.value, 'yyyy-mm-dd');
deliveryTimeShow.value = false;
}
/**
* 制单日期
*/
const billingTimeShow = ref(false);
const billingTimeConfirm = (e : any) => {
formData.value.billing_time = uni.$u.date(e.value, 'yyyy-mm-dd');
billingTimeShow.value = false;
}
// 删除
const hanldeDelete = (index : number) => {
formData.value.product.splice(index, 1);
}
// 总金额
const totalData = computed(() => {
let data = {
money: 0,
nums: 0,
};
for (let item of formData.value.product) {
let amount = Number(item.num) * Number(item.price);
data.money += amount;
data.nums += Number(item.num);
}
return data;
});
// 保存
const handSave = async() => {s
if (loading.value) return
if (!formData.value.customer_id) {
uni.showToast({ icon: 'none', title:'请选择食堂' })
return false;
}
if (!formData.value.warehouse_id) {
uni.showToast({ icon: 'none', title:'请选择仓库' })
return false;
}
if (!formData.value.delivery_time) {
uni.showToast({ icon: 'none', title: '请选择送货日期' })
return false;
}
if (!formData.value.type) {
uni.showToast({ icon: 'none', title: '请选择交货方式' })
return false;
}
if (!formData.value.billing_time) {
uni.showToast({ icon: 'none', title: '请选择制单日期' })
return false;
}
loading.value = true
formData.value.total_num = totalData.value.nums;
formData.value.total_amount = totalData.value.money;
formData.value.billing_time = uni.$u.date(formData.value.billing_time, 'yyyy-mm-dd');
formData.value.delivery_time = uni.$u.date(formData.value.delivery_time, 'yyyy-mm-dd');
if(imageFileList.value.length > 0){
const imgUrls=imageFileList.value.map((item:any)=>item.url)
formData.value.outbound_image=imgUrls.join(',')
}
addManual(formData.value).then((res : any) => {
loading.value = false
if (res.code == 1) {
redirect({ url: '/addon/erp/pages/index' })
}
}).catch((err) => {
// 如果是4001没有绑定企业账号强制跳转绑定
if (err.code == 4001) {
redirect({ url: '/addon/erp/pages/member/bind' })
}
loading.value = false
})
// console.log("formRef.value", formRef.value);
// console.log("formData.value", formData.value);
// return;
// formRef.value.validate().then(() => {
// }).catch(err => {
// console.log("err", err);
// })
}
</script>
<style lang="scss" scoped>
.form-edit :deep(.up-form-item__body__left__content__label) {
font-size: 28rpx !important;
}
.form-edit :deep(.up-form-item__body__right) {
display: flex;
align-items: center;
}
.footer {
height: calc(100rpx + var(--top-m) + var(--top-m) + constant(safe-area-inset-bottom)) !important;
height: calc(100rpx + var(--top-m) + var(--top-m) + env(safe-area-inset-bottom)) !important;
}
</style>