Files
todo-frontend/src/pages/order/create.vue
2026-03-25 16:24:07 +00:00

527 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="page">
<!-- 客户信息 -->
<view class="section">
<view class="section-title">客户信息</view>
<view class="form-item">
<text class="label">客户</text>
<picker
mode="selector"
:range="customers"
range-key="name"
@change="selectCustomer"
>
<view class="picker-value">
{{ selectedCustomer ? selectedCustomer.name : '请选择客户' }}
</view>
</picker>
</view>
</view>
<!-- 商品选择 -->
<view class="section">
<view class="section-header">
<text class="section-title">商品明细</text>
<text class="add-btn" @click="goToSelectProduct">+ 添加商品</text>
</view>
<view v-for="(item, index) in orderItems" :key="index" class="order-item">
<view class="item-info">
<text class="item-name">{{ item.productName }}</text>
<text class="item-spec">{{ item.spec || '-' }}</text>
</view>
<view class="item-edit">
<view class="quantity-edit">
<text class="qty-label">数量</text>
<input
class="qty-input"
type="number"
v-model="item.quantity"
@change="calcAmount"
/>
</view>
<view class="price-edit">
<text class="qty-label">单价</text>
<input
class="price-input"
type="digit"
v-model="item.price"
@change="calcAmount"
/>
</view>
<text class="item-subtotal">¥{{ (item.price * item.quantity).toFixed(2) }}</text>
<text class="delete-btn" @click="removeItem(index)">×</text>
</view>
</view>
<view v-if="orderItems.length === 0" class="empty-tip">
请添加商品
</view>
</view>
<!-- 优惠设置 -->
<view class="section">
<view class="section-title">优惠设置</view>
<view class="form-item">
<text class="label">折扣率</text>
<input
class="discount-input"
type="digit"
v-model="discountRate"
@change="calcAmount"
/>
<text class="unit">%</text>
</view>
</view>
<!-- 订单金额 -->
<view class="section amount-section">
<view class="amount-row">
<text class="amount-label">原价合计</text>
<text class="amount-value">¥{{ totalAmount.toFixed(2) }}</text>
</view>
<view class="amount-row">
<text class="amount-label">优惠金额</text>
<text class="amount-value discount">-¥{{ discountAmount.toFixed(2) }}</text>
</view>
<view class="amount-row actual">
<text class="amount-label">实付金额</text>
<text class="amount-value">¥{{ actualAmount.toFixed(2) }}</text>
</view>
</view>
<!-- 支付方式 -->
<view class="section">
<view class="section-title">支付方式</view>
<view class="payment-methods">
<view
class="method-item"
:class="{ active: paymentMethod === 'cash' }"
@click="paymentMethod = 'cash'"
>
💵 现金
</view>
<view
class="method-item"
:class="{ active: paymentMethod === 'wechat' }"
@click="paymentMethod = 'wechat'"
>
💬 微信
</view>
<view
class="method-item"
:class="{ active: paymentMethod === 'alipay' }"
@click="paymentMethod = 'alipay'"
>
💰 支付宝
</view>
</view>
</view>
<!-- 备注 -->
<view class="section">
<view class="section-title">备注</view>
<textarea
class="remark-input"
v-model="remark"
placeholder="请输入备注信息"
/>
</view>
<!-- 提交按钮 -->
<view class="submit-bar">
<view class="submit-info">
<text>实付: </text>
<text class="submit-amount">¥{{ actualAmount.toFixed(2) }}</text>
</view>
<button class="submit-btn" @click="createOrder">创建订单</button>
</view>
</view>
</template>
<script>
import orderApi from '@/api/order'
import productApi from '@/api/product'
import customerApi from '@/api/customer'
export default {
data() {
return {
// 客户相关
customers: [],
selectedCustomer: null,
// 商品相关
orderItems: [],
productList: [],
searchKeyword: '',
// 金额相关
discountRate: 100, // 折扣率
totalAmount: 0, // 原价
discountAmount: 0, // 优惠金额
actualAmount: 0, // 实付金额
// 其他
paymentMethod: 'cash',
remark: '',
// 编辑模式
editingOrderId: null
}
},
onLoad(options) {
this.loadCustomers()
this.loadProducts()
if (options.orderId) {
this.editingOrderId = options.orderId
this.loadOrder(options.orderId)
}
},
methods: {
async loadOrder(orderId) {
try {
const detail = await orderApi.getOrderDetail(orderId)
const order = detail.order
// 设置订单信息
if (order.customerId) {
this.selectedCustomer = this.customers.find(c => c.customerId === order.customerId)
}
this.orderItems = (detail.items || []).map(item => ({
productId: item.productId,
productName: item.productName,
spec: item.productSpec,
unit: item.unit,
price: item.price,
quantity: item.quantity
}))
this.discountRate = order.discountRate
this.remark = order.remark || ''
this.paymentMethod = order.paymentMethod || 'cash'
this.calcAmount()
} catch (e) {
console.error(e)
}
},
async loadCustomers() {
try {
const res = await customerApi.getCustomers({ page: 1, pageSize: 100 })
this.customers = res.records || []
} catch (e) {
console.error(e)
}
},
async loadProducts() {
try {
const res = await productApi.getProducts({ page: 1, pageSize: 100 })
this.productList = res.records || []
} catch (e) {
console.error(e)
}
},
selectCustomer(e) {
this.selectedCustomer = this.customers[e.detail.value]
},
goToSelectProduct() {
uni.navigateTo({ url: '/pages/product/select' })
},
addProduct(product) {
// 检查是否已添加
const exists = this.orderItems.find(item => item.productId === product.productId)
if (exists) {
uni.showToast({ title: '商品已添加', icon: 'none' })
return
}
this.orderItems.push({
productId: product.productId,
productName: product.name,
spec: product.spec,
unit: product.unit,
price: product.price,
quantity: 1
})
this.calcAmount()
},
removeItem(index) {
this.orderItems.splice(index, 1)
this.calcAmount()
},
// 核心金额计算逻辑
calcAmount() {
// 1. 计算原价 = Σ(单价 × 数量)
this.totalAmount = this.orderItems.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0)
// 2. 计算优惠金额 = 原价 × (100 - 折扣率) / 100
this.discountAmount = this.totalAmount * (100 - this.discountRate) / 100
// 3. 计算实付金额 = 原价 - 优惠金额
this.actualAmount = this.totalAmount - this.discountAmount
},
async createOrder() {
if (this.orderItems.length === 0) {
uni.showToast({ title: '请添加商品', icon: 'none' })
return
}
const data = {
customerId: this.selectedCustomer ? this.selectedCustomer.customerId : null,
items: this.orderItems.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.price
})),
discountRate: parseFloat(this.discountRate),
remark: this.remark,
paymentMethod: this.paymentMethod
}
try {
let order
if (this.editingOrderId) {
order = await orderApi.updateOrder(this.editingOrderId, data)
uni.showToast({ title: '订单更新成功', icon: 'success' })
} else {
order = await orderApi.createOrder(data)
uni.showToast({ title: '订单创建成功', icon: 'success' })
}
// 跳转到订单列表(未完成)
setTimeout(() => {
uni.switchTab({
url: '/pages/order/list?status=0'
})
}, 1500)
} catch (e) {
console.error(e)
}
}
}
}
</script>
<style>
.page {
padding: 20rpx;
padding-bottom: 160rpx;
}
.section {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
}
.add-btn {
color: #3cc51f;
font-size: 26rpx;
}
.form-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.label {
width: 120rpx;
font-size: 28rpx;
color: #666;
}
.picker-value {
flex: 1;
font-size: 28rpx;
}
.discount-input {
width: 120rpx;
text-align: right;
}
.order-item {
padding: 20rpx;
background: #f9f9f9;
border-radius: 8rpx;
margin-bottom: 16rpx;
}
.item-info {
margin-bottom: 16rpx;
}
.item-name {
font-size: 28rpx;
font-weight: bold;
display: block;
}
.item-spec {
font-size: 24rpx;
color: #999;
}
.item-edit {
display: flex;
align-items: center;
}
.quantity-edit, .price-edit {
display: flex;
align-items: center;
margin-right: 20rpx;
}
.qty-label, .qty-label {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.qty-input, .price-input {
width: 100rpx;
height: 56rpx;
background: #fff;
border: 1rpx solid #ddd;
border-radius: 8rpx;
text-align: center;
font-size: 24rpx;
}
.item-subtotal {
flex: 1;
text-align: right;
font-size: 28rpx;
font-weight: bold;
color: #ff4d4f;
}
.delete-btn {
margin-left: 20rpx;
font-size: 36rpx;
color: #999;
}
.empty-tip {
padding: 40rpx;
text-align: center;
color: #999;
}
.amount-section {
background: #fff7e6;
}
.amount-row {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
}
.amount-label {
font-size: 26rpx;
color: #666;
}
.amount-value {
font-size: 26rpx;
color: #333;
}
.amount-value.discount {
color: #52c41a;
}
.amount-row.actual {
border-top: 1rpx dashed #ddd;
padding-top: 20rpx;
margin-top: 12rpx;
}
.amount-row.actual .amount-value {
font-size: 32rpx;
font-weight: bold;
color: #ff4d4f;
}
.payment-methods {
display: flex;
gap: 20rpx;
}
.method-item {
flex: 1;
padding: 20rpx;
text-align: center;
background: #f5f5f5;
border-radius: 8rpx;
font-size: 26rpx;
}
.method-item.active {
background: #e6f7ff;
color: #1890ff;
border: 1rpx solid #1890ff;
}
.remark-input {
width: 100%;
height: 120rpx;
font-size: 28rpx;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 20rpx;
background: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
}
.submit-info {
flex: 1;
font-size: 28rpx;
}
.submit-amount {
font-size: 36rpx;
font-weight: bold;
color: #ff4d4f;
}
.submit-btn {
width: 200rpx;
height: 80rpx;
line-height: 80rpx;
background: #3cc51f;
color: #fff;
border: none;
border-radius: 40rpx;
font-size: 28rpx;
}
</style>