feat: order-export,generation,status
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m45s

This commit is contained in:
luo
2026-06-17 17:46:04 +08:00
parent 2a8f4e40d6
commit 85d158dfec
15 changed files with 300 additions and 8 deletions

View File

@@ -0,0 +1,20 @@
# Change: 新增资产业务状态和世代编号字段
## Why
后端已在 IoT 卡和设备的资产详情接口中新增 `asset_status`(业务状态)和 `generation`(资产世代编号)字段,前端需要展示这些字段供运营人员查看。
## What Changes
- 资产信息详情新增 `asset_status``asset_status_name` 字段展示业务状态1:在库, 2:已销售, 3:已换货, 4:已停用)
- 资产信息详情新增 `generation` 字段展示资产世代编号初始值1每次换货转新后+1
- 相关格式化函数更新以支持新字段
## Impact
- Affected specs:
- `asset-information`
- Affected code:
- `src/views/asset-management/asset-information/types.ts` - AssetInfo 接口
- `src/views/asset-management/asset-information/components/BasicInfoCard.vue` - 详情展示
- `src/views/asset-management/asset-information/composables/useAssetFormatters.ts` - 格式化函数

View File

@@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: Asset Status and Generation Display
The admin frontend SHALL display `asset_status`, `asset_status_name`, and `generation` fields in the asset information detail page for both IoT cards and devices.
#### Scenario: Display IoT card asset status and generation
- **GIVEN** 用户打开 IoT 卡资产信息详情页
- **WHEN** 后端返回 `asset_status``asset_status_name``generation` 字段
- **THEN** 页面 MUST display the asset status with appropriate tag type
- **AND** 页面 MUST display the generation number
#### Scenario: Display device asset status and generation
- **GIVEN** 用户打开设备资产信息详情页
- **WHEN** 后端返回 `asset_status``asset_status_name``generation` 字段
- **THEN** 页面 MUST display the asset status with appropriate tag type
- **AND** 页面 MUST display the generation number
### Requirement: Asset Status Formatting
The admin frontend SHALL format asset status values with appropriate labels and tag types.
#### Scenario: Map asset status to display name
- **GIVEN** `asset_status` value is 1
- **THEN** display name MUST be "在库"
- **WHEN** `asset_status` value is 2
- **THEN** display name MUST be "已销售"
- **WHEN** `asset_status` value is 3
- **THEN** display name MUST be "已换货"
- **WHEN** `asset_status` value is 4
- **THEN** display name MUST be "已停用"

View File

@@ -0,0 +1,28 @@
## 1. Proposal Review
- [ ] 1.1 确认 `asset_status` 字段含义1:在库, 2:已销售, 3:已换货, 4:已停用
- [ ] 1.2 确认 `generation` 字段含义资产世代编号初始值1每次换货转新后+1
## 2. Type Updates
- [ ] 2.1 在 `AssetInfo` 接口中添加 `asset_status?: number`
- [ ] 2.2 在 `AssetInfo` 接口中添加 `asset_status_name?: string`
- [ ] 2.3 在 `AssetInfo` 接口中添加 `generation?: number`
## 3. Formatter Functions
- [ ] 3.1 在 `useAssetFormatters` 中添加 `getAssetStatusName` 函数(如果不存在)
- [ ] 3.2 在 `useAssetFormatters` 中添加 `getAssetStatusType` 函数(如果不存在)
## 4. BasicInfoCard Component
- [ ] 4.1 在卡信息区域添加「业务状态」展示行
- [ ] 4.2 在卡信息区域添加「资产世代」展示行
- [ ] 4.3 在设备信息区域添加「业务状态」展示行
- [ ] 4.4 在设备信息区域添加「资产世代」展示行
## 5. Verification
- [ ] 5.1 验证 IoT 卡详情页正确显示业务状态和世代编号
- [ ] 5.2 验证设备详情页正确显示业务状态和世代编号
- [ ] 5.3 验证格式化函数正确处理各状态值

View File

@@ -0,0 +1,24 @@
# Change: 新增订单导出场景
## Why
导出管理现已支持设备管理和IoT卡管理两个导出场景需要扩展至订单管理以便订单列表也能按当前检索条件全量导出。
## What Changes
- 在导出任务中新增 `order` 导出场景支持
- 导出管理列表的场景筛选中新增「订单管理」选项
- 订单列表页面新增导出按钮,创建 `scene=order` 的导出任务
- 订单导出按钮需要权限编码控制
## Impact
- Affected specs:
- `export-task-management`
- Affected code:
- `src/types/api/exportTask.ts` - ExportTaskScene 类型
- `src/views/asset-management/export-task-management/export-task-list/index.vue` - 场景选项
- `src/components/business/ExportTaskCreateDialog.vue` - sceneName 映射
- `src/views/order-management/order-list/index.vue` - 导出按钮
- Dependencies:
- 依赖后端 export task API 支持 `scene=order`

View File

@@ -0,0 +1,35 @@
## ADDED Requirements
### Requirement: Order Export Scene Support
The system SHALL support `scene="order"` for creating export tasks from the order management list.
#### Scenario: Create order export task from order management
- **GIVEN** 用户在订单管理页设置了列表检索条件
- **AND** 当前用户具备订单导出权限
- **WHEN** 用户确认创建导出任务
- **THEN** 系统 MUST submit `scene="order"`
- **AND** 系统 MUST submit the current order list filter conditions as `query`
### Requirement: Order Export in Export Task List
The admin frontend SHALL display "订单管理" as a selectable scene option when filtering export tasks.
#### Scenario: Filter export tasks by order scene
- **GIVEN** 用户打开导出列表页面
- **WHEN** 用户选择「订单管理」作为场景筛选条件
- **THEN** 系统 MUST call `GET /api/admin/export-tasks` with `scene=order`
## MODIFIED Requirements
### Requirement: Reusable Export Task Creation Dialog
The admin frontend already provides a reusable dialog for creating export tasks. This requirement adds the order scene to the scene name mapping.
#### Scenario: Display order scene name in dialog
- **GIVEN** 导出弹窗通过 `scene="order"` 打开
- **WHEN** 弹窗渲染
- **THEN** 弹窗 MUST display "订单管理" as the scene name

View File

@@ -0,0 +1,33 @@
## 1. Proposal Review
- [ ] 1.1 确认订单导出权限编码(如 `orders:export`
- [ ] 1.2 确认导出任务 scene 值使用 `order`
## 2. Type Updates
- [ ] 2.1 在 `ExportTaskScene` 类型中添加 `order` 选项
## 3. Export Task List Page
- [ ] 3.1 在导出列表的场景筛选选项中添加「订单管理」
## 4. Export Dialog Component
- [ ] 4.1 在 `ExportTaskCreateDialog``sceneName` 映射中添加 `order` → '订单管理'
## 5. Order List Page
- [ ] 5.1 在订单列表页添加导出按钮
- [ ] 5.2 使用 `ExportTaskCreateDialog`,传入 `scene="order"` 和当前筛选条件
- [ ] 5.3 添加权限控制(按钮需检查 `orders:export` 权限)
## 6. Permissions And UX
- [ ] 6.1 导出按钮在无权限时隐藏
- [ ] 6.2 创建导出任务成功后提示
## 7. Verification
- [ ] 7.1 验证订单列表页可按当前筛选条件创建 `order` 导出任务
- [ ] 7.2 验证导出管理列表可按场景筛选 `order` 任务
- [ ] 7.3 验证无权限时导出按钮不显示

View File

@@ -76,7 +76,8 @@
const sceneName = computed(() => {
const sceneMap: Record<ExportTaskScene, string> = {
device: '设备管理',
iot_card: 'IoT卡管理'
iot_card: 'IoT卡管理',
order: '订单管理'
}
return sceneMap[props.scene]

View File

@@ -63,6 +63,14 @@ export interface SimCardProduct {
updateTime?: string
}
// 网卡业务状态枚举
export enum AssetStatus {
IN_STOCK = 1, // 在库
SOLD = 2, // 已销售
REPLACED = 3, // 已换货
DISABLED = 4 // 已停用
}
// 网卡实体
export interface Card {
id: string | number
@@ -73,6 +81,9 @@ export interface Card {
networkType: NetworkType
cardType: CardType
status: CardStatus
generation?: number // 资产世代编号初始值1每次换货转新后+1
asset_status?: AssetStatus // 业务状态 (1:在库, 2:已销售, 3:已换货, 4:已停用)
asset_status_name?: string // 业务状态名称(中文)
// 关联信息
productId?: string | number // 关联商品ID
productName?: string
@@ -369,6 +380,9 @@ export interface StandaloneIotCard {
activation_status: number // 激活状态 (0:未激活, 1:已激活)
network_status: number // 网络状态 (0:停机, 1:开机)
real_name_status: number // 实名状态 (0:未实名, 1:已实名)
generation?: number // 资产世代编号初始值1每次换货转新后+1
asset_status?: AssetStatus // 业务状态 (1:在库, 2:已销售, 3:已换货, 4:已停用)
asset_status_name?: string // 业务状态名称(中文)
batch_no?: string // 批次号 (可选)
supplier?: string // 供应商 (可选)
shop_id?: number | null // 店铺ID (可选)
@@ -515,6 +529,9 @@ export interface IotCardDetailResponse {
activation_status: number // 激活状态 (0:未激活, 1:已激活)
network_status: number // 网络状态 (0:停机, 1:开机)
real_name_status: number // 实名状态 (0:未实名, 1:已实名)
generation?: number // 资产世代编号初始值1每次换货转新后+1
asset_status?: AssetStatus // 业务状态 (1:在库, 2:已销售, 3:已换货, 4:已停用)
asset_status_name?: string // 业务状态名称(中文)
batch_no: string // 批次号
supplier: string // 供应商
shop_id: number | null // 店铺ID

View File

@@ -14,6 +14,14 @@ export enum DeviceStatus {
DEACTIVATED = 4 // 已停用
}
// 设备业务状态枚举
export enum DeviceAssetStatus {
IN_STOCK = 1, // 在库
SOLD = 2, // 已销售
REPLACED = 3, // 已换货
DISABLED = 4 // 已停用
}
// 设备在线状态枚举
export enum DeviceOnlineStatus {
UNKNOWN = 0, // 未知
@@ -38,6 +46,9 @@ export interface Device {
bound_card_count: number // 已绑定卡数量
status: DeviceStatus // 状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)
status_name: string // 状态名称
generation?: number // 资产世代编号初始值1每次换货转新后+1
asset_status?: DeviceAssetStatus // 业务状态 (1:在库, 2:已销售, 3:已换货, 4:已停用)
asset_status_name?: string // 业务状态名称(中文)
shop_id: number | null // 店铺ID
shop_name: string // 店铺名称
batch_no: string // 批次号

View File

@@ -1,6 +1,6 @@
import type { BaseResponse, PaginationParams } from './common'
export type ExportTaskScene = 'device' | 'iot_card'
export type ExportTaskScene = 'device' | 'iot_card' | 'order'
export type ExportTaskFormat = 'xlsx' | 'csv'

View File

@@ -1219,6 +1219,8 @@
{ label: '设备类型', prop: 'device_type' },
{ label: '状态', prop: 'status' },
{ label: '状态名称', prop: 'status_name' },
{ label: '资产世代', prop: 'generation' },
{ label: '业务状态', prop: 'asset_status' },
{ label: '激活状态', prop: 'activation_status_name' },
{ label: '在线状态', prop: 'online_status' },
{ label: '实名策略', prop: 'realname_policy' },
@@ -1527,6 +1529,28 @@
width: 100,
formatter: (row: Device) => row.status_name || '-'
},
{
prop: 'generation',
label: '资产世代',
width: 100,
formatter: (row: Device) => row.generation ?? '-'
},
{
prop: 'asset_status',
label: '业务状态',
width: 100,
formatter: (row: Device) => {
const assetStatusMap: Record<number, { text: string; type: any }> = {
1: { text: '在库', type: 'info' },
2: { text: '已销售', type: 'success' },
3: { text: '已换货', type: 'warning' },
4: { text: '已停用', type: 'danger' }
}
if (row.asset_status == null) return '-'
const status = assetStatusMap[row.asset_status] || { text: '未知', type: 'info' }
return h(ElTag, { type: status.type }, () => status.text)
}
},
{
prop: 'activation_status_name',
label: '激活状态',

View File

@@ -67,7 +67,8 @@
const getSceneName = (scene?: ExportTaskScene) => {
const sceneMap: Record<ExportTaskScene, string> = {
device: '设备管理',
iot_card: 'IoT卡管理'
iot_card: 'IoT卡管理',
order: '订单管理'
}
return scene ? sceneMap[scene] : '-'

View File

@@ -74,7 +74,8 @@
const sceneOptions = [
{ label: '设备管理', value: 'device' },
{ label: 'IoT卡管理', value: 'iot_card' }
{ label: 'IoT卡管理', value: 'iot_card' },
{ label: '订单管理', value: 'order' }
]
const statusOptions = [

View File

@@ -1363,6 +1363,8 @@
{ label: '运营商', prop: 'carrier_name' },
{ label: '卡业务类型', prop: 'card_category' },
{ label: '状态', prop: 'status' },
{ label: '资产世代', prop: 'generation' },
{ label: '业务状态', prop: 'asset_status' },
{ label: '激活状态', prop: 'activation_status' },
{ label: '激活时间', prop: 'activated_at' },
{ label: '网络状态', prop: 'network_status' },
@@ -1550,6 +1552,28 @@
return h(ElTag, { type: getStatusType(row.status) }, () => getStatusText(row.status))
}
},
{
prop: 'generation',
label: '资产世代',
width: 100,
formatter: (row: StandaloneIotCard) => row.generation ?? '-'
},
{
prop: 'asset_status',
label: '业务状态',
width: 100,
formatter: (row: StandaloneIotCard) => {
const assetStatusMap: Record<number, { text: string; type: any }> = {
1: { text: '在库', type: 'info' },
2: { text: '已销售', type: 'success' },
3: { text: '已换货', type: 'warning' },
4: { text: '已停用', type: 'danger' }
}
if (row.asset_status == null) return '-'
const status = assetStatusMap[row.asset_status] || { text: '未知', type: 'info' }
return h(ElTag, { type: status.type }, () => status.text)
}
},
{
prop: 'activation_status',
label: '激活状态',

View File

@@ -22,6 +22,9 @@
<ElButton @click="showCreateDialog" v-permission="'orders:add'">{{
'创建订单'
}}</ElButton>
<ElButton v-if="hasAuth('orders:export')" @click="exportDialogVisible = true">{{
'导出'
}}</ElButton>
</template>
</ArtTableHeader>
@@ -152,17 +155,17 @@
</ElOption>
</ElSelect>
<template v-if="noSeriesId">
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-warning)">
<div style="margin-top: 4px; font-size: 12px; color: var(--el-text-color-warning)">
该设备未关联套餐系列无法购买套餐
</div>
</template>
<template v-else-if="packagesEmptyWithSeriesId">
<div style=" margin-top: 4px; font-size: 12px;color: var(--el-text-color-info)">
<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 style="margin-top: 4px; font-size: 12px; color: var(--el-color-warning)">
提示: 赠送套餐只能使用线下支付方式且必须上传支付凭证
</div>
</template>
@@ -237,13 +240,22 @@
:file-key="paymentVoucherFileKey"
@close="paymentVoucherFileKey = ''"
/>
<!-- 导出任务对话框 -->
<ExportTaskCreateDialog
v-model="exportDialogVisible"
scene="order"
:query="exportQuery"
confirm-permission="orders:export"
title="导出订单"
/>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h, nextTick } from 'vue'
import { computed, h, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import {
OrderService,
@@ -277,6 +289,7 @@
import { formatDateTime } from '@/utils/business/format'
import { RoutesAlias } from '@/router/routesAlias'
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
import ExportTaskCreateDialog from '@/components/business/ExportTaskCreateDialog.vue'
defineOptions({ name: 'OrderList' })
@@ -293,6 +306,7 @@
const voucherUploading = ref(false)
const tableRef = ref()
const createDialogVisible = ref(false)
const exportDialogVisible = ref(false)
const paymentVoucherFileKey = ref('')
let activeVoucherUploadId = 0
@@ -1130,6 +1144,31 @@
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()