李红攀:V2.6.969,客户管理、销售订单
This commit is contained in:
423
src/pages-erp/sale-order/form/index.vue
Normal file
423
src/pages-erp/sale-order/form/index.vue
Normal file
@@ -0,0 +1,423 @@
|
||||
<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-picker
|
||||
v-model="formData.customerId"
|
||||
label="客户"
|
||||
label-width="180rpx"
|
||||
prop="customerId"
|
||||
:columns="customerColumns"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model="formData.orderTime"
|
||||
type="date"
|
||||
label="订单时间"
|
||||
label-width="180rpx"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<wd-picker
|
||||
v-model="formData.saleUserId"
|
||||
label="销售人员"
|
||||
label-width="180rpx"
|
||||
:columns="userColumns"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group title="付款信息" border>
|
||||
<wd-picker
|
||||
v-model="formData.paymentCondition"
|
||||
label="付款条件"
|
||||
label-width="180rpx"
|
||||
:columns="paymentConditionColumns"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<wd-picker
|
||||
v-model="formData.paymentMethod"
|
||||
label="付款方式"
|
||||
label-width="180rpx"
|
||||
:columns="paymentMethodColumns"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<wd-picker
|
||||
v-model="formData.accountId"
|
||||
label="结算账户"
|
||||
label-width="180rpx"
|
||||
:columns="accountColumns"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<wd-input
|
||||
v-model="formData.depositPrice"
|
||||
label="收取订金"
|
||||
label-width="180rpx"
|
||||
placeholder="请输入订金"
|
||||
type="number"
|
||||
clearable
|
||||
/>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group title="订单产品" border>
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="(item, index) in formData.items"
|
||||
:key="index"
|
||||
class="mb-16rpx rounded-8rpx bg-gray-50 p-24rpx"
|
||||
>
|
||||
<view class="mb-12rpx flex items-center justify-between">
|
||||
<text class="text-28rpx font-semibold">{{ item.productName || `产品${index + 1}` }}</text>
|
||||
<wd-button size="small" type="error" plain @click="removeItem(index)">删除</wd-button>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center">
|
||||
<text class="w-140rpx text-24rpx text-gray-500">数量</text>
|
||||
<wd-input
|
||||
v-model="item.count"
|
||||
type="number"
|
||||
placeholder="数量"
|
||||
clearable
|
||||
@change="calculateTotal"
|
||||
/>
|
||||
</view>
|
||||
<view class="mb-12rpx flex items-center">
|
||||
<text class="w-140rpx text-24rpx text-gray-500">单价</text>
|
||||
<wd-input
|
||||
v-model="item.unitPrice"
|
||||
type="number"
|
||||
placeholder="单价"
|
||||
clearable
|
||||
@change="calculateTotal"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<text class="w-140rpx text-24rpx text-gray-500">小计</text>
|
||||
<text class="text-28rpx text-[#409eff]">{{ formatPrice(calculateItemTotal(item)) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<wd-button type="primary" plain block @click="showProductPicker = true">添加产品</wd-button>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group title="优惠信息" border>
|
||||
<wd-input
|
||||
v-model="formData.discountPercent"
|
||||
label="优惠率(%)"
|
||||
label-width="180rpx"
|
||||
placeholder="请输入优惠率"
|
||||
type="number"
|
||||
clearable
|
||||
@change="calculateTotal"
|
||||
/>
|
||||
<wd-input
|
||||
:model-value="formatPrice(formData.discountPrice || 0)"
|
||||
label="收款优惠"
|
||||
label-width="180rpx"
|
||||
placeholder="自动计算"
|
||||
disabled
|
||||
/>
|
||||
<wd-input
|
||||
:model-value="formatPrice(formData.totalPrice || 0)"
|
||||
label="优惠后金额"
|
||||
label-width="180rpx"
|
||||
placeholder="自动计算"
|
||||
disabled
|
||||
/>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group title="其他信息" border>
|
||||
<wd-textarea
|
||||
v-model="formData.remark"
|
||||
label="备注"
|
||||
label-width="180rpx"
|
||||
placeholder="请输入备注"
|
||||
:maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</view>
|
||||
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button type="primary" block :loading="formLoading" @click="handleSubmit">
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<wd-popup v-model="showProductPicker" position="bottom" custom-style="height: 60%">
|
||||
<view class="p-24rpx">
|
||||
<view class="mb-24rpx flex items-center justify-between">
|
||||
<text class="text-32rpx font-semibold">选择产品</text>
|
||||
<wd-button size="small" @click="showProductPicker = false">关闭</wd-button>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="product-picker-list">
|
||||
<view
|
||||
v-for="product in productList"
|
||||
:key="product.id"
|
||||
class="product-picker-item"
|
||||
:class="{ 'is-active': selectedProductId === product.id }"
|
||||
@click="selectedProductId = product.id"
|
||||
>
|
||||
<view class="product-picker-item__main">
|
||||
<text class="product-picker-item__name">{{ product.name }}</text>
|
||||
<text class="product-picker-item__price">¥{{ formatPrice(product.salePrice) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<wd-button type="primary" block class="mt-24rpx" @click="addProduct">确定添加</wd-button>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
|
||||
import type { SaleOrder, SaleOrderItem } from '@/api/erp/sale-order'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { createSaleOrder, getSaleOrder, updateSaleOrder } from '@/api/erp/sale-order'
|
||||
import { getProductSimpleList } from '@/api/erp/product'
|
||||
import { getCustomerSimpleList } from '@/api/erp/customer'
|
||||
import { getSimpleUserList } from '@/api/system/user'
|
||||
import { getAccountSimpleList } from '@/api/erp/account'
|
||||
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 formRef = ref<FormInstance>()
|
||||
|
||||
const customerList = ref<any[]>([])
|
||||
const userList = ref<any[]>([])
|
||||
const productList = ref<any[]>([])
|
||||
const accountList = ref<any[]>([])
|
||||
|
||||
const showProductPicker = ref(false)
|
||||
const selectedProductId = ref<number>()
|
||||
|
||||
const formData = ref<SaleOrder>({
|
||||
id: undefined,
|
||||
customerId: undefined,
|
||||
orderTime: Date.now(),
|
||||
saleUserId: undefined,
|
||||
paymentCondition: undefined,
|
||||
paymentMethod: undefined,
|
||||
accountId: undefined,
|
||||
depositPrice: undefined,
|
||||
discountPercent: undefined,
|
||||
discountPrice: 0,
|
||||
totalPrice: 0,
|
||||
remark: undefined,
|
||||
items: [],
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
customerId: [{ required: true, message: '请选择客户' }],
|
||||
}
|
||||
|
||||
const customerColumns = computed(() => customerList.value.map(v => ({ label: v.name, value: v.id })))
|
||||
const userColumns = computed(() => userList.value.map(v => ({ label: v.nickname, value: v.id })))
|
||||
const accountColumns = computed(() => accountList.value.map(v => ({ label: v.name, value: v.id })))
|
||||
|
||||
const paymentConditionOptions = [
|
||||
{ label: '款到发货', value: 1 },
|
||||
{ label: '货到付款', value: 2 },
|
||||
{ label: '月结', value: 3 },
|
||||
]
|
||||
const paymentConditionColumns = computed(() => paymentConditionOptions.map(v => ({ label: v.label, value: v.value })))
|
||||
|
||||
const paymentMethodOptions = [
|
||||
{ label: '现金', value: 1 },
|
||||
{ label: '银行转账', value: 2 },
|
||||
{ label: '微信支付', value: 3 },
|
||||
{ label: '支付宝', value: 4 },
|
||||
]
|
||||
const paymentMethodColumns = computed(() => paymentMethodOptions.map(v => ({ label: v.label, value: v.value })))
|
||||
|
||||
function formatPrice(price?: number) {
|
||||
if (price === undefined || price === null) return '0.00'
|
||||
return Number(price).toFixed(2)
|
||||
}
|
||||
|
||||
function calculateItemTotal(item: SaleOrderItem) {
|
||||
const count = Number(item.count) || 0
|
||||
const unitPrice = Number(item.unitPrice) || 0
|
||||
return count * unitPrice
|
||||
}
|
||||
|
||||
function calculateTotal() {
|
||||
if (!formData.value.items || formData.value.items.length === 0) {
|
||||
formData.value.totalPrice = 0
|
||||
formData.value.discountPrice = 0
|
||||
return
|
||||
}
|
||||
|
||||
const total = formData.value.items.reduce((sum, item) => sum + calculateItemTotal(item), 0)
|
||||
const discountPercent = Number(formData.value.discountPercent) || 0
|
||||
formData.value.discountPrice = total * (discountPercent / 100)
|
||||
formData.value.totalPrice = total - formData.value.discountPrice
|
||||
}
|
||||
|
||||
function addProduct() {
|
||||
const product = productList.value.find(p => p.id === selectedProductId.value)
|
||||
if (!product) {
|
||||
toast.warning('请选择产品')
|
||||
return
|
||||
}
|
||||
|
||||
const newItem: SaleOrderItem = {
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
count: 1,
|
||||
unitPrice: product.salePrice || 0,
|
||||
totalPrice: product.salePrice || 0,
|
||||
}
|
||||
|
||||
formData.value.items = [...(formData.value.items || []), newItem]
|
||||
selectedProductId.value = undefined
|
||||
showProductPicker.value = false
|
||||
calculateTotal()
|
||||
}
|
||||
|
||||
function removeItem(index: number) {
|
||||
formData.value.items?.splice(index, 1)
|
||||
calculateTotal()
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-erp/sale-order/index')
|
||||
}
|
||||
|
||||
async function getDetail() {
|
||||
if (!props.id) return
|
||||
try {
|
||||
toast.loading('加载中...')
|
||||
formData.value = await getSaleOrder(props.id)
|
||||
calculateTotal()
|
||||
} finally {
|
||||
toast.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
const [customersResult, usersResult, productsResult, accountsResult] = await Promise.allSettled([
|
||||
getCustomerSimpleList(),
|
||||
getSimpleUserList(),
|
||||
getProductSimpleList(),
|
||||
getAccountSimpleList(),
|
||||
])
|
||||
|
||||
customerList.value = customersResult.status === 'fulfilled' ? (customersResult.value || []) : []
|
||||
userList.value = usersResult.status === 'fulfilled' ? (usersResult.value || []) : []
|
||||
productList.value = productsResult.status === 'fulfilled' ? (productsResult.value || []) : []
|
||||
accountList.value = accountsResult.status === 'fulfilled' ? (accountsResult.value || []) : []
|
||||
|
||||
if ([customersResult, usersResult, productsResult, accountsResult].some(result => result.status === 'rejected')) {
|
||||
console.error('加载选项失败', {
|
||||
customersResult,
|
||||
usersResult,
|
||||
productsResult,
|
||||
accountsResult,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value!.validate()
|
||||
if (!valid) return
|
||||
|
||||
if (!formData.value.items || formData.value.items.length === 0) {
|
||||
toast.warning('请添加订单产品')
|
||||
return
|
||||
}
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
const submitData = { ...formData.value }
|
||||
if (submitData.orderTime) {
|
||||
submitData.orderTime = new Date(submitData.orderTime).getTime()
|
||||
}
|
||||
|
||||
submitData.items = (submitData.items || []).map(item => ({
|
||||
...item,
|
||||
totalPrice: calculateItemTotal(item),
|
||||
}))
|
||||
|
||||
if (props.id) {
|
||||
await updateSaleOrder(submitData)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await createSaleOrder(submitData)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
handleBack()
|
||||
}, 500)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadOptions()
|
||||
await getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.product-picker-list {
|
||||
max-height: 720rpx;
|
||||
}
|
||||
|
||||
.product-picker-item {
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.product-picker-item.is-active {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.product-picker-item__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.product-picker-item__name {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-picker-item__price {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user