953 lines
30 KiB
Vue
953 lines
30 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="order-list-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="searchForm"
|
||
:items="searchFormItems"
|
||
:show-expand="false"
|
||
@reset="handleReset"
|
||
@search="handleSearch"
|
||
></ArtSearchBar>
|
||
|
||
<ElCard shadow="never" class="art-table-card">
|
||
<!-- 表格头部 -->
|
||
<ArtTableHeader
|
||
:columnList="columnOptions"
|
||
v-model:columns="columnChecks"
|
||
@refresh="handleRefresh"
|
||
>
|
||
<template #left>
|
||
<ElButton @click="showCreateDialog" v-permission="'orders:add'">{{
|
||
t('orderManagement.createOrder')
|
||
}}</ElButton>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="orderList"
|
||
:currentPage="pagination.page"
|
||
:pageSize="pagination.page_size"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
@row-contextmenu="handleRowContextMenu"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 右键菜单 -->
|
||
<ArtMenuRight
|
||
ref="contextMenuRef"
|
||
:menu-items="contextMenuItems"
|
||
:menu-width="120"
|
||
@select="handleContextMenuSelect"
|
||
/>
|
||
|
||
<!-- 创建订单对话框 -->
|
||
<ElDialog
|
||
v-model="createDialogVisible"
|
||
:title="t('orderManagement.createOrder')"
|
||
width="600px"
|
||
@closed="handleCreateDialogClosed"
|
||
>
|
||
<ElForm ref="createFormRef" :model="createForm" :rules="createRules" label-width="120px">
|
||
<ElFormItem :label="t('orderManagement.table.orderType')" prop="order_type">
|
||
<ElSelect
|
||
v-model="createForm.order_type"
|
||
:placeholder="t('orderManagement.searchForm.orderTypePlaceholder')"
|
||
style="width: 100%"
|
||
>
|
||
<ElOption :label="t('orderManagement.orderType.singleCard')" value="single_card" />
|
||
<ElOption :label="t('orderManagement.orderType.device')" value="device" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
<ElFormItem
|
||
v-if="createForm.order_type === 'single_card'"
|
||
:label="t('orderManagement.createForm.iotCardId')"
|
||
prop="iot_card_id"
|
||
>
|
||
<ElSelect
|
||
v-model="createForm.iot_card_id"
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
:placeholder="t('orderManagement.createForm.iotCardIdPlaceholder')"
|
||
:remote-method="searchIotCards"
|
||
:loading="cardSearchLoading"
|
||
style="width: 100%"
|
||
clearable
|
||
>
|
||
<ElOption
|
||
v-for="card in iotCardOptions"
|
||
:key="card.id"
|
||
:label="`${card.iccid} (${card.msisdn || '无接入号'})`"
|
||
:value="card.id"
|
||
>
|
||
<div style="display: flex; justify-content: space-between">
|
||
<span>{{ card.iccid }}</span>
|
||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
||
{{ card.msisdn || '无接入号' }}
|
||
</span>
|
||
</div>
|
||
</ElOption>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
<ElFormItem
|
||
v-if="createForm.order_type === 'device'"
|
||
:label="t('orderManagement.createForm.deviceId')"
|
||
prop="device_id"
|
||
>
|
||
<ElSelect
|
||
v-model="createForm.device_id"
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
:placeholder="t('orderManagement.createForm.deviceIdPlaceholder')"
|
||
:remote-method="searchDevices"
|
||
:loading="deviceSearchLoading"
|
||
style="width: 100%"
|
||
clearable
|
||
>
|
||
<ElOption
|
||
v-for="device in deviceOptions"
|
||
:key="device.id"
|
||
:label="`${device.device_no} (${device.device_name})`"
|
||
:value="device.id"
|
||
>
|
||
<div style="display: flex; justify-content: space-between">
|
||
<span>{{ device.device_no }}</span>
|
||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
||
{{ device.device_name }}
|
||
</span>
|
||
</div>
|
||
</ElOption>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
<ElFormItem :label="t('orderManagement.createForm.packageIds')" prop="package_ids">
|
||
<ElSelect
|
||
v-model="createForm.package_ids"
|
||
:placeholder="t('orderManagement.createForm.packageIdsPlaceholder')"
|
||
multiple
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
:remote-method="handlePackageSearch"
|
||
:loading="packageSearchLoading"
|
||
:disabled="
|
||
(!createForm.iot_card_id && !createForm.device_id) ||
|
||
(createForm.order_type === 'single_card' && !createForm.iot_card_id) ||
|
||
(createForm.order_type === 'device' && !createForm.device_id)
|
||
"
|
||
clearable
|
||
style="width: 100%"
|
||
>
|
||
<ElOption
|
||
v-for="pkg in packageOptions"
|
||
:key="pkg.id"
|
||
:label="`${pkg.package_name} (¥${(pkg.cost_price / 100).toFixed(2)})`"
|
||
:value="pkg.id"
|
||
>
|
||
<div style="display: flex; justify-content: space-between">
|
||
<span>{{ pkg.package_name }}</span>
|
||
<span style="color: var(--el-text-color-secondary); font-size: 12px">
|
||
¥{{ (pkg.cost_price / 100).toFixed(2) }}
|
||
</span>
|
||
</div>
|
||
</ElOption>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="createDialogVisible = false">{{
|
||
t('orderManagement.actions.cancel')
|
||
}}</ElButton>
|
||
<ElButton
|
||
type="primary"
|
||
@click="handleCreateOrder(createFormRef)"
|
||
:loading="createLoading"
|
||
>
|
||
{{ t('orderManagement.actions.submit') }}
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 订单详情对话框 -->
|
||
<ElDialog
|
||
v-model="detailDialogVisible"
|
||
:title="t('orderManagement.orderDetail')"
|
||
width="800px"
|
||
>
|
||
<div v-if="currentOrder" class="order-detail">
|
||
<ElDescriptions :column="2" border>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.orderNo')">
|
||
{{ currentOrder.order_no }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.orderType')">
|
||
{{ getOrderTypeText(currentOrder.order_type) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.paymentStatus')">
|
||
<ElTag :type="getPaymentStatusType(currentOrder.payment_status)">
|
||
{{ currentOrder.payment_status_text }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.totalAmount')">
|
||
{{ formatCurrency(currentOrder.total_amount) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.paymentMethod')">
|
||
{{ getPaymentMethodText(currentOrder.payment_method) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.buyerType')">
|
||
{{ getBuyerTypeText(currentOrder.buyer_type) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.commissionStatus')">
|
||
{{ getCommissionStatusText(currentOrder.commission_status) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.paidAt')">
|
||
{{ currentOrder.paid_at ? formatDateTime(currentOrder.paid_at) : '-' }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.createdAt')">
|
||
{{ formatDateTime(currentOrder.created_at) }}
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem :label="t('orderManagement.table.updatedAt')">
|
||
{{ formatDateTime(currentOrder.updated_at) }}
|
||
</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
|
||
<!-- 订单项列表 -->
|
||
<div
|
||
v-if="currentOrder.items && currentOrder.items.length > 0"
|
||
style="margin-top: 20px"
|
||
>
|
||
<h4>{{ t('orderManagement.orderItems') }}</h4>
|
||
<ElTable :data="currentOrder.items" border style="margin-top: 10px">
|
||
<ElTableColumn
|
||
prop="package_name"
|
||
:label="t('orderManagement.items.packageName')"
|
||
min-width="150"
|
||
/>
|
||
<ElTableColumn
|
||
prop="quantity"
|
||
:label="t('orderManagement.items.quantity')"
|
||
width="100"
|
||
/>
|
||
<ElTableColumn
|
||
prop="unit_price"
|
||
:label="t('orderManagement.items.unitPrice')"
|
||
width="120"
|
||
>
|
||
<template #default="{ row }">
|
||
{{ formatCurrency(row.unit_price) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn prop="amount" :label="t('orderManagement.items.amount')" width="120">
|
||
<template #default="{ row }">
|
||
{{ formatCurrency(row.amount) }}
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="detailDialogVisible = false">{{
|
||
t('orderManagement.actions.close')
|
||
}}</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { useRouter } from 'vue-router'
|
||
import { OrderService, CardService, DeviceService, PackageManageService } from '@/api/modules'
|
||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import type {
|
||
Order,
|
||
OrderQueryParams,
|
||
CreateOrderRequest,
|
||
PaymentStatus,
|
||
OrderType,
|
||
BuyerType,
|
||
OrderPaymentMethod,
|
||
OrderCommissionStatus,
|
||
StandaloneIotCard,
|
||
Device,
|
||
PackageResponse
|
||
} from '@/types/api'
|
||
import type { SearchFormItem } from '@/types'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import { RoutesAlias } from '@/router/routesAlias'
|
||
|
||
defineOptions({ name: 'OrderList' })
|
||
|
||
const { t } = useI18n()
|
||
const router = useRouter()
|
||
const { hasAuth } = useAuth()
|
||
|
||
const loading = ref(false)
|
||
const createLoading = ref(false)
|
||
const tableRef = ref()
|
||
const createDialogVisible = ref(false)
|
||
const detailDialogVisible = ref(false)
|
||
const currentOrder = ref<Order | null>(null)
|
||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||
const currentRow = ref<Order | null>(null)
|
||
|
||
// 搜索表单初始值
|
||
const initialSearchState: OrderQueryParams = {
|
||
order_no: '',
|
||
payment_status: undefined,
|
||
order_type: undefined,
|
||
start_time: '',
|
||
end_time: ''
|
||
}
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive<OrderQueryParams>({ ...initialSearchState })
|
||
|
||
// 搜索表单配置
|
||
const searchFormItems: SearchFormItem[] = [
|
||
{
|
||
label: t('orderManagement.searchForm.orderNo'),
|
||
prop: 'order_no',
|
||
type: 'input',
|
||
placeholder: t('orderManagement.searchForm.orderNoPlaceholder'),
|
||
config: {
|
||
clearable: true
|
||
}
|
||
},
|
||
{
|
||
label: t('orderManagement.searchForm.paymentStatus'),
|
||
prop: 'payment_status',
|
||
type: 'select',
|
||
placeholder: t('orderManagement.searchForm.paymentStatusPlaceholder'),
|
||
options: [
|
||
{ label: t('orderManagement.paymentStatus.pending'), value: 1 },
|
||
{ label: t('orderManagement.paymentStatus.paid'), value: 2 },
|
||
{ label: t('orderManagement.paymentStatus.cancelled'), value: 3 },
|
||
{ label: t('orderManagement.paymentStatus.refunded'), value: 4 }
|
||
],
|
||
config: {
|
||
clearable: true
|
||
}
|
||
},
|
||
{
|
||
label: t('orderManagement.searchForm.orderType'),
|
||
prop: 'order_type',
|
||
type: 'select',
|
||
placeholder: t('orderManagement.searchForm.orderTypePlaceholder'),
|
||
options: [
|
||
{ label: t('orderManagement.orderType.singleCard'), value: 'single_card' },
|
||
{ label: t('orderManagement.orderType.device'), value: 'device' }
|
||
],
|
||
config: {
|
||
clearable: true
|
||
}
|
||
},
|
||
{
|
||
label: t('orderManagement.searchForm.dateRange'),
|
||
prop: 'dateRange',
|
||
type: 'daterange',
|
||
config: {
|
||
clearable: true,
|
||
startPlaceholder: t('orderManagement.searchForm.startDate'),
|
||
endPlaceholder: t('orderManagement.searchForm.endDate'),
|
||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||
}
|
||
}
|
||
]
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
page: 1,
|
||
page_size: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: t('orderManagement.table.id'), prop: 'id' },
|
||
{ label: t('orderManagement.table.orderNo'), prop: 'order_no' },
|
||
{ label: t('orderManagement.table.orderType'), prop: 'order_type' },
|
||
{ label: t('orderManagement.table.buyerType'), prop: 'buyer_type' },
|
||
{ label: t('orderManagement.table.paymentStatus'), prop: 'payment_status' },
|
||
{ label: t('orderManagement.table.totalAmount'), prop: 'total_amount' },
|
||
{ label: t('orderManagement.table.paymentMethod'), prop: 'payment_method' },
|
||
{ label: t('orderManagement.table.paidAt'), prop: 'paid_at' },
|
||
{ label: t('orderManagement.table.createdAt'), prop: 'created_at' }
|
||
]
|
||
|
||
const createFormRef = ref<FormInstance>()
|
||
|
||
const createRules = reactive<FormRules>({
|
||
order_type: [
|
||
{
|
||
required: true,
|
||
message: t('orderManagement.validation.orderTypeRequired'),
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
package_ids: [
|
||
{
|
||
required: true,
|
||
message: t('orderManagement.validation.packageIdsRequired'),
|
||
trigger: 'change'
|
||
}
|
||
]
|
||
})
|
||
|
||
const createForm = reactive<CreateOrderRequest>({
|
||
order_type: 'single_card',
|
||
package_ids: [],
|
||
iot_card_id: null,
|
||
device_id: null
|
||
})
|
||
|
||
const orderList = ref<Order[]>([])
|
||
|
||
// 套餐搜索相关
|
||
const packageOptions = ref<PackageResponse[]>([])
|
||
const packageSearchLoading = ref(false)
|
||
|
||
// IoT卡搜索相关
|
||
const iotCardOptions = ref<StandaloneIotCard[]>([])
|
||
const cardSearchLoading = ref(false)
|
||
|
||
// 设备搜索相关
|
||
const deviceOptions = ref<Device[]>([])
|
||
const deviceSearchLoading = ref(false)
|
||
|
||
// 搜索套餐(根据套餐名称,可选按series_id筛选)
|
||
const searchPackages = async (query: string, seriesId?: number) => {
|
||
packageSearchLoading.value = true
|
||
try {
|
||
const res = await PackageManageService.getPackages({
|
||
package_name: query || undefined,
|
||
series_id: seriesId,
|
||
page: 1,
|
||
page_size: 20,
|
||
status: 1, // 只获取启用的套餐
|
||
shelf_status: 1 // 只获取已上架的套餐
|
||
})
|
||
if (res.code === 0) {
|
||
packageOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Search packages failed:', error)
|
||
packageOptions.value = []
|
||
} finally {
|
||
packageSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 套餐远程搜索方法(自动使用当前选中的IOT卡/设备的series_id)
|
||
const handlePackageSearch = (query: string) => {
|
||
let seriesId: number | undefined
|
||
// 如果是单卡订单并且已选择IOT卡,使用该卡的series_id筛选
|
||
if (createForm.order_type === 'single_card' && createForm.iot_card_id) {
|
||
const selectedCard = iotCardOptions.value.find((card) => card.id === createForm.iot_card_id)
|
||
if (selectedCard && selectedCard.series_id) {
|
||
seriesId = selectedCard.series_id
|
||
}
|
||
} else if (createForm.order_type === 'device' && createForm.device_id) {
|
||
// 如果是设备订单并且已选择设备,使用设备的series_id筛选
|
||
const selectedDevice = deviceOptions.value.find((dev) => dev.id === createForm.device_id)
|
||
if (selectedDevice && selectedDevice.series_id) {
|
||
seriesId = selectedDevice.series_id
|
||
}
|
||
}
|
||
searchPackages(query, seriesId)
|
||
}
|
||
|
||
// 搜索IoT卡(根据ICCID)
|
||
const searchIotCards = async (query: string) => {
|
||
cardSearchLoading.value = true
|
||
try {
|
||
const res = await CardService.getStandaloneIotCards({
|
||
iccid: query || undefined,
|
||
page: 1,
|
||
page_size: 20
|
||
})
|
||
if (res.code === 0) {
|
||
iotCardOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Search IoT cards failed:', error)
|
||
iotCardOptions.value = []
|
||
} finally {
|
||
cardSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索设备(根据设备号device_no)
|
||
const searchDevices = async (query: string) => {
|
||
deviceSearchLoading.value = true
|
||
try {
|
||
const res = await DeviceService.getDevices({
|
||
device_no: query || undefined,
|
||
page: 1,
|
||
page_size: 20
|
||
})
|
||
if (res.code === 0) {
|
||
deviceOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Search devices failed:', error)
|
||
deviceOptions.value = []
|
||
} finally {
|
||
deviceSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 格式化货币 - 将分转换为元
|
||
const formatCurrency = (amount: number): string => {
|
||
return `¥${(amount / 100).toFixed(2)}`
|
||
}
|
||
|
||
// 获取支付状态标签类型
|
||
const getPaymentStatusType = (
|
||
status: PaymentStatus
|
||
): 'success' | 'info' | 'warning' | 'danger' => {
|
||
const statusMap: Record<PaymentStatus, 'success' | 'info' | 'warning' | 'danger'> = {
|
||
1: 'warning', // 待支付
|
||
2: 'success', // 已支付
|
||
3: 'info', // 已取消
|
||
4: 'danger' // 已退款
|
||
}
|
||
return statusMap[status] || 'info'
|
||
}
|
||
|
||
// 获取订单类型文本
|
||
const getOrderTypeText = (type: OrderType): string => {
|
||
return type === 'single_card'
|
||
? t('orderManagement.orderType.singleCard')
|
||
: t('orderManagement.orderType.device')
|
||
}
|
||
|
||
// 获取买家类型文本
|
||
const getBuyerTypeText = (type: BuyerType): string => {
|
||
return type === 'personal'
|
||
? t('orderManagement.buyerType.personal')
|
||
: t('orderManagement.buyerType.agent')
|
||
}
|
||
|
||
// 获取支付方式文本
|
||
const getPaymentMethodText = (method: OrderPaymentMethod): string => {
|
||
const methodMap: Record<OrderPaymentMethod, string> = {
|
||
wallet: t('orderManagement.paymentMethod.wallet'),
|
||
wechat: t('orderManagement.paymentMethod.wechat'),
|
||
alipay: t('orderManagement.paymentMethod.alipay')
|
||
}
|
||
return methodMap[method] || method
|
||
}
|
||
|
||
// 获取佣金状态文本
|
||
const getCommissionStatusText = (status: OrderCommissionStatus): string => {
|
||
const statusMap: Record<OrderCommissionStatus, string> = {
|
||
0: t('orderManagement.commissionStatus.notApplicable'),
|
||
1: t('orderManagement.commissionStatus.pending'),
|
||
2: t('orderManagement.commissionStatus.settled')
|
||
}
|
||
return statusMap[status] || '-'
|
||
}
|
||
|
||
// 动态列配置
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{
|
||
prop: 'id',
|
||
label: t('orderManagement.table.id'),
|
||
width: 80
|
||
},
|
||
{
|
||
prop: 'order_no',
|
||
label: t('orderManagement.table.orderNo'),
|
||
minWidth: 220
|
||
},
|
||
{
|
||
prop: 'order_type',
|
||
label: t('orderManagement.table.orderType'),
|
||
width: 120,
|
||
formatter: (row: Order) => {
|
||
return h(ElTag, { type: row.order_type === 'single_card' ? 'primary' : 'success' }, () =>
|
||
getOrderTypeText(row.order_type)
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'buyer_type',
|
||
label: t('orderManagement.table.buyerType'),
|
||
width: 120,
|
||
formatter: (row: Order) => {
|
||
return h(ElTag, { type: row.buyer_type === 'personal' ? 'info' : 'warning' }, () =>
|
||
getBuyerTypeText(row.buyer_type)
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'payment_status',
|
||
label: t('orderManagement.table.paymentStatus'),
|
||
width: 120,
|
||
formatter: (row: Order) => {
|
||
return h(
|
||
ElTag,
|
||
{ type: getPaymentStatusType(row.payment_status) },
|
||
() => row.payment_status_text
|
||
)
|
||
}
|
||
},
|
||
{
|
||
prop: 'total_amount',
|
||
label: t('orderManagement.table.totalAmount'),
|
||
width: 120,
|
||
formatter: (row: Order) => formatCurrency(row.total_amount)
|
||
},
|
||
{
|
||
prop: 'payment_method',
|
||
label: t('orderManagement.table.paymentMethod'),
|
||
width: 120,
|
||
formatter: (row: Order) => getPaymentMethodText(row.payment_method)
|
||
},
|
||
{
|
||
prop: 'paid_at',
|
||
label: t('orderManagement.table.paidAt'),
|
||
width: 180,
|
||
formatter: (row: Order) => (row.paid_at ? formatDateTime(row.paid_at) : '-')
|
||
},
|
||
{
|
||
prop: 'created_at',
|
||
label: t('orderManagement.table.createdAt'),
|
||
width: 180,
|
||
formatter: (row: Order) => formatDateTime(row.created_at)
|
||
}
|
||
])
|
||
|
||
// 当选择IOT卡时,根据series_id筛选套餐
|
||
watch(
|
||
() => createForm.iot_card_id,
|
||
(newCardId) => {
|
||
if (newCardId && createForm.order_type === 'single_card') {
|
||
// 找到选中的IOT卡
|
||
const selectedCard = iotCardOptions.value.find((card) => card.id === newCardId)
|
||
if (selectedCard && selectedCard.series_id) {
|
||
// 根据series_id重新加载套餐列表
|
||
loadDefaultPackages(selectedCard.series_id)
|
||
} else {
|
||
// 如果没有series_id,加载所有套餐
|
||
loadDefaultPackages()
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
// 当选择设备时,根据series_id筛选套餐
|
||
watch(
|
||
() => createForm.device_id,
|
||
(newDeviceId) => {
|
||
if (newDeviceId && createForm.order_type === 'device') {
|
||
// 找到选中的设备
|
||
const selectedDevice = deviceOptions.value.find((dev) => dev.id === newDeviceId)
|
||
if (selectedDevice && selectedDevice.series_id) {
|
||
// 根据series_id重新加载套餐列表
|
||
loadDefaultPackages(selectedDevice.series_id)
|
||
} else {
|
||
// 如果没有series_id,加载所有套餐
|
||
loadDefaultPackages()
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
onMounted(() => {
|
||
getTableData()
|
||
})
|
||
|
||
// 获取订单列表
|
||
const getTableData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params: OrderQueryParams = {
|
||
page: pagination.page,
|
||
page_size: pagination.page_size,
|
||
order_no: searchForm.order_no || undefined,
|
||
payment_status: searchForm.payment_status,
|
||
order_type: searchForm.order_type,
|
||
start_time: searchForm.start_time || undefined,
|
||
end_time: searchForm.end_time || undefined
|
||
}
|
||
const res = await OrderService.getOrders(params)
|
||
if (res.code === 0) {
|
||
orderList.value = res.data.list || []
|
||
pagination.total = res.data.total || 0
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 重置搜索
|
||
const handleReset = () => {
|
||
Object.assign(searchForm, { ...initialSearchState })
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
// 处理日期范围
|
||
if (searchForm.dateRange && Array.isArray(searchForm.dateRange)) {
|
||
searchForm.start_time = searchForm.dateRange[0]
|
||
searchForm.end_time = searchForm.dateRange[1]
|
||
} else {
|
||
searchForm.start_time = ''
|
||
searchForm.end_time = ''
|
||
}
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 刷新表格
|
||
const handleRefresh = () => {
|
||
getTableData()
|
||
}
|
||
|
||
// 处理表格分页变化
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.page_size = newPageSize
|
||
getTableData()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.page = newCurrentPage
|
||
getTableData()
|
||
}
|
||
|
||
// 显示创建订单对话框
|
||
const showCreateDialog = async () => {
|
||
createDialogVisible.value = true
|
||
// 加载IoT卡和设备列表,套餐列表在选择IoT卡/设备后才加载
|
||
await Promise.all([loadDefaultIotCards(), loadDefaultDevices()])
|
||
}
|
||
|
||
// 加载默认套餐列表(可选按series_id筛选)
|
||
const loadDefaultPackages = async (seriesId?: number) => {
|
||
packageSearchLoading.value = true
|
||
try {
|
||
const res = await PackageManageService.getPackages({
|
||
series_id: seriesId,
|
||
page: 1,
|
||
page_size: 20,
|
||
status: 1, // 只获取启用的套餐
|
||
shelf_status: 1 // 只获取已上架的套餐
|
||
})
|
||
if (res.code === 0) {
|
||
packageOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Load default packages failed:', error)
|
||
packageOptions.value = []
|
||
} finally {
|
||
packageSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载默认IoT卡列表
|
||
const loadDefaultIotCards = async () => {
|
||
cardSearchLoading.value = true
|
||
try {
|
||
const res = await CardService.getStandaloneIotCards({
|
||
page: 1,
|
||
page_size: 20
|
||
})
|
||
if (res.code === 0) {
|
||
iotCardOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Load default IoT cards failed:', error)
|
||
iotCardOptions.value = []
|
||
} finally {
|
||
cardSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载默认设备列表
|
||
const loadDefaultDevices = async () => {
|
||
deviceSearchLoading.value = true
|
||
try {
|
||
const res = await DeviceService.getDevices({
|
||
page: 1,
|
||
page_size: 20
|
||
})
|
||
if (res.code === 0) {
|
||
deviceOptions.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('Load default devices failed:', error)
|
||
deviceOptions.value = []
|
||
} finally {
|
||
deviceSearchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 对话框关闭后的清理
|
||
const handleCreateDialogClosed = () => {
|
||
// 重置表单(会同时清除验证状态)
|
||
createFormRef.value?.resetFields()
|
||
|
||
// 重置表单数据到初始值
|
||
createForm.order_type = 'single_card'
|
||
createForm.package_ids = []
|
||
createForm.iot_card_id = null
|
||
createForm.device_id = null
|
||
|
||
// 清空套餐、IoT卡和设备搜索结果
|
||
packageOptions.value = []
|
||
iotCardOptions.value = []
|
||
deviceOptions.value = []
|
||
}
|
||
|
||
// 创建订单
|
||
const handleCreateOrder = async (formEl: FormInstance | undefined) => {
|
||
if (!formEl) return
|
||
|
||
await formEl.validate(async (valid) => {
|
||
if (valid) {
|
||
createLoading.value = true
|
||
try {
|
||
const data: CreateOrderRequest = {
|
||
order_type: createForm.order_type,
|
||
package_ids: createForm.package_ids,
|
||
iot_card_id: createForm.order_type === 'single_card' ? createForm.iot_card_id : null,
|
||
device_id: createForm.order_type === 'device' ? createForm.device_id : null
|
||
}
|
||
|
||
await OrderService.createOrder(data)
|
||
ElMessage.success(t('orderManagement.messages.createSuccess'))
|
||
createDialogVisible.value = false
|
||
formEl.resetFields()
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
createLoading.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 查看订单详情
|
||
const showOrderDetail = (row: Order) => {
|
||
router.push({
|
||
path: `${RoutesAlias.OrderList}/detail/${row.id}`
|
||
})
|
||
}
|
||
|
||
// 取消订单
|
||
const handleCancelOrder = (row: Order) => {
|
||
// 已支付的订单不能取消
|
||
if (row.payment_status === 2) {
|
||
ElMessage.warning(t('orderManagement.messages.cannotCancelPaid'))
|
||
return
|
||
}
|
||
|
||
ElMessageBox.confirm(
|
||
t('orderManagement.messages.cancelConfirmText'),
|
||
t('orderManagement.messages.cancelConfirm'),
|
||
{
|
||
confirmButtonText: t('orderManagement.actions.confirm'),
|
||
cancelButtonText: t('orderManagement.actions.cancel'),
|
||
type: 'warning'
|
||
}
|
||
)
|
||
.then(async () => {
|
||
try {
|
||
await OrderService.cancelOrder(row.id)
|
||
ElMessage.success(t('orderManagement.messages.cancelSuccess'))
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消操作
|
||
})
|
||
}
|
||
|
||
// 右键菜单项配置
|
||
const contextMenuItems = computed((): MenuItemType[] => {
|
||
if (!currentRow.value) return []
|
||
|
||
const items: MenuItemType[] = []
|
||
|
||
if (hasAuth('orders:view_detail')) {
|
||
items.push({ key: 'detail', label: '详情' })
|
||
}
|
||
|
||
// 只有待支付和已支付的订单可以删除
|
||
if (
|
||
(currentRow.value.payment_status === 1 || currentRow.value.payment_status === 2) &&
|
||
hasAuth('orders:delete')
|
||
) {
|
||
items.push({ key: 'cancel', label: '删除' })
|
||
}
|
||
|
||
return items
|
||
})
|
||
|
||
// 处理表格行右键菜单
|
||
const handleRowContextMenu = (row: Order, column: any, event: MouseEvent) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
currentRow.value = row
|
||
contextMenuRef.value?.show(event)
|
||
}
|
||
|
||
// 处理右键菜单选择
|
||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||
if (!currentRow.value) return
|
||
|
||
switch (item.key) {
|
||
case 'detail':
|
||
showOrderDetail(currentRow.value)
|
||
break
|
||
case 'cancel':
|
||
handleCancelOrder(currentRow.value)
|
||
break
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.order-list-page {
|
||
height: 100%;
|
||
}
|
||
|
||
.order-detail {
|
||
:deep(.el-descriptions__label) {
|
||
width: 140px;
|
||
}
|
||
}
|
||
</style>
|