Files
one-pipe-system/src/views/order-management/order-list/index.vue
luo d9d07422a9
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m44s
fix: update some files
2026-08-18 17:36:41 +08:00

1426 lines
44 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>
<ArtTableFullScreen>
<div class="order-list-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
show-expand
@reset="handleReset"
label-width="90"
@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'">{{
'创建订单'
}}</ElButton>
<ElButton
v-if="hasAuth(BULK_PURCHASE_PERMISSIONS.create)"
type="primary"
@click="router.push(RoutesAlias.BulkPurchase)"
>
批量订购
</ElButton>
<ElButton v-if="hasAuth('orders:export')" @click="exportDialogVisible = true">{{
'导出'
}}</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"
:actions="getActions"
:actionsWidth="180"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 创建订单对话框 -->
<ElDialog
v-model="createDialogVisible"
:title="'创建订单'"
width="30%"
@closed="handleCreateDialogClosed"
>
<ElForm ref="createFormRef" :model="createForm" :rules="createRules" label-width="80px">
<ElFormItem :label="'订单类型'" prop="order_type">
<ElSelect
v-model="createForm.order_type"
:placeholder="'请选择订单类型'"
style="width: 100%"
>
<ElOption :label="'单卡订单'" value="single_card" />
<ElOption :label="'设备订单'" value="device" />
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="createForm.order_type === 'single_card'"
:label="'IoT卡'"
prop="iot_card_id"
>
<ElSelect
v-model="createForm.iot_card_id"
filterable
remote
reserve-keyword
:placeholder="'请选择IoT卡'"
:remote-method="searchIotCards"
:loading="cardSearchLoading"
style="width: 100%"
clearable
@change="handleIotCardChange"
>
<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="font-size: 12px; color: var(--el-text-color-secondary)">
{{ card.msisdn || '无接入号' }}
</span>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="createForm.order_type === 'device'" :label="'设备'" prop="device_id">
<ElSelect
v-model="createForm.device_id"
filterable
remote
reserve-keyword
:placeholder="'请选择设备'"
:remote-method="searchDevices"
:loading="deviceSearchLoading"
style="width: 100%"
clearable
@change="handleDeviceChange"
>
<ElOption
v-for="device in deviceOptions"
:key="device.id"
:label="`${device.virtual_no} (${device.device_name})`"
:value="device.id"
>
<div style="display: flex; justify-content: space-between">
<span>{{ device.virtual_no }}</span>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
{{ device.device_name }}
</span>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem :label="'套餐'" prop="package_id">
<ElSelect
v-model="createForm.package_id"
:placeholder="'请选择套餐'"
filterable
reserve-keyword
:loading="packageSearchLoading"
:disabled="!createForm.iot_card_id && !createForm.device_id"
clearable
style="width: 100%"
>
<ElOption
v-for="pkg in packageOptions"
:key="pkg.id"
:label="`${pkg.package_name} (${formatPackageSuggestedRetailPrice(pkg)})`"
:value="pkg.id"
>
<div style="display: flex; justify-content: space-between">
<span>{{ pkg.package_name }}</span>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
<template v-if="pkg.is_gift">
<el-tag type="warning" size="small">赠送</el-tag>
</template>
<template v-else>{{ formatPackageSuggestedRetailPrice(pkg) }}</template>
</span>
</div>
</ElOption>
</ElSelect>
<template v-if="noSeriesId">
<div style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-warning)">
{{ noSeriesMessage }}
</div>
</template>
<template v-else-if="packagesEmptyWithSeriesId">
<div style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-info)">
该系列暂无可用套餐
</div>
</template>
<template v-else-if="selectedPackageIsGift">
<div style="margin-top: 4px; font-size: 12px; color: var(--el-color-warning)">
提示: 赠送套餐只能使用线下支付方式且必须上传支付凭证
</div>
</template>
</ElFormItem>
<ElFormItem label="支付方式" prop="payment_method">
<ElSelect
v-model="createForm.payment_method"
placeholder="请选择支付方式"
style="width: 100%"
:disabled="selectedPackageIsGift"
>
<ElOption v-if="!selectedPackageIsGift" label="钱包支付" value="wallet" />
<!-- 只有平台用户(user_type 1:超级管理员, 2:平台用户)才显示线下支付选项 -->
<ElOption
v-if="userStore.info.user_type === 1 || userStore.info.user_type === 2"
label="线下支付"
value="offline"
/>
</ElSelect>
<div style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
<template v-if="createForm.payment_method === 'wallet'">
提示: 使用钱包支付时,订单将直接完成
</template>
<template v-else-if="createForm.payment_method === 'offline'">
提示: 线下支付订单需要手动确认支付
</template>
</div>
</ElFormItem>
<ElFormItem
v-if="createForm.payment_method === 'offline'"
label="支付凭证"
prop="payment_voucher_key"
>
<VoucherUpload
ref="uploadRef"
v-model="createForm.payment_voucher_key"
voucher-name="支付凭证"
@uploading-change="voucherUploading = $event"
@change="createFormRef?.validateField('payment_voucher_key')"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="createDialogVisible = false">{{ '取消' }}</ElButton>
<ElButton
type="primary"
@click="handleCreateOrder(createFormRef)"
:loading="createLoading || voucherUploading"
:disabled="voucherUploading"
>
{{ voucherUploading ? '凭证上传中...' : '提交' }}
</ElButton>
</div>
</template>
</ElDialog>
<!-- 支付凭证预览 -->
<PaymentVoucherDialog
:file-keys="paymentVoucherFileKeys"
@close="paymentVoucherFileKeys = []"
/>
<!-- 导出任务对话框 -->
<ExportTaskCreateDialog
v-model="exportDialogVisible"
scene="order"
:query="exportQuery"
confirm-permission="orders:export"
title="导出订单"
/>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { computed, h } from 'vue'
import { useRouter } from 'vue-router'
import {
OrderService,
CardService,
DeviceService,
PackageManageService,
ShopService
} 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,
OrderCommissionResult,
StandaloneIotCard,
Device,
PackageResponse
} from '@/types/api'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/store/modules/user'
import { formatDateTime } from '@/utils/business/format'
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
import { RoutesAlias } from '@/router/routesAlias'
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
import {
resolveAuditResourceTarget,
resolveFinanceAuditTarget
} from '@/utils/business/auditNavigation'
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
import ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.vue'
import VoucherUpload from '@/components/business/VoucherUpload.vue'
import { BULK_PURCHASE_PERMISSIONS } from '@/config/constants/bulkPurchase'
defineOptions({ name: 'OrderList' })
type OrderSearchFormState = Omit<OrderQueryParams, 'is_expired'> & {
is_expired?: number
}
const router = useRouter()
const { hasAuth } = useAuth()
const userStore = useUserStore()
const loading = ref(false)
const createLoading = ref(false)
const voucherUploading = ref(false)
const tableRef = ref()
const createDialogVisible = ref(false)
const exportDialogVisible = ref(false)
const paymentVoucherFileKeys = ref<string[]>([])
// 搜索表单初始值
const initialSearchState: OrderSearchFormState = {
order_no: '',
buyer_phone: '',
payment_status: undefined,
payment_method: undefined,
order_type: undefined,
purchase_role: undefined,
identifier: '',
is_expired: undefined,
seller_shop_id: undefined,
start_time: '',
end_time: ''
}
// 搜索表单
const searchForm = reactive<OrderSearchFormState>({ ...initialSearchState })
// 店铺列表(用于代理商搜索)
const shopOptions = ref<any[]>([])
// 搜索代理商
const searchShops = async (query: string) => {
try {
const params: any = {
page: 1,
page_size: 20
}
if (query) {
params.shop_name = query
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
shopOptions.value = res.data.items || []
}
} catch (error) {
console.error('Search shops failed:', error)
}
}
// 获取订单角色文本
const getPurchaseRoleText = (role: string): string => {
const roleMap: Record<string, string> = {
self_purchase: '自己购买',
purchased_by_parent: '上级代理购买',
purchased_by_platform: '平台代购',
purchase_for_subordinate: '给下级购买'
}
return roleMap[role] || role
}
// 搜索表单配置
const searchFormItems: SearchFormItem[] = [
{
label: '订单号',
prop: 'order_no',
type: 'input',
placeholder: '请输入订单号',
config: {
maxlength: 50,
clearable: true
}
},
{
label: '资产标识符',
prop: 'identifier',
type: 'input',
config: {
maxlength: 30,
clearable: true,
placeholder: 'ICCID/VirtualNo/IMEI/MSISDN/SN'
}
},
{
label: '买家手机号',
prop: 'buyer_phone',
type: 'input',
placeholder: '请输入买家手机号',
config: {
maxlength: 20,
clearable: true
}
},
{
label: '所属代理商',
prop: 'seller_shop_id',
type: 'select',
placeholder: '请选择所属代理商',
options: () =>
shopOptions.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
})),
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: (query: string) => searchShops(query)
}
},
{
label: '支付方式',
prop: 'payment_method',
type: 'select',
placeholder: '请选择支付方式',
options: [
{ label: '钱包支付', value: 'wallet' },
{ label: '微信支付', value: 'wechat' },
{ label: '支付宝支付', value: 'alipay' },
{ label: '线下支付', value: 'offline' }
],
config: {
clearable: true
}
},
{
label: '支付状态',
prop: 'payment_status',
type: 'select',
placeholder: '请选择支付状态',
options: [
{ label: '待支付', value: 1 },
{ label: '已支付', value: 2 },
{ label: '已取消', value: 3 },
{ label: '已退款', value: 4 }
],
config: {
clearable: true
}
},
{
label: '是否过期',
prop: 'is_expired',
type: 'select',
placeholder: '请选择',
options: [
{ label: '已过期', value: 1 },
{ label: '未过期', value: 0 }
],
config: {
clearable: true
}
},
{
label: '订单类型',
prop: 'order_type',
type: 'select',
placeholder: '请选择订单类型',
options: [
{ label: '单卡订单', value: 'single_card' },
{ label: '设备订单', value: 'device' }
],
config: {
clearable: true
}
},
{
label: '订单渠道',
prop: 'purchase_role',
type: 'select',
placeholder: '请选择订单渠道',
options: [
{ label: '自己购买', value: 'self_purchase' },
{ label: '上级代理购买', value: 'purchased_by_parent' },
{ label: '平台代购', value: 'purchased_by_platform' },
{ label: '给下级购买', value: 'purchase_for_subordinate' }
],
config: {
clearable: true
}
},
{
label: '起止时间',
prop: 'dateRange',
type: 'datetimerange',
config: {
type: 'datetimerange',
rangeSeparator: '至',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
}
}
]
// 分页
const pagination = reactive({
page: 1,
page_size: 20,
total: 0
})
// 列配置
const columnOptions = computed(() => {
const options = [
{ label: '订单编号', prop: 'order_no' },
{ label: '买家手机号', prop: 'buyer_phone' },
{ label: '买家昵称', prop: 'buyer_nickname' },
{ label: '订单类型', prop: 'order_type' },
{ label: '资产类型', prop: 'asset_type' },
{ label: '买家类型', prop: 'buyer_type' },
{ label: '资产标识符', prop: 'asset_identifier' },
{ label: '订单渠道', prop: 'purchase_role' },
{ label: '购买备注', prop: 'purchase_remark' },
{ label: '是否代购', prop: 'is_purchase_on_behalf' },
{ label: '是否上级代购', prop: 'is_purchased_by_parent' },
{ label: '下单时间', prop: 'created_at' },
{ label: '操作者', prop: 'operator_name' },
{ label: '销售店铺', prop: 'seller_shop_name' },
{ label: '支付状态', prop: 'payment_status' },
{ label: '佣金流程状态', prop: 'commission_status_name' },
{ label: '佣金业务结果', prop: 'commission_result_name' },
{ label: '订单金额', prop: 'total_amount' },
{ label: '是否过期', prop: 'is_expired' },
{ label: '超时时间', prop: 'expires_at' }
]
// 只有非代理账号和非企业账号才能看到实付金额选项
if (canViewActualPaidAmount.value) {
options.push({ label: '实付金额', prop: 'actual_paid_amount' })
}
options.push(
{ label: '支付方式', prop: 'payment_method' },
{ label: '支付时间', prop: 'paid_at' }
)
return options
})
const createFormRef = ref<FormInstance>()
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
const createRules = computed<FormRules>(() => {
const baseRules: FormRules = {
order_type: [
{
required: true,
message: '请选择订单类型',
trigger: 'change'
}
],
package_id: [
{
required: true,
message: '请选择套餐',
trigger: 'change'
}
],
payment_method: [
{
required: true,
message: '请选择支付方式',
trigger: 'change'
}
]
}
// 线下支付时,支付凭证为必填
if (createForm.payment_method === 'offline') {
baseRules.payment_voucher_key = [
{
required: true,
message: '请上传支付凭证'
}
]
}
return baseRules
})
const createForm = reactive({
order_type: 'single_card' as OrderType,
package_id: undefined as number | undefined, // 前端使用单选
iot_card_id: null as number | null,
iot_card_series_id: null as number | null, // IoT卡的套餐系列ID
device_id: null as number | null,
device_series_id: null as number | null, // 设备的套餐系列ID
payment_method: 'wallet' as OrderPaymentMethod, // 默认使用钱包支付
payment_voucher_key: [] as string[] // 线下支付凭证
})
const orderList = ref<Order[]>([])
// 套餐搜索相关
const packageOptions = ref<PackageResponse[]>([])
const packageSearchLoading = ref(false)
const isLoadingPackagesFromSelection = ref(false)
// IoT卡搜索相关
const iotCardOptions = ref<StandaloneIotCard[]>([])
const cardSearchLoading = ref(false)
// 设备搜索相关
const deviceOptions = ref<Device[]>([])
const deviceSearchLoading = ref(false)
// 套餐列表为空但有seriesId的提示
const packagesEmptyWithSeriesId = ref(false)
// 检查是否选择了卡/设备但没有关联套餐系列
const noSeriesId = computed(() => {
if (createForm.order_type === 'single_card' && createForm.iot_card_id) {
return !createForm.iot_card_series_id
}
if (createForm.order_type === 'device' && createForm.device_id) {
return !createForm.device_series_id
}
return false
})
const noSeriesMessage = computed(
() => `${createForm.order_type === 'single_card' ? '卡' : '设备'}未关联套餐系列,无法购买套餐`
)
// 检查是否选择了赠送套餐
const selectedPackageIsGift = computed(() => {
if (!createForm.package_id) return false
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === createForm.package_id)
return selectedPackage?.is_gift || false
})
const formatPackageSuggestedRetailPrice = (pkg: PackageResponse): string => {
if (pkg.suggested_retail_price === null || pkg.suggested_retail_price === undefined) {
return '未配置建议售价'
}
return `¥${(pkg.suggested_retail_price / 100).toFixed(2)}`
}
// IoT卡选择变化时根据series_id加载套餐列表
const handleIotCardChange = (cardId: number | null) => {
if (!cardId) {
createForm.iot_card_series_id = null
createForm.package_id = undefined
packageOptions.value = []
packagesEmptyWithSeriesId.value = false
return
}
const selectedCard = iotCardOptions.value.find((card) => card.id === cardId)
createForm.iot_card_series_id = selectedCard?.series_id || null
createForm.package_id = undefined
packageOptions.value = []
packagesEmptyWithSeriesId.value = false
if (selectedCard?.series_id) {
loadDefaultPackages(selectedCard.series_id)
} else {
packageOptions.value = []
}
}
// 设备选择变化时根据series_id加载套餐列表
const handleDeviceChange = (deviceId: number | null) => {
if (!deviceId) {
createForm.device_series_id = null
createForm.package_id = undefined
packageOptions.value = []
packagesEmptyWithSeriesId.value = false
return
}
const selectedDevice = deviceOptions.value.find((dev) => dev.id === deviceId)
createForm.device_series_id = selectedDevice?.series_id || null
createForm.package_id = undefined
packageOptions.value = []
packagesEmptyWithSeriesId.value = false
if (selectedDevice?.series_id) {
loadDefaultPackages(selectedDevice.series_id)
} else {
packageOptions.value = []
}
}
// 搜索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
}
}
// 搜索设备根据设备号virtual_no
const searchDevices = async (query: string) => {
deviceSearchLoading.value = true
try {
const res = await DeviceService.getDevices({
virtual_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' ? '单卡订单' : '设备订单'
}
// 获取买家类型文本
const getBuyerTypeText = (type: BuyerType): string => {
return type === 'personal' ? '个人' : '代理'
}
// 获取支付方式文本
const getPaymentMethodText = (method: OrderPaymentMethod): string => {
const methodMap: Record<OrderPaymentMethod, string> = {
wallet: '钱包',
wechat: '微信',
alipay: '支付宝',
offline: '线下'
}
return methodMap[method] || method
}
// 获取佣金流程状态文本
const getCommissionStatusText = (status?: OrderCommissionStatus, statusName?: string): string => {
if (statusName) return statusName
if (status === undefined) return '-'
const statusMap: Record<OrderCommissionStatus, string> = {
1: '待计算',
2: '已完成',
3: '待人工处理'
}
return statusMap[status] || '-'
}
// 获取佣金业务结果文本
const getCommissionResultText = (result?: OrderCommissionResult, resultName?: string): string => {
if (resultName) return resultName
if (result === undefined) return '-'
const resultMap: Record<OrderCommissionResult, string> = {
0: '未知',
1: '有佣金',
2: '无佣金',
3: '链路异常'
}
return resultMap[result] || '-'
}
const getCommissionStatusType = (
status?: OrderCommissionStatus
): 'success' | 'info' | 'warning' | 'danger' => {
switch (status) {
case 1:
return 'warning'
case 2:
return 'success'
case 3:
return 'danger'
default:
return 'info'
}
}
const getCommissionResultType = (
result?: OrderCommissionResult
): 'success' | 'info' | 'warning' | 'danger' => {
switch (result) {
case 1:
return 'success'
case 2:
return 'info'
case 3:
return 'danger'
default:
return 'warning'
}
}
// 检查当前用户是否可以查看实付金额(代理账号和企业账号不能看到)
const canViewActualPaidAmount = computed(() => {
const userType = userStore.info.user_type
// 只有代理账号(3)和企业账号(4)不能看到实付金额
return userType !== 3 && userType !== 4
})
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => {
const baseColumns = [
{
prop: 'order_no',
label: '订单编号',
minWidth: 220,
showOverflowTooltip: true,
formatter: (row: Order) => {
return h(
'span',
{
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
onClick: (e: MouseEvent) => {
e.stopPropagation()
handleNameClick(row)
}
},
row.order_no
)
}
},
{
prop: 'buyer_phone',
label: '买家手机号',
width: 130,
formatter: (row: Order) => row.buyer_phone || '-'
},
{
prop: 'buyer_nickname',
label: '买家昵称',
width: 130,
formatter: (row: Order) => row.buyer_nickname || '-'
},
{
prop: 'order_type',
label: '订单类型',
width: 120,
formatter: (row: Order) => {
return h(ElTag, { type: row.order_type === 'single_card' ? 'primary' : 'success' }, () =>
getOrderTypeText(row.order_type)
)
}
},
{
prop: 'asset_type',
label: '资产类型',
width: 110,
formatter: (row: Order) =>
row.asset_type === 'card' ? 'IoT卡' : row.asset_type === 'device' ? '设备' : '-'
},
{
prop: 'buyer_type',
label: '买家类型',
width: 120,
formatter: (row: Order) => {
return h(ElTag, { type: row.buyer_type === 'personal' ? 'info' : 'warning' }, () =>
getBuyerTypeText(row.buyer_type)
)
}
},
{
prop: 'payment_status',
label: '支付状态',
width: 120,
formatter: (row: Order) => {
return h(
ElTag,
{ type: getPaymentStatusType(row.payment_status) },
() => row.payment_status_text
)
}
},
{
prop: 'is_expired',
label: '是否过期',
width: 100,
formatter: (row: Order) =>
row.is_expired === undefined ? '-' : row.is_expired ? '是' : '否'
},
{
prop: 'expires_at',
label: '超时时间',
width: 180,
formatter: (row: Order) => (row.expires_at ? formatDateTime(row.expires_at) : '-')
},
{
prop: 'asset_identifier',
label: '资产标识符',
width: 180,
showOverflowTooltip: true,
formatter: (row: Order) => row.asset_identifier || '-'
},
{
prop: 'purchase_role',
label: '订单渠道',
width: 140,
formatter: (row: Order) => {
if (!row.purchase_role) return '-'
const roleTypeMap: Record<string, 'success' | 'info' | 'warning' | 'danger'> = {
self_purchase: 'success',
purchased_by_parent: 'warning',
purchased_by_platform: 'danger',
purchase_for_subordinate: 'info'
}
return h(
ElTag,
{ type: roleTypeMap[row.purchase_role || ''] || 'info', size: 'small' },
() => getPurchaseRoleText(row.purchase_role || '')
)
}
},
{
prop: 'purchase_remark',
label: '购买备注',
minWidth: 180,
showOverflowTooltip: true,
formatter: (row: Order) => row.purchase_remark || '-'
},
{
prop: 'is_purchase_on_behalf',
label: '是否代购',
width: 100,
formatter: (row: Order) =>
row.is_purchase_on_behalf === undefined ? '-' : row.is_purchase_on_behalf ? '是' : '否'
},
{
prop: 'is_purchased_by_parent',
label: '是否上级代购',
width: 110,
formatter: (row: Order) =>
row.is_purchased_by_parent === undefined ? '-' : row.is_purchased_by_parent ? '是' : '否'
},
{
prop: 'created_at',
label: '下单时间',
width: 180,
formatter: (row: Order) => formatDateTime(row.created_at)
},
{
prop: 'operator_name',
label: '操作者',
width: 140,
formatter: (row: Order) => row.operator_name || '-'
},
{
prop: 'seller_shop_name',
label: '销售店铺',
width: 160,
showOverflowTooltip: true,
formatter: (row: Order) => row.seller_shop_name || '-'
},
{
prop: 'commission_status_name',
label: '佣金流程状态',
width: 140,
formatter: (row: Order) =>
h(ElTag, { type: getCommissionStatusType(row.commission_status), size: 'small' }, () =>
getCommissionStatusText(row.commission_status, row.commission_status_name)
)
},
{
prop: 'commission_result_name',
label: '佣金业务结果',
width: 140,
formatter: (row: Order) =>
h(ElTag, { type: getCommissionResultType(row.commission_result), size: 'small' }, () =>
getCommissionResultText(row.commission_result, row.commission_result_name)
)
},
{
prop: 'total_amount',
label: '订单金额',
width: 120,
formatter: (row: Order) => formatCurrency(row.total_amount)
}
]
// 只有非代理账号和非企业账号才能看到实付金额列
if (canViewActualPaidAmount.value) {
baseColumns.push({
prop: 'actual_paid_amount',
label: '实付金额',
width: 120,
formatter: (row: Order) => {
if (row.actual_paid_amount === undefined || row.actual_paid_amount === null) return '-'
return formatCurrency(row.actual_paid_amount)
}
})
}
baseColumns.push(
{
prop: 'payment_method',
label: '支付方式',
width: 120,
formatter: (row: Order) => getPaymentMethodText(row.payment_method)
},
{
prop: 'paid_at',
label: '支付时间',
width: 180,
formatter: (row: Order) => (row.paid_at ? formatDateTime(row.paid_at) : '-')
}
)
return baseColumns
})
// 当订单类型切换时,清空已选择的套餐
watch(
() => createForm.order_type,
() => {
createForm.package_id = undefined
packageOptions.value = []
}
)
// 当套餐切换时,如果选择的是赠送套餐,自动切换到线下支付
watch(
() => createForm.package_id,
(packageId) => {
if (packageId) {
const selectedPackage = packageOptions.value.find((pkg) => pkg.id === packageId)
if (selectedPackage?.is_gift) {
createForm.payment_method = 'offline'
}
}
}
)
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,
buyer_phone: searchForm.buyer_phone || undefined,
payment_status: searchForm.payment_status,
payment_method: searchForm.payment_method,
order_type: searchForm.order_type,
purchase_role: searchForm.purchase_role,
identifier: searchForm.identifier || undefined,
is_expired:
searchForm.is_expired === undefined ? undefined : Boolean(searchForm.is_expired),
seller_shop_id: searchForm.seller_shop_id,
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.items || []
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 exportQuery = computed(() => {
const query: Record<string, unknown> = {
order_no: searchForm.order_no || undefined,
buyer_phone: searchForm.buyer_phone || undefined,
payment_status: searchForm.payment_status,
payment_method: searchForm.payment_method,
order_type: searchForm.order_type,
purchase_role: searchForm.purchase_role,
identifier: searchForm.identifier || undefined,
is_expired: searchForm.is_expired === undefined ? undefined : Boolean(searchForm.is_expired),
seller_shop_id: searchForm.seller_shop_id,
start_time: searchForm.start_time || undefined,
end_time: searchForm.end_time || undefined
}
Object.keys(query).forEach((key) => {
if (query[key] === undefined || query[key] === null || query[key] === '') {
delete query[key]
}
})
return query
})
// 刷新表格
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卡和设备列表在用户搜索时才加载不在此处预加载
}
// 加载默认套餐列表可选按series_id筛选
const loadDefaultPackages = async (seriesId?: number) => {
packageSearchLoading.value = true
isLoadingPackagesFromSelection.value = true
packagesEmptyWithSeriesId.value = false
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 || []
// seriesId存在但返回空列表时显示提示
if (seriesId && (!res.data.items || res.data.items.length === 0)) {
packagesEmptyWithSeriesId.value = true
}
}
} catch (error) {
console.error('Load default packages failed:', error)
packageOptions.value = []
} finally {
packageSearchLoading.value = false
isLoadingPackagesFromSelection.value = false
}
}
// 对话框关闭后的清理
const handleCreateDialogClosed = () => {
// 重置表单(会同时清除验证状态)
createFormRef.value?.resetFields()
// 重置表单数据到初始值
createForm.order_type = 'single_card'
createForm.package_id = undefined
createForm.iot_card_id = null
createForm.device_id = null
createForm.payment_method = 'wallet'
createForm.payment_voucher_key = []
voucherUploading.value = false
// 清空上传文件列表
uploadRef.value?.clearFiles(false)
// 清空套餐、IoT卡和设备搜索结果
packageOptions.value = []
iotCardOptions.value = []
deviceOptions.value = []
}
// 创建订单
const handleCreateOrder = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (voucherUploading.value) {
ElMessage.warning('支付凭证上传中,请稍候')
return
}
await formEl.validate(async (valid) => {
if (valid) {
createLoading.value = true
try {
// 获取 identifier (ICCID 或 VirtualNo)
let identifier = ''
if (createForm.order_type === 'single_card') {
if (!createForm.iot_card_id) {
ElMessage.error('请选择IoT卡')
return
}
const selectedCard = iotCardOptions.value.find(
(card) => card.id === createForm.iot_card_id
)
if (!selectedCard?.iccid) {
ElMessage.error('未找到选中的卡信息')
return
}
identifier = selectedCard.iccid
} else {
if (!createForm.device_id) {
ElMessage.error('请选择设备')
return
}
const selectedDevice = deviceOptions.value.find(
(dev) => dev.id === createForm.device_id
)
if (!selectedDevice?.virtual_no) {
ElMessage.error('未找到选中的设备信息')
return
}
identifier = selectedDevice.virtual_no
}
// 验证必填字段
if (!createForm.package_id) {
ElMessage.error('请选择套餐')
return
}
// 线下支付时,支付凭证为必填
if (
createForm.payment_method === 'offline' &&
!hasVoucherKeys(createForm.payment_voucher_key)
) {
ElMessage.error('线下支付必须上传支付凭证')
return
}
// 创建订单(后端接收数组,前端单选转为单元素数组)
const data: CreateOrderRequest = {
identifier,
package_ids: [createForm.package_id],
payment_method: createForm.payment_method
}
// 线下支付时,添加支付凭证附件列表
if (
createForm.payment_method === 'offline' &&
hasVoucherKeys(createForm.payment_voucher_key)
) {
data.payment_voucher_key = toVoucherKeyList(createForm.payment_voucher_key)
}
const res = await OrderService.createOrder(data)
if (res.code !== 0) return
// 根据支付方式显示不同的成功消息
if (createForm.payment_method === 'wallet') {
ElMessage.success('订单创建成功,已自动完成支付')
} else {
ElMessage.success('订单创建成功')
}
createDialogVisible.value = false
formEl.resetFields()
await getTableData()
} catch (error: any) {
// 用户取消确认对话框
if (error === 'cancel') {
return
}
console.error(error)
} finally {
createLoading.value = false
}
}
})
}
// 查看订单详情
const showOrderDetail = (row: Order) => {
router.push({
path: `${RoutesAlias.OrderList}/detail/${row.id}`
})
}
// 处理名称点击
const handleNameClick = (row: Order) => {
if (hasAuth('orders:view_detail')) {
showOrderDetail(row)
} else {
ElMessage.warning('您没有查看详情的权限')
}
}
// 取消订单
const handleCancelOrder = (row: Order) => {
// 已支付的订单不能取消
if (row.payment_status === 2) {
ElMessage.warning('已支付的订单无法取消')
return
}
ElMessageBox.confirm('确定要取消该订单吗?', '取消订单', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
await OrderService.cancelOrder(row.id)
ElMessage.success('订单已取消')
await getTableData()
} catch (error) {
console.error(error)
}
})
.catch(() => {
// 用户取消操作
})
}
// 获取操作按钮
const getActions = (row: Order) => {
const actions: any[] = []
if (
[1, 2].includes(userStore.getUserInfo.user_type || 0) &&
hasAuth(AUDIT_PERMISSIONS.orderEntry)
) {
const auditTarget = resolveAuditResourceTarget({
userType: userStore.getUserInfo.user_type,
resourceType: 'order',
internalId: row.id
})
if (auditTarget)
actions.push({
label: '审计记录',
handler: () => openAuditInvestigation(auditTarget),
type: 'primary'
})
if (hasAuth(AUDIT_PERMISSIONS.orderFinanceEntry)) {
const financeTarget = resolveFinanceAuditTarget('order_id', row.id)
if (financeTarget)
actions.push({
label: '资金链路',
handler: () => openAuditInvestigation(financeTarget),
type: 'primary'
})
}
}
// 线下支付且有凭证可以查看
if (
row.payment_method === 'offline' &&
hasVoucherKeys(row.payment_voucher_key) &&
hasAuth('orders:view_payment_voucher')
) {
actions.push({
label: '查看支付凭证',
handler: () => handleViewPaymentVoucher(row),
type: 'primary'
})
}
// 只有待支付的订单可以删除
if (row.payment_status === 1 && hasAuth('orders:delete')) {
actions.push({
label: '删除',
handler: () => handleCancelOrder(row),
type: 'danger'
})
}
return actions
}
// 查看支付凭证
const handleViewPaymentVoucher = (row: Order) => {
if (!hasVoucherKeys(row.payment_voucher_key)) return
paymentVoucherFileKeys.value = toVoucherKeyList(row.payment_voucher_key)
}
</script>
<style scoped lang="scss">
.order-list-page {
height: 100%;
}
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>