Compare commits
12 Commits
889ff3bc28
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8229ad9248 | ||
|
|
fc01fa2ac9 | ||
|
|
65cc63674e | ||
|
|
01cddd7eda | ||
|
|
a7b006076a | ||
|
|
c36c59e268 | ||
|
|
e56951d4b7 | ||
|
|
b5c58d695f | ||
|
|
6f6ac83b71 | ||
|
|
48e4adf3ff | ||
|
|
d335984c20 | ||
|
|
89e6d19d1f |
@@ -3,7 +3,7 @@ name: 构建并部署前端到测试环境
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- main
|
||||||
- dev
|
- dev
|
||||||
- test
|
- test
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: 设置镜像标签
|
- name: 设置镜像标签
|
||||||
id: tag
|
id: tag
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.ref }}" = "refs/heads/develop" ]; then
|
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||||
echo "tag=latest" >> $GITHUB_OUTPUT
|
echo "tag=latest" >> $GITHUB_OUTPUT
|
||||||
elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then
|
elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then
|
||||||
echo "tag=dev" >> $GITHUB_OUTPUT
|
echo "tag=dev" >> $GITHUB_OUTPUT
|
||||||
@@ -51,8 +51,8 @@ jobs:
|
|||||||
docker push ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}
|
docker push ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}
|
||||||
docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}
|
docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||||
|
|
||||||
- name: 部署到本地(仅 develop 分支)
|
- name: 部署到本地(仅 main 分支)
|
||||||
if: github.ref == 'refs/heads/develop'
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
# 确保部署目录存在
|
# 确保部署目录存在
|
||||||
mkdir -p ${{ env.DEPLOY_DIR }}
|
mkdir -p ${{ env.DEPLOY_DIR }}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Change: Add purchased package names to the order list
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Order list records already include their purchased line items, but operators cannot identify the purchased packages without opening the order detail. Showing these names next to the order number makes multi-package orders immediately understandable.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Place the existing `资产标识符` column immediately after `订单编号`, followed by a `购买套餐名称` column in the order list.
|
||||||
|
- Render `items[].package_name` in the API-returned order, joining multiple item names with the Chinese enumeration separator `、`.
|
||||||
|
- Show a placeholder when an order has no returned items or no usable package name.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `order-list-purchased-package-names`
|
||||||
|
- Affected code: `src/views/order-management/order-list/index.vue`
|
||||||
|
- Dependencies: `GET /api/admin/orders` returns `items` with each item's `package_name`.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Order list purchased package name display
|
||||||
|
|
||||||
|
The order list SHALL display `资产标识符` immediately after `订单编号`, followed by a `购买套餐名称` column. The package-name column MUST render package names from the current row's `items` in their API-returned order and MUST join multiple names with `、`.
|
||||||
|
|
||||||
|
#### Scenario: Order contains multiple purchased packages
|
||||||
|
|
||||||
|
- **GIVEN** an order response whose `items` contains package names `正式套餐` and `加油包33`
|
||||||
|
- **WHEN** the order is rendered in the order list
|
||||||
|
- **THEN** `资产标识符` MUST be positioned between `订单编号` and `购买套餐名称`
|
||||||
|
- **AND** the `购买套餐名称` column MUST display `正式套餐、加油包33`
|
||||||
|
- **AND** the order-number column MUST retain its existing navigation behavior
|
||||||
|
|
||||||
|
#### Scenario: Order has no available package names
|
||||||
|
|
||||||
|
- **GIVEN** an order response with `items=null`, an empty item list, or item records without usable names
|
||||||
|
- **WHEN** the order is rendered in the order list
|
||||||
|
- **THEN** the `购买套餐名称` column MUST display the standard placeholder
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 Place `资产标识符` after order number and add the purchased-package-name column immediately after it in the order-list column chooser.
|
||||||
|
- [x] 1.2 Render package names from `Order.items` in API-returned order, separating multiple values with `、`.
|
||||||
|
- [x] 1.3 Render a placeholder for null, empty, or unnamed order items without altering the order-number link behavior.
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 Verify a single item renders its package name and multiple items render `套餐A、套餐B`.
|
||||||
|
- [x] 2.2 Run targeted format, type, and lint checks.
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="手动更新卡实名状态"
|
title="手动更新卡实名状态"
|
||||||
width="30%"
|
width="30%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
<ElForm ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="设置实名认证策略"
|
title="设置实名认证策略"
|
||||||
width="40%"
|
width="40%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||||
<ElFormItem label="资产标识">
|
<ElFormItem label="资产标识">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="切换SIM卡"
|
title="切换SIM卡"
|
||||||
width="40%"
|
width="40%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<div v-if="loading" style="padding: 40px; text-align: center">
|
<div v-if="loading" style="padding: 40px; text-align: center">
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ export interface AssetBoundCard {
|
|||||||
card_id: number // 卡 ID
|
card_id: number // 卡 ID
|
||||||
iccid: string // ICCID
|
iccid: string // ICCID
|
||||||
msisdn: string // 手机号
|
msisdn: string // 手机号
|
||||||
|
carrier_type?: string // 运营商类型
|
||||||
|
carrier_name?: string // 运营商名称
|
||||||
network_status: NetworkStatus // 网络状态
|
network_status: NetworkStatus // 网络状态
|
||||||
real_name_status: RealNameStatus // 实名状态
|
real_name_status: RealNameStatus // 实名状态
|
||||||
real_name_at?: string | null // 实名时间
|
real_name_at?: string | null // 实名时间
|
||||||
|
|||||||
@@ -10,6 +10,19 @@ export enum RefundStatus {
|
|||||||
RETURNED = 4 // 已退回
|
RETURNED = 4 // 已退回
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 审批状态
|
||||||
|
export enum RefundApprovalStatus {
|
||||||
|
SUBMITTING = 0, // 提交中
|
||||||
|
APPROVING = 1, // 审批中
|
||||||
|
APPROVED = 2, // 已通过
|
||||||
|
REJECTED = 3, // 已拒绝
|
||||||
|
REVOKED = 4, // 已撤销
|
||||||
|
REVOKED_AFTER_APPROVED = 5, // 通过后撤销
|
||||||
|
DELETED = 6, // 已删除
|
||||||
|
SUBMIT_FAILED = 7, // 提交失败
|
||||||
|
RESULT_UNKNOWN = 8 // 提交结果未知
|
||||||
|
}
|
||||||
|
|
||||||
export interface RefundAttachment {
|
export interface RefundAttachment {
|
||||||
file_key: string
|
file_key: string
|
||||||
file_name?: string
|
file_name?: string
|
||||||
@@ -70,7 +83,7 @@ export interface Refund {
|
|||||||
approval_provider?: 'wecom' | null
|
approval_provider?: 'wecom' | null
|
||||||
approval_instance_id?: number | null
|
approval_instance_id?: number | null
|
||||||
approval_source?: 'none' | 'legacy' | 'wecom' | null // 审批来源
|
approval_source?: 'none' | 'legacy' | 'wecom' | null // 审批来源
|
||||||
approval_status?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | null // 审批状态
|
approval_status?: RefundApprovalStatus | null // 审批状态
|
||||||
approval_status_name?: string | null // 审批状态名称
|
approval_status_name?: string | null // 审批状态名称
|
||||||
current_approver_summary?: string | null // 当前审批人摘要
|
current_approver_summary?: string | null // 当前审批人摘要
|
||||||
processing_status?: string | null // 业务处理状态
|
processing_status?: string | null // 业务处理状态
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { normalizeApiError } from '@/utils/business/apiError'
|
|||||||
import { syncCompatibleIdFields } from '@/utils/business/id'
|
import { syncCompatibleIdFields } from '@/utils/business/id'
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
timeout: 15000, // 请求超时时间(毫秒)
|
timeout: 90000, // 请求超时时间(毫秒)
|
||||||
baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理,其他模式使用完整URL
|
baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理,其他模式使用完整URL
|
||||||
withCredentials: false, // 异步请求携带cookie
|
withCredentials: false, // 异步请求携带cookie
|
||||||
transformRequest: [
|
transformRequest: [
|
||||||
|
|||||||
@@ -118,13 +118,13 @@
|
|||||||
label="实名时间"
|
label="实名时间"
|
||||||
label-class-name="asset-label asset-label--realname-time"
|
label-class-name="asset-label asset-label--realname-time"
|
||||||
>{{
|
>{{
|
||||||
cardInfo?.real_name_at ? formatDateTime(cardInfo.real_name_at) : '-'
|
cardInfo?.real_name_at ? formatAssetInfoTime(cardInfo.real_name_at) : '-'
|
||||||
}}</ElDescriptionsItem
|
}}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="实名同步时间"
|
label="实名同步时间"
|
||||||
label-class-name="asset-label asset-label--realname-sync"
|
label-class-name="asset-label asset-label--realname-sync"
|
||||||
>{{ formatDateTime(cardInfo?.last_real_name_check_at) || '-' }}</ElDescriptionsItem
|
>{{ formatAssetInfoTime(cardInfo?.last_real_name_check_at) }}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
|
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
@@ -143,12 +143,12 @@
|
|||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="卡状态同步时间"
|
label="卡状态同步时间"
|
||||||
label-class-name="asset-label asset-label--card-sync"
|
label-class-name="asset-label asset-label--card-sync"
|
||||||
>{{ formatDateTime(cardInfo?.last_card_status_check_at) || '-' }}</ElDescriptionsItem
|
>{{ formatAssetInfoTime(cardInfo?.last_card_status_check_at) }}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="流量同步时间"
|
label="流量同步时间"
|
||||||
label-class-name="asset-label asset-label--traffic-sync"
|
label-class-name="asset-label asset-label--traffic-sync"
|
||||||
>{{ formatDateTime(cardInfo?.last_data_check_at) || '-' }}</ElDescriptionsItem
|
>{{ formatAssetInfoTime(cardInfo?.last_data_check_at) }}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
|
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
@@ -241,11 +241,10 @@
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="激活状态"
|
label="虚拟号"
|
||||||
label-class-name="asset-label asset-label--activation-status"
|
label-class-name="asset-label asset-label--virtual-number"
|
||||||
|
>{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
{{ cardInfo?.activation_status_name || '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="设备型号"
|
label="设备型号"
|
||||||
@@ -257,7 +256,7 @@
|
|||||||
label-class-name="asset-label asset-label--realname-time"
|
label-class-name="asset-label asset-label--realname-time"
|
||||||
>{{
|
>{{
|
||||||
selectedDeviceStatusCard?.real_name_at
|
selectedDeviceStatusCard?.real_name_at
|
||||||
? formatDateTime(selectedDeviceStatusCard.real_name_at)
|
? formatAssetInfoTime(selectedDeviceStatusCard.real_name_at)
|
||||||
: '-'
|
: '-'
|
||||||
}}</ElDescriptionsItem
|
}}</ElDescriptionsItem
|
||||||
>
|
>
|
||||||
@@ -275,10 +274,11 @@
|
|||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="虚拟号"
|
label="激活状态"
|
||||||
label-class-name="asset-label asset-label--virtual-number"
|
label-class-name="asset-label asset-label--activation-status"
|
||||||
>{{ cardInfo?.virtual_no || '-' }}</ElDescriptionsItem
|
|
||||||
>
|
>
|
||||||
|
{{ cardInfo?.activation_status_name || '-' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem
|
<ElDescriptionsItem
|
||||||
label="切卡模式"
|
label="切卡模式"
|
||||||
label-class-name="asset-label asset-label--switch-mode"
|
label-class-name="asset-label asset-label--switch-mode"
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="binding-card-field">
|
<div class="binding-card-field">
|
||||||
<span class="binding-card-field-label">运营商</span>
|
<span class="binding-card-field-label">运营商</span>
|
||||||
<span class="binding-card-field-value">{{ bindingCard.carrier_name || '-' }}</span>
|
<span class="binding-card-field-value">{{ getCarrierAccountDisplay(bindingCard) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="binding-card-field">
|
<div class="binding-card-field">
|
||||||
<span class="binding-card-field-label">卡槽位置</span>
|
<span class="binding-card-field-label">卡槽位置</span>
|
||||||
@@ -467,7 +467,11 @@
|
|||||||
<div class="binding-card-field">
|
<div class="binding-card-field">
|
||||||
<span class="binding-card-field-label">实名时间</span>
|
<span class="binding-card-field-label">实名时间</span>
|
||||||
<span class="binding-card-field-value">
|
<span class="binding-card-field-value">
|
||||||
{{ bindingCard.real_name_at ? formatDateTime(bindingCard.real_name_at) : '-' }}
|
{{
|
||||||
|
bindingCard.real_name_at
|
||||||
|
? formatAssetInfoTime(bindingCard.real_name_at)
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="binding-card-field">
|
<div class="binding-card-field">
|
||||||
@@ -679,6 +683,7 @@
|
|||||||
card_id?: number
|
card_id?: number
|
||||||
iccid: string
|
iccid: string
|
||||||
msisdn?: string
|
msisdn?: string
|
||||||
|
carrier_type?: string
|
||||||
carrier_name?: string
|
carrier_name?: string
|
||||||
slot_position?: number
|
slot_position?: number
|
||||||
is_current: boolean
|
is_current: boolean
|
||||||
@@ -883,12 +888,17 @@
|
|||||||
return !isAgentOrEnterpriseAccount.value
|
return !isAgentOrEnterpriseAccount.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const carrierAccountDisplay = computed(() => {
|
const getCarrierAccountDisplay = (card?: {
|
||||||
|
carrier_type?: string
|
||||||
|
carrier_name?: string
|
||||||
|
}) => {
|
||||||
if (isAgentOrEnterpriseAccount.value) {
|
if (isAgentOrEnterpriseAccount.value) {
|
||||||
return getCarrierTypeName(props.cardInfo?.carrier_type)
|
return getCarrierTypeName(card?.carrier_type)
|
||||||
}
|
}
|
||||||
return props.cardInfo?.carrier_name || '-'
|
return card?.carrier_name || '-'
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const carrierAccountDisplay = computed(() => getCarrierAccountDisplay(props.cardInfo))
|
||||||
|
|
||||||
const descriptionsColumn = computed(() => {
|
const descriptionsColumn = computed(() => {
|
||||||
if (width.value <= 640) return 1
|
if (width.value <= 640) return 1
|
||||||
@@ -989,19 +999,19 @@
|
|||||||
{
|
{
|
||||||
key: 'realname',
|
key: 'realname',
|
||||||
label: '实名同步时间',
|
label: '实名同步时间',
|
||||||
value: formatDateTime(props.cardInfo?.last_real_name_check_at) || '-',
|
value: formatAssetInfoTime(props.cardInfo?.last_real_name_check_at),
|
||||||
icon: 'realname-sync'
|
icon: 'realname-sync'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'cardSync',
|
key: 'cardSync',
|
||||||
label: '卡状态同步时间',
|
label: '卡状态同步时间',
|
||||||
value: formatDateTime(props.cardInfo?.last_card_status_check_at) || '-',
|
value: formatAssetInfoTime(props.cardInfo?.last_card_status_check_at),
|
||||||
icon: 'card-sync'
|
icon: 'card-sync'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'trafficSync',
|
key: 'trafficSync',
|
||||||
label: '流量同步时间',
|
label: '流量同步时间',
|
||||||
value: formatDateTime(props.cardInfo?.last_data_check_at) || '-',
|
value: formatAssetInfoTime(props.cardInfo?.last_data_check_at),
|
||||||
icon: 'traffic-sync'
|
icon: 'traffic-sync'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1134,6 +1144,34 @@
|
|||||||
return formatDataSize(value)
|
return formatDataSize(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口中的这几项时间以其原始文本为基准直接加 8 小时;时区后缀不参与换算。
|
||||||
|
* 例如 2026-08-06T11:54:26.693382+08:00 显示为 2026-08-06 19:54:26。
|
||||||
|
*/
|
||||||
|
const formatAssetInfoTime = (value?: string | null) => {
|
||||||
|
if (!value?.trim()) return '-'
|
||||||
|
|
||||||
|
const timeText = value.trim()
|
||||||
|
const match = timeText.match(
|
||||||
|
/^(\d{4})-(\d{2})-(\d{2})(?:T|\s)(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?$/i
|
||||||
|
)
|
||||||
|
if (!match) return '-'
|
||||||
|
|
||||||
|
const [, year, month, day, hours, minutes, seconds = '00'] = match
|
||||||
|
const date = new Date(
|
||||||
|
Date.UTC(
|
||||||
|
Number(year),
|
||||||
|
Number(month) - 1,
|
||||||
|
Number(day),
|
||||||
|
Number(hours) + 8,
|
||||||
|
Number(minutes),
|
||||||
|
Number(seconds)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return formatDateTime(date)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理轮询开关变化
|
// 处理轮询开关变化
|
||||||
const handlePollingChange = (value: string | number | boolean) => {
|
const handlePollingChange = (value: string | number | boolean) => {
|
||||||
emit('update:pollingEnabled', Boolean(value))
|
emit('update:pollingEnabled', Boolean(value))
|
||||||
@@ -1452,7 +1490,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.asset-label--bound-device-name::before) {
|
:deep(.asset-label--bound-device-name::before) {
|
||||||
content: '\e6ef';
|
content: '\e7d8';
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.asset-label--device-name::before) {
|
:deep(.asset-label--device-name::before) {
|
||||||
|
|||||||
@@ -319,8 +319,9 @@
|
|||||||
v-model="usedDataForm.data_usage"
|
v-model="usedDataForm.data_usage"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="usedDataMaxValue"
|
:max="usedDataMaxValue"
|
||||||
:precision="3"
|
:precision="0"
|
||||||
:step="0.1"
|
:step="1"
|
||||||
|
step-strictly
|
||||||
class="used-data-input__number"
|
class="used-data-input__number"
|
||||||
placeholder="请输入新的套餐真实已用量"
|
placeholder="请输入新的套餐真实已用量"
|
||||||
/>
|
/>
|
||||||
@@ -457,7 +458,7 @@
|
|||||||
|
|
||||||
const usedDataMaxValue = computed(() => {
|
const usedDataMaxValue = computed(() => {
|
||||||
const realTotalMb = selectedPackage.value?.real_total_mb ?? 0
|
const realTotalMb = selectedPackage.value?.real_total_mb ?? 0
|
||||||
return usedDataForm.unit === 'GB' ? Number((realTotalMb / 1024).toFixed(3)) : realTotalMb
|
return usedDataForm.unit === 'GB' ? Math.floor(realTotalMb / 1024) : Math.floor(realTotalMb)
|
||||||
})
|
})
|
||||||
|
|
||||||
const validateUsedData = (
|
const validateUsedData = (
|
||||||
@@ -471,6 +472,11 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(usedDataForm.data_usage)) {
|
||||||
|
callback(new Error('新的已用量必须为整数'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (dataUsageMb === undefined || dataUsageMb < 0) {
|
if (dataUsageMb === undefined || dataUsageMb < 0) {
|
||||||
callback(new Error('请输入可换算为非负数字 MB 的已用量'))
|
callback(new Error('请输入可换算为非负数字 MB 的已用量'))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="套餐流量详单"
|
title="套餐流量详单"
|
||||||
width="900px"
|
width="900px"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<div v-if="data">
|
<div v-if="data">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="往期订单"
|
title="往期订单"
|
||||||
width="60%"
|
width="60%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<div v-if="loading" style="padding: 40px 0; text-align: center">
|
<div v-if="loading" style="padding: 40px 0; text-align: center">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="套餐充值"
|
title="套餐充值"
|
||||||
width="40%"
|
width="40%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
width="500px"
|
width="500px"
|
||||||
:lock-scroll="true"
|
:lock-scroll="true"
|
||||||
:z-index="4000"
|
:z-index="4000"
|
||||||
modal-class="asset-information-dialog speed-limit-modal"
|
modal-class="speed-limit-modal"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||||
<ElFormItem label="固定档位" prop="code">
|
<ElFormItem label="固定档位" prop="code">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="设置切卡模式"
|
title="设置切卡模式"
|
||||||
width="45%"
|
width="45%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||||
<ElFormItem label="设备标识">
|
<ElFormItem label="设备标识">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="设置WiFi"
|
title="设置WiFi"
|
||||||
width="40%"
|
width="40%"
|
||||||
modal-class="asset-information-dialog"
|
|
||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px" autocomplete="off">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px" autocomplete="off">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function useAssetFormatters() {
|
|||||||
const getRealNameStatusType = (status?: number): 'info' | 'success' | 'warning' | 'danger' => {
|
const getRealNameStatusType = (status?: number): 'info' | 'success' | 'warning' | 'danger' => {
|
||||||
if (status === undefined || status === null) return 'info'
|
if (status === undefined || status === null) return 'info'
|
||||||
const map: Record<number, 'info' | 'success' | 'warning' | 'danger'> = {
|
const map: Record<number, 'info' | 'success' | 'warning' | 'danger'> = {
|
||||||
0: 'info',
|
0: 'danger',
|
||||||
1: 'success'
|
1: 'success'
|
||||||
}
|
}
|
||||||
return map[status] || 'info'
|
return map[status] || 'info'
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const NULLABLE_REALTIME_CARD_FIELDS = new Set([
|
|||||||
'last_card_status_check_at'
|
'last_card_status_check_at'
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 详情接口是绑定卡实名时间的唯一来源;实时状态接口不应覆盖或保留旧值。
|
||||||
|
const DETAIL_ONLY_BOUND_CARD_FIELDS = new Set(['real_name_at'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化数据大小(MB -> GB/MB)
|
* 格式化数据大小(MB -> GB/MB)
|
||||||
*/
|
*/
|
||||||
@@ -60,10 +63,13 @@ const mergeAssetBoundCards = (
|
|||||||
): AssetBoundCard[] =>
|
): AssetBoundCard[] =>
|
||||||
realtimeCards.map((realtimeCard) => {
|
realtimeCards.map((realtimeCard) => {
|
||||||
const existingCard = existingCards.find((card) => card.iccid === realtimeCard.iccid)
|
const existingCard = existingCards.find((card) => card.iccid === realtimeCard.iccid)
|
||||||
if (!existingCard) return realtimeCard
|
if (!existingCard) {
|
||||||
|
return { ...realtimeCard, real_name_at: null }
|
||||||
|
}
|
||||||
|
|
||||||
const mergedCard = { ...existingCard } as unknown as Record<string, unknown>
|
const mergedCard = { ...existingCard } as unknown as Record<string, unknown>
|
||||||
for (const [key, value] of Object.entries(realtimeCard)) {
|
for (const [key, value] of Object.entries(realtimeCard)) {
|
||||||
|
if (DETAIL_ONLY_BOUND_CARD_FIELDS.has(key)) continue
|
||||||
if (value !== undefined && (value !== null || NULLABLE_REALTIME_CARD_FIELDS.has(key))) {
|
if (value !== undefined && (value !== null || NULLABLE_REALTIME_CARD_FIELDS.has(key))) {
|
||||||
mergedCard[key] = value
|
mergedCard[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1653,112 +1653,3 @@
|
|||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
/* Dialogs are teleported to body, so their surface needs a page-level hook. */
|
|
||||||
.asset-information-dialog.el-dialog {
|
|
||||||
max-width: calc(100vw - 32px);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #e5eaf1;
|
|
||||||
border-radius: 14px;
|
|
||||||
box-shadow: 0 18px 50px rgb(15 23 42 / 18%);
|
|
||||||
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 20px 24px 16px;
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: 1px solid #e5eaf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 650;
|
|
||||||
color: #172033;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
padding: 22px 24px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__footer {
|
|
||||||
padding: 14px 24px 20px;
|
|
||||||
border-top: 1px solid #eef2f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item__label {
|
|
||||||
color: #526078;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
min-height: 34px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table {
|
|
||||||
--el-table-header-bg-color: #f8fafc;
|
|
||||||
--el-table-row-hover-bg-color: #f5f8ff;
|
|
||||||
--el-table-border-color: #e5eaf1;
|
|
||||||
border-radius: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-descriptions__label.el-descriptions__cell.is-bordered-label {
|
|
||||||
color: #526078;
|
|
||||||
background: #f8fafc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.asset-information-dialog.el-dialog {
|
|
||||||
width: calc(100vw - 20px) !important;
|
|
||||||
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 16px 18px 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__body {
|
|
||||||
max-height: calc(100vh - 150px);
|
|
||||||
padding: 18px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__footer {
|
|
||||||
padding: 12px 18px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .asset-information-dialog.el-dialog {
|
|
||||||
border-color: #2b3a52;
|
|
||||||
|
|
||||||
.el-dialog__header,
|
|
||||||
.el-dialog__footer {
|
|
||||||
border-color: #2b3a52;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dialog__title,
|
|
||||||
.el-form-item__label {
|
|
||||||
color: #e5edf8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-table {
|
|
||||||
--el-table-header-bg-color: #202d42;
|
|
||||||
--el-table-row-hover-bg-color: #223451;
|
|
||||||
--el-table-border-color: #2b3a52;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-descriptions__label.el-descriptions__cell.is-bordered-label {
|
|
||||||
color: #afbdd0;
|
|
||||||
background: #202d42;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface BindingCard {
|
|||||||
msisdn?: string
|
msisdn?: string
|
||||||
is_current: boolean
|
is_current: boolean
|
||||||
status?: number
|
status?: number
|
||||||
|
carrier_type?: string
|
||||||
carrier_name?: string
|
carrier_name?: string
|
||||||
slot_position?: number
|
slot_position?: number
|
||||||
network_status?: number
|
network_status?: number
|
||||||
|
|||||||
@@ -108,6 +108,7 @@
|
|||||||
v-model="allocateForm.target_shop_id"
|
v-model="allocateForm.target_shop_id"
|
||||||
:options="shopCascadeOptions"
|
:options="shopCascadeOptions"
|
||||||
:props="shopCascadeProps"
|
:props="shopCascadeProps"
|
||||||
|
:before-filter="handleAllocateShopBeforeFilter"
|
||||||
placeholder="请选择目标店铺"
|
placeholder="请选择目标店铺"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
@@ -238,7 +239,10 @@
|
|||||||
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选设备方式" prop="selection_type">
|
<ElFormItem label="选设备方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="seriesBindingForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="seriesBindingForm.selection_type"
|
||||||
|
@change="handleDeviceBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">设备ID列表</ElRadio>
|
<ElRadio label="list">设备ID列表</ElRadio>
|
||||||
<ElRadio label="range">设备号范围</ElRadio>
|
<ElRadio label="range">设备号范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -736,7 +740,10 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选设备方式" prop="selection_type">
|
<ElFormItem label="选设备方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="enterpriseDeviceAuthorizeForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="enterpriseDeviceAuthorizeForm.selection_type"
|
||||||
|
@change="handleDeviceBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">设备号列表</ElRadio>
|
<ElRadio label="list">设备号列表</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
@@ -842,7 +849,10 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选设备方式" prop="selection_type">
|
<ElFormItem label="选设备方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="enterpriseDeviceRecallForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="enterpriseDeviceRecallForm.selection_type"
|
||||||
|
@change="handleDeviceBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">设备号列表</ElRadio>
|
<ElRadio label="list">设备号列表</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
@@ -1040,6 +1050,62 @@
|
|||||||
const batchRealnamePolicyError = ref('')
|
const batchRealnamePolicyError = ref('')
|
||||||
const exportDialogVisible = ref(false)
|
const exportDialogVisible = ref(false)
|
||||||
const shopCascadeOptions = ref<any[]>([])
|
const shopCascadeOptions = ref<any[]>([])
|
||||||
|
let allocateShopSearchRequestId = 0
|
||||||
|
const mapShopCascadeNodes = (items: any[]) => {
|
||||||
|
const seen = new Set<number>()
|
||||||
|
|
||||||
|
return items.reduce<any[]>((nodes, item) => {
|
||||||
|
if (seen.has(item.id)) return nodes
|
||||||
|
|
||||||
|
seen.add(item.id)
|
||||||
|
nodes.push({
|
||||||
|
value: item.id,
|
||||||
|
label: item.shop_name,
|
||||||
|
leaf: !item.has_children
|
||||||
|
})
|
||||||
|
return nodes
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
const handleAllocateShopBeforeFilter = async (query: string) => {
|
||||||
|
const keyword = query.trim()
|
||||||
|
if (!keyword) return true
|
||||||
|
|
||||||
|
const requestId = ++allocateShopSearchRequestId
|
||||||
|
try {
|
||||||
|
const shopResponse = await ShopService.getShops({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
shop_name: keyword
|
||||||
|
})
|
||||||
|
if (requestId !== allocateShopSearchRequestId) return false
|
||||||
|
if (shopResponse.code !== 0) {
|
||||||
|
shopCascadeOptions.value = []
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentIds = Array.from(
|
||||||
|
new Set((shopResponse.data.items || []).map((shop) => shop.parent_id ?? null))
|
||||||
|
)
|
||||||
|
const cascadeResponses = await Promise.all(
|
||||||
|
parentIds.map((parentId) =>
|
||||||
|
ShopService.getShopsCascade({
|
||||||
|
shop_name: keyword,
|
||||||
|
parent_id: parentId ?? undefined
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (requestId !== allocateShopSearchRequestId) return false
|
||||||
|
|
||||||
|
shopCascadeOptions.value = mapShopCascadeNodes(
|
||||||
|
cascadeResponses.flatMap((response) => (response.code === 0 ? response.data || [] : []))
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId === allocateShopSearchRequestId) shopCascadeOptions.value = []
|
||||||
|
console.error('搜索批量分配目标店铺失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
const shopCascadeProps = {
|
const shopCascadeProps = {
|
||||||
lazy: true,
|
lazy: true,
|
||||||
checkStrictly: true,
|
checkStrictly: true,
|
||||||
@@ -1050,12 +1116,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const nodes = (res.data || []).map((item: any) => ({
|
resolve(mapShopCascadeNodes(res.data || []))
|
||||||
value: item.id,
|
|
||||||
label: item.shop_name,
|
|
||||||
leaf: !item.has_children
|
|
||||||
}))
|
|
||||||
resolve(nodes)
|
|
||||||
} else {
|
} else {
|
||||||
resolve([])
|
resolve([])
|
||||||
}
|
}
|
||||||
@@ -1072,11 +1133,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
shopCascadeOptions.value = mapShopCascadeNodes(res.data || [])
|
||||||
value: item.id,
|
|
||||||
label: item.shop_name,
|
|
||||||
leaf: !item.has_children
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载顶级店铺失败:', error)
|
console.error('加载顶级店铺失败:', error)
|
||||||
@@ -1355,7 +1412,7 @@
|
|||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = reactive<Record<string, any>>({ ...initialSearchState })
|
const searchForm = reactive<Record<string, any>>({ ...initialSearchState })
|
||||||
|
|
||||||
// 搜索批次号(用于搜索表单)
|
// 批次号仅在用户切换至筛选条件或输入批次号搜索时加载。
|
||||||
const loadSearchBatchNoOptions = async (batchNo?: string) => {
|
const loadSearchBatchNoOptions = async (batchNo?: string) => {
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
@@ -1374,11 +1431,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索批次号
|
|
||||||
const handleSearchBatchNo = (query: string) => {
|
const handleSearchBatchNo = (query: string) => {
|
||||||
if (query) {
|
loadSearchBatchNoOptions(query || undefined)
|
||||||
loadSearchBatchNoOptions(query)
|
}
|
||||||
} else {
|
|
||||||
|
const handleDeviceBatchSelectionTypeChange = (
|
||||||
|
selectionType: string | number | boolean | undefined
|
||||||
|
) => {
|
||||||
|
if (selectionType === DeviceSelectionType.FILTER) {
|
||||||
loadSearchBatchNoOptions()
|
loadSearchBatchNoOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2515,18 +2575,14 @@
|
|||||||
|
|
||||||
const showEnterpriseDeviceAuthorizeDialog = async () => {
|
const showEnterpriseDeviceAuthorizeDialog = async () => {
|
||||||
resetEnterpriseDeviceAuthorizeForm()
|
resetEnterpriseDeviceAuthorizeForm()
|
||||||
await Promise.all([
|
await Promise.all([handleSearchEnterprise(), loadTopLevelShopCascadeOptions()])
|
||||||
handleSearchEnterprise(),
|
|
||||||
loadSearchBatchNoOptions(),
|
|
||||||
loadTopLevelShopCascadeOptions()
|
|
||||||
])
|
|
||||||
enterpriseDeviceAuthorizeDialogVisible.value = true
|
enterpriseDeviceAuthorizeDialogVisible.value = true
|
||||||
enterpriseDeviceAuthorizeFormRef.value?.clearValidate()
|
enterpriseDeviceAuthorizeFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
const showEnterpriseDeviceRecallDialog = async () => {
|
const showEnterpriseDeviceRecallDialog = async () => {
|
||||||
resetEnterpriseDeviceRecallForm()
|
resetEnterpriseDeviceRecallForm()
|
||||||
await Promise.all([handleSearchEnterprise(), loadSearchBatchNoOptions()])
|
await handleSearchEnterprise()
|
||||||
enterpriseDeviceRecallDialogVisible.value = true
|
enterpriseDeviceRecallDialogVisible.value = true
|
||||||
enterpriseDeviceRecallFormRef.value?.clearValidate()
|
enterpriseDeviceRecallFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
@@ -2667,7 +2723,7 @@
|
|||||||
selection_type:
|
selection_type:
|
||||||
selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER
|
selectedDevices.value.length > 0 ? DeviceSelectionType.LIST : DeviceSelectionType.FILTER
|
||||||
})
|
})
|
||||||
await Promise.all([loadPackageSeriesList(), loadSearchBatchNoOptions()])
|
await loadPackageSeriesList()
|
||||||
seriesBindingDialogVisible.value = true
|
seriesBindingDialogVisible.value = true
|
||||||
seriesBindingFormRef.value?.clearValidate()
|
seriesBindingFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,8 +268,8 @@
|
|||||||
{{ createNewDeviceEmptyMessage }}
|
{{ createNewDeviceEmptyMessage }}
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem v-if="createDirectInheritedShopHint" label="归属提示">
|
<ElFormItem label="归属提示">
|
||||||
<div class="exchange-shop-hint">{{ createDirectInheritedShopHint }}</div>
|
<div class="exchange-shop-hint">新资产会继承旧资产店铺</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem v-if="createForm.flow_type === 'direct'" label="是否迁移数据">
|
<ElFormItem v-if="createForm.flow_type === 'direct'" label="是否迁移数据">
|
||||||
<ElSwitch v-model="createForm.migrate_data" />
|
<ElSwitch v-model="createForm.migrate_data" />
|
||||||
@@ -771,11 +771,6 @@
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDirectInheritedShopHint = computed(() => {
|
|
||||||
const shopName = getShopDisplayName(createSelectedNewAsset.value)
|
|
||||||
return shopName ? `换货完成后将归属:${shopName}` : ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const shipInheritedShopDisplayText = computed(() => {
|
const shipInheritedShopDisplayText = computed(() => {
|
||||||
const shopName = getShopDisplayName(shipSelectedNewAsset.value)
|
const shopName = getShopDisplayName(shipSelectedNewAsset.value)
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
v-model="allocateForm.to_shop_id"
|
v-model="allocateForm.to_shop_id"
|
||||||
:options="shopCascadeOptions"
|
:options="shopCascadeOptions"
|
||||||
:props="shopCascadeProps"
|
:props="shopCascadeProps"
|
||||||
|
:before-filter="handleAllocateShopBeforeFilter"
|
||||||
placeholder="请选择目标店铺"
|
placeholder="请选择目标店铺"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
@@ -119,7 +120,10 @@
|
|||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选卡方式" prop="selection_type">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="allocateForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="allocateForm.selection_type"
|
||||||
|
@change="handleCardBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
<ElRadio label="range">号段范围</ElRadio>
|
<ElRadio label="range">号段范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -246,7 +250,10 @@
|
|||||||
>
|
>
|
||||||
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules" label-width="100px">
|
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules" label-width="100px">
|
||||||
<ElFormItem label="选卡方式" prop="selection_type">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="recallForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="recallForm.selection_type"
|
||||||
|
@change="handleCardBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
<ElRadio label="range">号段范围</ElRadio>
|
<ElRadio label="range">号段范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -415,7 +422,10 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选卡方式" prop="selection_type">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="enterpriseCardAuthorizeForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="enterpriseCardAuthorizeForm.selection_type"
|
||||||
|
@change="handleCardBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
<ElRadio label="range">号段范围</ElRadio>
|
<ElRadio label="range">号段范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -568,7 +578,10 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="选卡方式" prop="selection_type">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="enterpriseCardRecallForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="enterpriseCardRecallForm.selection_type"
|
||||||
|
@change="handleCardBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
<ElRadio label="range">号段范围</ElRadio>
|
<ElRadio label="range">号段范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -739,7 +752,10 @@
|
|||||||
label-width="100"
|
label-width="100"
|
||||||
>
|
>
|
||||||
<ElFormItem label="选卡方式" prop="selection_type">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="seriesBindingForm.selection_type">
|
<ElRadioGroup
|
||||||
|
v-model="seriesBindingForm.selection_type"
|
||||||
|
@change="handleCardBatchSelectionTypeChange"
|
||||||
|
>
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
<ElRadio label="range">号段范围</ElRadio>
|
<ElRadio label="range">号段范围</ElRadio>
|
||||||
<ElRadio label="filter">筛选条件</ElRadio>
|
<ElRadio label="filter">筛选条件</ElRadio>
|
||||||
@@ -1338,10 +1354,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量分配对话框批次号选项
|
// 批次号仅在用户切换至筛选条件时加载;避免弹窗打开即请求导入任务列表。
|
||||||
const allocateBatchNoOptions = ref<any[]>([])
|
const allocateBatchNoOptions = ref<any[]>([])
|
||||||
|
|
||||||
// 加载批量分配对话框批次号选项
|
|
||||||
const loadAllocateBatchNoOptions = async (batchNo?: string) => {
|
const loadAllocateBatchNoOptions = async (batchNo?: string) => {
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
@@ -1360,11 +1375,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索批量分配对话框批次号
|
|
||||||
const handleSearchAllocateBatchNo = (query: string) => {
|
const handleSearchAllocateBatchNo = (query: string) => {
|
||||||
if (query) {
|
loadAllocateBatchNoOptions(query || undefined)
|
||||||
loadAllocateBatchNoOptions(query)
|
}
|
||||||
} else {
|
|
||||||
|
const handleCardBatchSelectionTypeChange = (
|
||||||
|
selectionType: string | number | boolean | undefined
|
||||||
|
) => {
|
||||||
|
if (selectionType === CardSelectionType.FILTER) {
|
||||||
loadAllocateBatchNoOptions()
|
loadAllocateBatchNoOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1506,6 +1524,62 @@
|
|||||||
|
|
||||||
// 店铺相关
|
// 店铺相关
|
||||||
const shopCascadeOptions = ref<any[]>([])
|
const shopCascadeOptions = ref<any[]>([])
|
||||||
|
let allocateShopSearchRequestId = 0
|
||||||
|
const mapShopCascadeNodes = (items: any[]) => {
|
||||||
|
const seen = new Set<number>()
|
||||||
|
|
||||||
|
return items.reduce<any[]>((nodes, item) => {
|
||||||
|
if (seen.has(item.id)) return nodes
|
||||||
|
|
||||||
|
seen.add(item.id)
|
||||||
|
nodes.push({
|
||||||
|
value: item.id,
|
||||||
|
label: item.shop_name,
|
||||||
|
leaf: !item.has_children
|
||||||
|
})
|
||||||
|
return nodes
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
const handleAllocateShopBeforeFilter = async (query: string) => {
|
||||||
|
const keyword = query.trim()
|
||||||
|
if (!keyword) return true
|
||||||
|
|
||||||
|
const requestId = ++allocateShopSearchRequestId
|
||||||
|
try {
|
||||||
|
const shopResponse = await ShopService.getShops({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
shop_name: keyword
|
||||||
|
})
|
||||||
|
if (requestId !== allocateShopSearchRequestId) return false
|
||||||
|
if (shopResponse.code !== 0) {
|
||||||
|
shopCascadeOptions.value = []
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentIds = Array.from(
|
||||||
|
new Set((shopResponse.data.items || []).map((shop) => shop.parent_id ?? null))
|
||||||
|
)
|
||||||
|
const cascadeResponses = await Promise.all(
|
||||||
|
parentIds.map((parentId) =>
|
||||||
|
ShopService.getShopsCascade({
|
||||||
|
shop_name: keyword,
|
||||||
|
parent_id: parentId ?? undefined
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (requestId !== allocateShopSearchRequestId) return false
|
||||||
|
|
||||||
|
shopCascadeOptions.value = mapShopCascadeNodes(
|
||||||
|
cascadeResponses.flatMap((response) => (response.code === 0 ? response.data || [] : []))
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
if (requestId === allocateShopSearchRequestId) shopCascadeOptions.value = []
|
||||||
|
console.error('搜索批量分配目标店铺失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
const shopCascadeProps = {
|
const shopCascadeProps = {
|
||||||
lazy: true,
|
lazy: true,
|
||||||
checkStrictly: true,
|
checkStrictly: true,
|
||||||
@@ -1516,12 +1590,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
const res = await ShopService.getShopsCascade({ parent_id: parentId })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const nodes = (res.data || []).map((item: any) => ({
|
resolve(mapShopCascadeNodes(res.data || []))
|
||||||
value: item.id,
|
|
||||||
label: item.shop_name,
|
|
||||||
leaf: !item.has_children
|
|
||||||
}))
|
|
||||||
resolve(nodes)
|
|
||||||
} else {
|
} else {
|
||||||
resolve([])
|
resolve([])
|
||||||
}
|
}
|
||||||
@@ -1538,11 +1607,7 @@
|
|||||||
try {
|
try {
|
||||||
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
const res = await ShopService.getShopsCascade({ parent_id: undefined })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
|
shopCascadeOptions.value = mapShopCascadeNodes(res.data || [])
|
||||||
value: item.id,
|
|
||||||
label: item.shop_name,
|
|
||||||
leaf: !item.has_children
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载顶级店铺失败:', error)
|
console.error('加载顶级店铺失败:', error)
|
||||||
@@ -1843,6 +1908,19 @@
|
|||||||
{ label: '无换卡记录', value: false }
|
{ label: '无换卡记录', value: false }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '是否绑设备',
|
||||||
|
prop: 'is_standalone',
|
||||||
|
type: 'select',
|
||||||
|
config: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '全部'
|
||||||
|
},
|
||||||
|
options: () => [
|
||||||
|
{ label: '未绑定设备', value: true },
|
||||||
|
{ label: '已绑定设备', value: false }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '起始号',
|
label: '起始号',
|
||||||
prop: 'iccid_start',
|
prop: 'iccid_start',
|
||||||
@@ -1860,19 +1938,6 @@
|
|||||||
clearable: true,
|
clearable: true,
|
||||||
placeholder: '请输入ICCID结束号'
|
placeholder: '请输入ICCID结束号'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '是否绑设备',
|
|
||||||
prop: 'is_standalone',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
placeholder: '全部'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '未绑定设备', value: true },
|
|
||||||
{ label: '已绑定设备', value: false }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2469,6 +2534,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
allocateDialogVisible.value = true
|
allocateDialogVisible.value = true
|
||||||
|
shopCascadeOptions.value = []
|
||||||
Object.assign(allocateForm, {
|
Object.assign(allocateForm, {
|
||||||
selection_type: CardSelectionType.LIST,
|
selection_type: CardSelectionType.LIST,
|
||||||
to_shop_id: undefined,
|
to_shop_id: undefined,
|
||||||
@@ -2481,7 +2547,6 @@
|
|||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
loadAllocateCarrierOptions()
|
loadAllocateCarrierOptions()
|
||||||
loadAllocateBatchNoOptions()
|
|
||||||
if (allocateFormRef.value) {
|
if (allocateFormRef.value) {
|
||||||
allocateFormRef.value.resetFields()
|
allocateFormRef.value.resetFields()
|
||||||
}
|
}
|
||||||
@@ -2507,7 +2572,6 @@
|
|||||||
recallFormRef.value.resetFields()
|
recallFormRef.value.resetFields()
|
||||||
}
|
}
|
||||||
loadAllocateCarrierOptions()
|
loadAllocateCarrierOptions()
|
||||||
loadAllocateBatchNoOptions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭批量分配对话框
|
// 关闭批量分配对话框
|
||||||
@@ -2677,7 +2741,6 @@
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
handleSearchEnterprise(),
|
handleSearchEnterprise(),
|
||||||
loadAllocateCarrierOptions(),
|
loadAllocateCarrierOptions(),
|
||||||
loadAllocateBatchNoOptions(),
|
|
||||||
loadTopLevelShopCascadeOptions()
|
loadTopLevelShopCascadeOptions()
|
||||||
])
|
])
|
||||||
enterpriseCardAuthorizeDialogVisible.value = true
|
enterpriseCardAuthorizeDialogVisible.value = true
|
||||||
@@ -2686,11 +2749,7 @@
|
|||||||
|
|
||||||
const showEnterpriseCardRecallDialog = async () => {
|
const showEnterpriseCardRecallDialog = async () => {
|
||||||
resetEnterpriseCardRecallForm()
|
resetEnterpriseCardRecallForm()
|
||||||
await Promise.all([
|
await Promise.all([handleSearchEnterprise(), loadAllocateCarrierOptions()])
|
||||||
handleSearchEnterprise(),
|
|
||||||
loadAllocateCarrierOptions(),
|
|
||||||
loadAllocateBatchNoOptions()
|
|
||||||
])
|
|
||||||
enterpriseCardRecallDialogVisible.value = true
|
enterpriseCardRecallDialogVisible.value = true
|
||||||
enterpriseCardRecallFormRef.value?.clearValidate()
|
enterpriseCardRecallFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
@@ -2810,11 +2869,7 @@
|
|||||||
selection_type:
|
selection_type:
|
||||||
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER
|
selectedCards.value.length > 0 ? CardSelectionType.LIST : CardSelectionType.FILTER
|
||||||
})
|
})
|
||||||
await Promise.all([
|
await Promise.all([loadPackageSeriesList(), loadAllocateCarrierOptions()])
|
||||||
loadPackageSeriesList(),
|
|
||||||
loadAllocateCarrierOptions(),
|
|
||||||
loadAllocateBatchNoOptions()
|
|
||||||
])
|
|
||||||
seriesBindingDialogVisible.value = true
|
seriesBindingDialogVisible.value = true
|
||||||
seriesBindingFormRef.value?.clearValidate()
|
seriesBindingFormRef.value?.clearValidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,13 @@
|
|||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { RefundService } from '@/api/modules'
|
import { RefundService } from '@/api/modules'
|
||||||
import type { Refund, ResubmitRefundRequest, RefundAttachment } from '@/types/api'
|
import {
|
||||||
|
RefundApprovalStatus,
|
||||||
|
RefundStatus,
|
||||||
|
type Refund,
|
||||||
|
type ResubmitRefundRequest,
|
||||||
|
type RefundAttachment
|
||||||
|
} from '@/types/api'
|
||||||
import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format'
|
import { fenToYuan, formatDateTime, yuanToFen } from '@/utils/business/format'
|
||||||
import { toVoucherKeyList, getErrorMessage } from '@/utils/business'
|
import { toVoucherKeyList, getErrorMessage } from '@/utils/business'
|
||||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||||
@@ -456,22 +462,14 @@
|
|||||||
resubmitUploadRef.value?.clearFiles(false)
|
resubmitUploadRef.value?.clearFiles(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const canResubmit = (item: Refund) => {
|
const canResubmit = (item: Refund) =>
|
||||||
const statusText = [item.approval_status, item.approval_status_name, item.status_name]
|
item.status === RefundStatus.REJECTED ||
|
||||||
.filter(Boolean)
|
item.status === RefundStatus.RETURNED ||
|
||||||
.join(' ')
|
item.approval_status === RefundApprovalStatus.REJECTED ||
|
||||||
.toLowerCase()
|
item.approval_status === RefundApprovalStatus.REVOKED ||
|
||||||
|
item.approval_status === RefundApprovalStatus.REVOKED_AFTER_APPROVED ||
|
||||||
return (
|
item.approval_status === RefundApprovalStatus.SUBMIT_FAILED ||
|
||||||
item.status === 3 ||
|
item.approval_status === RefundApprovalStatus.RESULT_UNKNOWN
|
||||||
statusText.includes('reject') ||
|
|
||||||
statusText.includes('revoke') ||
|
|
||||||
statusText.includes('delete') ||
|
|
||||||
statusText.includes('驳回') ||
|
|
||||||
statusText.includes('撤销') ||
|
|
||||||
statusText.includes('删除')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleShowResubmit = () => {
|
const handleShowResubmit = () => {
|
||||||
if (!refund.value || !canResubmit(refund.value)) return
|
if (!refund.value || !canResubmit(refund.value)) return
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
RefundStatus,
|
RefundStatus,
|
||||||
|
RefundApprovalStatus,
|
||||||
type Refund,
|
type Refund,
|
||||||
type RefundQueryParams,
|
type RefundQueryParams,
|
||||||
type ResubmitRefundRequest,
|
type ResubmitRefundRequest,
|
||||||
@@ -734,22 +735,14 @@
|
|||||||
return keys.length ? keys : toVoucherKeyList(row.refund_voucher_key)
|
return keys.length ? keys : toVoucherKeyList(row.refund_voucher_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
const canResubmit = (row: Refund) => {
|
const canResubmit = (row: Refund) =>
|
||||||
const statusText = [row.approval_status, row.approval_status_name, row.status_name]
|
row.status === RefundStatus.REJECTED ||
|
||||||
.filter(Boolean)
|
row.status === RefundStatus.RETURNED ||
|
||||||
.join(' ')
|
row.approval_status === RefundApprovalStatus.REJECTED ||
|
||||||
.toLowerCase()
|
row.approval_status === RefundApprovalStatus.REVOKED ||
|
||||||
|
row.approval_status === RefundApprovalStatus.REVOKED_AFTER_APPROVED ||
|
||||||
return (
|
row.approval_status === RefundApprovalStatus.SUBMIT_FAILED ||
|
||||||
row.status === 3 ||
|
row.approval_status === RefundApprovalStatus.RESULT_UNKNOWN
|
||||||
statusText.includes('reject') ||
|
|
||||||
statusText.includes('revoke') ||
|
|
||||||
statusText.includes('delete') ||
|
|
||||||
statusText.includes('驳回') ||
|
|
||||||
statusText.includes('撤销') ||
|
|
||||||
statusText.includes('删除')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const canTriggerApproval = (row: Refund) => {
|
const canTriggerApproval = (row: Refund) => {
|
||||||
const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null
|
const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null
|
||||||
|
|||||||
@@ -507,12 +507,13 @@
|
|||||||
const columnOptions = computed(() => {
|
const columnOptions = computed(() => {
|
||||||
const options = [
|
const options = [
|
||||||
{ label: '订单编号', prop: 'order_no' },
|
{ label: '订单编号', prop: 'order_no' },
|
||||||
|
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||||
|
{ label: '购买套餐名称', prop: 'purchased_package_names' },
|
||||||
{ label: '买家手机号', prop: 'buyer_phone' },
|
{ label: '买家手机号', prop: 'buyer_phone' },
|
||||||
{ label: '买家昵称', prop: 'buyer_nickname' },
|
{ label: '买家昵称', prop: 'buyer_nickname' },
|
||||||
{ label: '订单类型', prop: 'order_type' },
|
{ label: '订单类型', prop: 'order_type' },
|
||||||
{ label: '资产类型', prop: 'asset_type' },
|
{ label: '资产类型', prop: 'asset_type' },
|
||||||
{ label: '买家类型', prop: 'buyer_type' },
|
{ label: '买家类型', prop: 'buyer_type' },
|
||||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
|
||||||
{ label: '订单渠道', prop: 'purchase_role' },
|
{ label: '订单渠道', prop: 'purchase_role' },
|
||||||
{ label: '购买备注', prop: 'purchase_remark' },
|
{ label: '购买备注', prop: 'purchase_remark' },
|
||||||
{ label: '是否代购', prop: 'is_purchase_on_behalf' },
|
{ label: '是否代购', prop: 'is_purchase_on_behalf' },
|
||||||
@@ -727,6 +728,14 @@
|
|||||||
return `¥${(amount / 100).toFixed(2)}`
|
return `¥${(amount / 100).toFixed(2)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatPurchasedPackageNames = (order: Order): string => {
|
||||||
|
const packageNames = (order.items || [])
|
||||||
|
.map((item) => (item.package_name || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
return packageNames.join('、') || '-'
|
||||||
|
}
|
||||||
|
|
||||||
// 获取支付状态标签类型
|
// 获取支付状态标签类型
|
||||||
const getPaymentStatusType = (
|
const getPaymentStatusType = (
|
||||||
status: PaymentStatus
|
status: PaymentStatus
|
||||||
@@ -847,6 +856,20 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'asset_identifier',
|
||||||
|
label: '资产标识符',
|
||||||
|
width: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: Order) => row.asset_identifier || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'purchased_package_names',
|
||||||
|
label: '购买套餐名称',
|
||||||
|
minWidth: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: Order) => formatPurchasedPackageNames(row)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'buyer_phone',
|
prop: 'buyer_phone',
|
||||||
label: '买家手机号',
|
label: '买家手机号',
|
||||||
@@ -911,13 +934,6 @@
|
|||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: Order) => (row.expires_at ? formatDateTime(row.expires_at) : '-')
|
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',
|
prop: 'purchase_role',
|
||||||
label: '订单渠道',
|
label: '订单渠道',
|
||||||
|
|||||||
@@ -1080,7 +1080,8 @@
|
|||||||
],
|
],
|
||||||
init_username: [
|
init_username: [
|
||||||
{ required: true, message: '请输入初始账号用户名', trigger: 'blur' },
|
{ required: true, message: '请输入初始账号用户名', trigger: 'blur' },
|
||||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
{ min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' },
|
||||||
|
{ pattern: /^[A-Za-z\d]+$/, message: '用户名只能包含英文或数字', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
init_password: [
|
init_password: [
|
||||||
{ required: true, message: '请输入初始账号密码', trigger: 'blur' },
|
{ required: true, message: '请输入初始账号密码', trigger: 'blur' },
|
||||||
|
|||||||
Reference in New Issue
Block a user