Files
todo-frontend/src/pages/order/create.vue
2026-03-23 15:03:37 +00:00

538 lines
11 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="addProduct">+ 添加商品</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>
<!-- 商品选择弹窗 -->
<uni-popup ref="productPopup" type="bottom">
<view class="product-popup">
<view class="popup-header">
<text>选择商品</text>
<text @click="$refs.productPopup.close()">关闭</text>
</view>
<view class="popup-search">
<input v-model="searchKeyword" placeholder="搜索商品" />
</view>
<scroll-view class="popup-list" scroll-y>
<view
v-for="p in productList"
:key="p.productId"
class="popup-item"
@click="selectProduct(p)"
>
<text>{{ p.name }}</text>
<text>¥{{ p.price }}</text>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script>
import orderApi from '@/api/order'
import productApi from '@/api/product'
export default {
data() {
return {
// 客户相关
customers: [],
selectedCustomer: null,
// 商品相关
orderItems: [],
productList: [],
searchKeyword: '',
// 金额相关
discountRate: 100, // 折扣率
totalAmount: 0, // 原价
discountAmount: 0, // 优惠金额
actualAmount: 0, // 实付金额
// 其他
paymentMethod: 'cash',
remark: ''
}
},
onLoad() {
this.loadProducts()
},
methods: {
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]
},
addProduct() {
this.$refs.productPopup.open()
},
selectProduct(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()
this.$refs.productPopup.close()
},
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 {
const order = await orderApi.createOrder(data)
uni.showToast({ title: '订单创建成功', icon: 'success' })
// 跳转到订单详情或列表
setTimeout(() => {
uni.navigateTo({
url: `/pages/order/list`
})
}, 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;
}
.product-popup {
background: #fff;
height: 60vh;
border-radius: 24rpx 24rpx 0 0;
}
.popup-header {
display: flex;
justify-content: space-between;
padding: 24rpx;
border-bottom: 1rpx solid #eee;
}
.popup-search {
padding: 20rpx;
}
.popup-search input {
height: 60rpx;
background: #f5f5f5;
border-radius: 8rpx;
padding: 0 20rpx;
}
.popup-list {
height: calc(60vh - 140rpx);
}
.popup-item {
display: flex;
justify-content: space-between;
padding: 24rpx;
border-bottom: 1rpx solid #f5f5f5;
}
</style>