feat: order-export,generation,status
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m45s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m45s
This commit is contained in:
@@ -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` - 格式化函数
|
||||
@@ -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 "已停用"
|
||||
@@ -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 验证格式化函数正确处理各状态值
|
||||
24
openspec/changes/add-order-export-scene/proposal.md
Normal file
24
openspec/changes/add-order-export-scene/proposal.md
Normal 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`
|
||||
@@ -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
|
||||
33
openspec/changes/add-order-export-scene/tasks.md
Normal file
33
openspec/changes/add-order-export-scene/tasks.md
Normal 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 验证无权限时导出按钮不显示
|
||||
@@ -76,7 +76,8 @@
|
||||
const sceneName = computed(() => {
|
||||
const sceneMap: Record<ExportTaskScene, string> = {
|
||||
device: '设备管理',
|
||||
iot_card: 'IoT卡管理'
|
||||
iot_card: 'IoT卡管理',
|
||||
order: '订单管理'
|
||||
}
|
||||
|
||||
return sceneMap[props.scene]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 // 批次号
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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: '激活状态',
|
||||
|
||||
@@ -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] : '-'
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
|
||||
const sceneOptions = [
|
||||
{ label: '设备管理', value: 'device' },
|
||||
{ label: 'IoT卡管理', value: 'iot_card' }
|
||||
{ label: 'IoT卡管理', value: 'iot_card' },
|
||||
{ label: '订单管理', value: 'order' }
|
||||
]
|
||||
|
||||
const statusOptions = [
|
||||
|
||||
@@ -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: '激活状态',
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user