2026-07-11 18:01:54 +08:00
|
|
|
<template>
|
|
|
|
|
<view class="yd-page-container erp-page">
|
2026-06-18 17:51:39 +08:00
|
|
|
<wd-navbar
|
|
|
|
|
:title="getTitle"
|
|
|
|
|
left-arrow
|
|
|
|
|
placeholder
|
|
|
|
|
safe-area-inset-top
|
|
|
|
|
fixed
|
|
|
|
|
@click-left="handleBack"
|
|
|
|
|
/>
|
|
|
|
|
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-body">
|
2026-06-18 17:51:39 +08:00
|
|
|
<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>
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-item-list">
|
2026-06-18 17:51:39 +08:00
|
|
|
<view
|
|
|
|
|
v-for="(item, index) in formData.items"
|
|
|
|
|
:key="index"
|
2026-07-11 18:01:54 +08:00
|
|
|
class="erp-form-item-card erp-form-item-card--padded"
|
2026-06-18 17:51:39 +08:00
|
|
|
>
|
|
|
|
|
<view class="mb-12rpx flex items-center justify-between">
|
2026-07-11 18:01:54 +08:00
|
|
|
<text class="erp-card-title">{{ item.productName || `产品${index + 1}` }}</text>
|
2026-06-18 17:51:39 +08:00
|
|
|
<wd-button size="small" type="error" plain @click="removeItem(index)">删除</wd-button>
|
|
|
|
|
</view>
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-input-row">
|
|
|
|
|
<text class="erp-form-input-label">数量</text>
|
2026-06-18 17:51:39 +08:00
|
|
|
<wd-input
|
|
|
|
|
v-model="item.count"
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="数量"
|
|
|
|
|
clearable
|
|
|
|
|
@change="calculateTotal"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-input-row">
|
|
|
|
|
<text class="erp-form-input-label">单价</text>
|
2026-06-18 17:51:39 +08:00
|
|
|
<wd-input
|
|
|
|
|
v-model="item.unitPrice"
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="单价"
|
|
|
|
|
clearable
|
|
|
|
|
@change="calculateTotal"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-total-row">
|
|
|
|
|
<text class="erp-form-input-label">小计</text>
|
|
|
|
|
<text class="text-28rpx text-[#409eff] font-semibold">{{ formatPrice(calculateItemTotal(item)) }}</text>
|
2026-06-18 17:51:39 +08:00
|
|
|
</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%">
|
2026-07-11 18:01:54 +08:00
|
|
|
<view class="erp-form-item-list">
|
2026-06-18 17:51:39 +08:00
|
|
|
<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>
|