382 lines
11 KiB
Vue
382 lines
11 KiB
Vue
<template>
|
||
<div class="order-create">
|
||
<el-page-header @back="$router.go(-1)" content="创建订单">
|
||
<template #extra>
|
||
<el-button type="primary" @click="handleSubmit" :loading="loading">
|
||
<el-icon><Check /></el-icon>
|
||
创建订单
|
||
</el-button>
|
||
</template>
|
||
</el-page-header>
|
||
|
||
<el-card class="form-card">
|
||
<el-form
|
||
ref="formRef"
|
||
:model="form"
|
||
:rules="rules"
|
||
label-width="100px"
|
||
@submit.prevent="handleSubmit"
|
||
>
|
||
<el-form-item label="订单类型" prop="orderType">
|
||
<el-select v-model="form.orderType" placeholder="请选择订单类型">
|
||
<el-option label="AI服务" value="SERVICE" />
|
||
<el-option label="AI订阅" value="SUBSCRIPTION" />
|
||
<el-option label="数字商品" value="DIGITAL" />
|
||
<el-option label="虚拟商品" value="VIRTUAL" />
|
||
</el-select>
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>选择您要购买的虚拟商品类型:AI服务(如AI绘画、AI写作)、AI订阅(按月/年付费)、数字商品(如软件、电子书)、虚拟商品(如游戏道具、虚拟货币)</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="货币" prop="currency">
|
||
<el-select v-model="form.currency" placeholder="请选择货币">
|
||
<el-option label="人民币 (CNY)" value="CNY" />
|
||
<el-option label="美元 (USD)" value="USD" />
|
||
<el-option label="欧元 (EUR)" value="EUR" />
|
||
</el-select>
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>选择支付货币类型,系统会根据您选择的货币进行计费和结算</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="订单描述" prop="description">
|
||
<el-input
|
||
v-model="form.description"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请详细描述您的订单需求,如:需要AI绘画服务,风格为动漫风格,尺寸为1024x1024像素"
|
||
/>
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>详细描述您的订单需求,包括服务要求、特殊需求等,这将帮助服务提供方更好地理解您的需求</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="联系邮箱" prop="contactEmail">
|
||
<el-input
|
||
v-model="form.contactEmail"
|
||
type="email"
|
||
placeholder="请输入联系邮箱(用于接收虚拟商品)"
|
||
/>
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>必填项!虚拟商品将通过此邮箱发送给您,请确保邮箱地址正确且可正常接收邮件</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="联系电话" prop="contactPhone">
|
||
<el-input
|
||
v-model="form.contactPhone"
|
||
placeholder="请输入联系电话(可选)"
|
||
/>
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>可选填写,用于紧急情况联系或重要通知,建议填写以便服务提供方在需要时联系您</span>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<!-- 虚拟商品不需要收货地址 -->
|
||
<template v-if="isPhysicalOrder">
|
||
<el-form-item label="收货地址" prop="shippingAddress">
|
||
<el-input
|
||
v-model="form.shippingAddress"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入收货地址"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="账单地址" prop="billingAddress">
|
||
<el-input
|
||
v-model="form.billingAddress"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入账单地址"
|
||
/>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<!-- 订单项 -->
|
||
<el-form-item label="虚拟商品">
|
||
<div class="field-description">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>添加您要购买的虚拟商品,包括商品名称、单价和数量。支持添加多个商品。</span>
|
||
</div>
|
||
<div class="order-items">
|
||
<div
|
||
v-for="(item, index) in form.orderItems"
|
||
:key="index"
|
||
class="order-item"
|
||
>
|
||
<el-row :gutter="20">
|
||
<el-col :span="8">
|
||
<el-input
|
||
v-model="item.productName"
|
||
placeholder="商品名称(如:AI绘画服务、AI写作助手、AI翻译服务等)"
|
||
@input="calculateSubtotal(index)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-input-number
|
||
v-model="item.unitPrice"
|
||
:precision="2"
|
||
:min="0"
|
||
placeholder="单价"
|
||
@change="calculateSubtotal(index)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-input-number
|
||
v-model="item.quantity"
|
||
:min="1"
|
||
placeholder="数量"
|
||
@change="calculateSubtotal(index)"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-input
|
||
v-model="item.subtotal"
|
||
readonly
|
||
placeholder="小计"
|
||
/>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-button
|
||
type="danger"
|
||
:icon="Delete"
|
||
circle
|
||
@click="removeItem(index)"
|
||
v-if="form.orderItems.length > 1"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<el-button
|
||
type="primary"
|
||
:icon="Plus"
|
||
@click="addItem"
|
||
class="add-item-btn"
|
||
>
|
||
添加虚拟商品
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<div class="total-amount">
|
||
<span class="total-label">订单总计:</span>
|
||
<span class="total-value">{{ form.currency }} {{ totalAmount }}</span>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useOrderStore } from '@/stores/orders'
|
||
import { ElMessage } from 'element-plus'
|
||
import { Plus, Delete, Check, InfoFilled } from '@element-plus/icons-vue'
|
||
|
||
const router = useRouter()
|
||
const orderStore = useOrderStore()
|
||
|
||
const formRef = ref()
|
||
const loading = ref(false)
|
||
|
||
const form = reactive({
|
||
orderType: 'SERVICE',
|
||
currency: 'CNY',
|
||
description: '',
|
||
contactEmail: '',
|
||
contactPhone: '',
|
||
shippingAddress: '',
|
||
billingAddress: '',
|
||
orderItems: [
|
||
{
|
||
productName: '',
|
||
unitPrice: 0,
|
||
quantity: 1,
|
||
subtotal: 0
|
||
}
|
||
]
|
||
})
|
||
|
||
const rules = {
|
||
orderType: [
|
||
{ required: true, message: '请选择订单类型', trigger: 'change' }
|
||
],
|
||
currency: [
|
||
{ required: true, message: '请选择货币', trigger: 'change' }
|
||
],
|
||
contactEmail: [
|
||
{ required: true, message: '请输入联系邮箱', trigger: 'blur' },
|
||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
// 检查是否为实体商品订单
|
||
const isPhysicalOrder = computed(() => {
|
||
return form.orderType === 'PHYSICAL'
|
||
})
|
||
|
||
// 计算总金额
|
||
const totalAmount = computed(() => {
|
||
return form.orderItems.reduce((total, item) => {
|
||
return total + parseFloat(item.subtotal || 0)
|
||
}, 0).toFixed(2)
|
||
})
|
||
|
||
// 计算小计
|
||
const calculateSubtotal = (index) => {
|
||
const item = form.orderItems[index]
|
||
if (item.unitPrice && item.quantity) {
|
||
item.subtotal = parseFloat((item.unitPrice * item.quantity).toFixed(2))
|
||
} else {
|
||
item.subtotal = 0
|
||
}
|
||
}
|
||
|
||
// 添加商品项
|
||
const addItem = () => {
|
||
form.orderItems.push({
|
||
productName: '',
|
||
unitPrice: 0,
|
||
quantity: 1,
|
||
subtotal: 0
|
||
})
|
||
}
|
||
|
||
// 删除商品项
|
||
const removeItem = (index) => {
|
||
if (form.orderItems.length > 1) {
|
||
form.orderItems.splice(index, 1)
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
try {
|
||
const valid = await formRef.value.validate()
|
||
if (!valid) return
|
||
|
||
// 验证订单项
|
||
const validItems = form.orderItems.filter(item =>
|
||
item.productName && item.unitPrice > 0 && item.quantity > 0
|
||
)
|
||
|
||
if (validItems.length === 0) {
|
||
ElMessage.error('请至少添加一个有效虚拟商品')
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
|
||
// 准备提交数据
|
||
const orderData = {
|
||
orderType: form.orderType,
|
||
currency: form.currency,
|
||
description: form.description,
|
||
contactEmail: form.contactEmail,
|
||
contactPhone: form.contactPhone,
|
||
shippingAddress: form.shippingAddress,
|
||
billingAddress: form.billingAddress,
|
||
totalAmount: parseFloat(totalAmount.value),
|
||
status: 'PENDING', // 新创建的订单状态为待支付
|
||
orderItems: validItems.map(item => ({
|
||
productName: item.productName,
|
||
unitPrice: parseFloat(item.unitPrice),
|
||
quantity: parseInt(item.quantity),
|
||
subtotal: parseFloat(item.subtotal)
|
||
}))
|
||
}
|
||
|
||
const response = await orderStore.createNewOrder(orderData)
|
||
|
||
if (response.success) {
|
||
ElMessage.success('虚拟商品订单创建成功!商品将发送到您的邮箱')
|
||
router.push('/orders')
|
||
} else {
|
||
ElMessage.error(response.message || '创建订单失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('Create order error:', error)
|
||
ElMessage.error('创建订单失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.order-create {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.form-card {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.order-items {
|
||
width: 100%;
|
||
}
|
||
|
||
.order-item {
|
||
margin-bottom: 16px;
|
||
padding: 16px;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 8px;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.add-item-btn {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.total-amount {
|
||
text-align: right;
|
||
padding: 16px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.total-label {
|
||
font-size: 16px;
|
||
color: #606266;
|
||
}
|
||
|
||
.field-description {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-top: 8px;
|
||
padding: 8px 12px;
|
||
background-color: #f0f9ff;
|
||
border: 1px solid #b3d8ff;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
color: #409eff;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.field-description .el-icon {
|
||
margin-right: 6px;
|
||
margin-top: 1px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.field-description span {
|
||
flex: 1;
|
||
}
|
||
</style>
|
||
|
||
|