Compare commits
3 Commits
cfb6b52cc2
...
99de03604d
| Author | SHA1 | Date | |
|---|---|---|---|
| 99de03604d | |||
| 1baa6d87cf | |||
| f604791c69 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ npminstall-debug.log
|
|||||||
|
|
||||||
# ai
|
# ai
|
||||||
.agents
|
.agents
|
||||||
|
|
||||||
|
#build
|
||||||
|
one-pipe-system-dist.zip
|
||||||
|
|||||||
@@ -13,3 +13,9 @@ This context captures the business language used by the admin system for orders,
|
|||||||
**Operational batch task**: An asynchronous admin task created from an uploaded batch import file and tracked through task status, counts, operator identity, and failure details. _Avoid_: Inline order operation, synchronous import
|
**Operational batch task**: An asynchronous admin task created from an uploaded batch import file and tracked through task status, counts, operator identity, and failure details. _Avoid_: Inline order operation, synchronous import
|
||||||
|
|
||||||
**Operational remark**: A note captured when an admin creates a financial or operational record. Operational remarks are retained for later review and are not edited after creation. _Avoid_: Editable comment, processing note
|
**Operational remark**: A note captured when an admin creates a financial or operational record. Operational remarks are retained for later review and are not edited after creation. _Avoid_: Editable comment, processing note
|
||||||
|
|
||||||
|
**Recharge order**: A C-side asset recharge order created when a user recharges an IoT card or device asset wallet. It is identified by `recharge_order_no`, is associated with `resource_type` and `resource_id`, and may be rejected while pending payment. _Avoid_: Agent recharge order
|
||||||
|
|
||||||
|
**Rejected recharge order**: A recharge order that an admin has rejected before payment because the recharge request should not proceed. Rejection is an irreversible terminal state and does not involve refunds. _Avoid_: Refunded recharge, closed recharge
|
||||||
|
|
||||||
|
**Agent recharge order**: A financial recharge record for an agent/shop wallet in the admin finance module. It uses a different API contract and status model from C-side recharge orders. _Avoid_: Recharge order
|
||||||
|
|||||||
110
scripts/tests/agentRechargeActions.test.mjs
Normal file
110
scripts/tests/agentRechargeActions.test.mjs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { mkdtemp, rm, stat } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
|
||||||
|
async function resolveSrcAlias(path) {
|
||||||
|
const resolvedPath = join(process.cwd(), 'src', path.slice(2))
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((await stat(resolvedPath)).isDirectory()) {
|
||||||
|
return join(resolvedPath, 'index.ts')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Let esbuild report the original resolution failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importBundledActions() {
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), 'agent-recharge-actions-'))
|
||||||
|
const bundlePath = join(tempDir, 'agentRechargeActions.mjs')
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ['src/views/finance/agent-recharge/agentRechargeActions.ts'],
|
||||||
|
bundle: true,
|
||||||
|
outfile: bundlePath,
|
||||||
|
format: 'esm',
|
||||||
|
platform: 'node',
|
||||||
|
absWorkingDir: process.cwd(),
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'alias-src',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^@\// }, async (args) => ({
|
||||||
|
path: await resolveSrcAlias(args.path)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const module = await import(pathToFileURL(bundlePath).href)
|
||||||
|
return {
|
||||||
|
module,
|
||||||
|
cleanup: () => rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('pending agent recharge exposes reject action for admins with rejection permission', async () => {
|
||||||
|
const { module, cleanup } = await importBundledActions()
|
||||||
|
const { buildAgentRechargeActions } = module
|
||||||
|
const row = {
|
||||||
|
id: 42,
|
||||||
|
status: 1,
|
||||||
|
payment_method: 'wechat',
|
||||||
|
payment_voucher_key: []
|
||||||
|
}
|
||||||
|
const handledRows = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const actions = buildAgentRechargeActions(row, {
|
||||||
|
hasAuth: (permission) => permission === 'agent_recharge:reject',
|
||||||
|
onViewPaymentVoucher: () => {},
|
||||||
|
onConfirmPayment: () => {},
|
||||||
|
onReject: (recharge) => handledRows.push(recharge)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
actions.map((action) => action.label),
|
||||||
|
['拒绝']
|
||||||
|
)
|
||||||
|
|
||||||
|
actions[0].handler()
|
||||||
|
|
||||||
|
assert.deepEqual(handledRows, [row])
|
||||||
|
} finally {
|
||||||
|
await cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('non-pending agent recharge does not expose reject action', async () => {
|
||||||
|
const { module, cleanup } = await importBundledActions()
|
||||||
|
const { buildAgentRechargeActions } = module
|
||||||
|
const row = {
|
||||||
|
id: 42,
|
||||||
|
status: 2,
|
||||||
|
payment_method: 'wechat',
|
||||||
|
payment_voucher_key: []
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const actions = buildAgentRechargeActions(row, {
|
||||||
|
hasAuth: (permission) => permission === 'agent_recharge:reject',
|
||||||
|
onViewPaymentVoucher: () => {},
|
||||||
|
onConfirmPayment: () => {},
|
||||||
|
onReject: () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
actions.map((action) => action.label),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
49
scripts/tests/agentRechargeDisplay.test.mjs
Normal file
49
scripts/tests/agentRechargeDisplay.test.mjs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { mkdtemp, rm } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
|
||||||
|
async function importBundledDisplay() {
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), 'agent-recharge-display-'))
|
||||||
|
const bundlePath = join(tempDir, 'agentRechargeDisplay.mjs')
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ['src/views/finance/agent-recharge/agentRechargeDisplay.ts'],
|
||||||
|
bundle: true,
|
||||||
|
outfile: bundlePath,
|
||||||
|
format: 'esm',
|
||||||
|
platform: 'node',
|
||||||
|
absWorkingDir: process.cwd()
|
||||||
|
})
|
||||||
|
|
||||||
|
const module = await import(pathToFileURL(bundlePath).href)
|
||||||
|
return {
|
||||||
|
module,
|
||||||
|
cleanup: () => rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('agent recharge rejection reason display uses backend rejection_reason when present', async () => {
|
||||||
|
const { module, cleanup } = await importBundledDisplay()
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.equal(module.formatRejectionReason('凭证不清晰'), '凭证不清晰')
|
||||||
|
} finally {
|
||||||
|
await cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('agent recharge rejection reason display falls back to dash when absent', async () => {
|
||||||
|
const { module, cleanup } = await importBundledDisplay()
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.equal(module.formatRejectionReason(''), '-')
|
||||||
|
assert.equal(module.formatRejectionReason(null), '-')
|
||||||
|
assert.equal(module.formatRejectionReason(undefined), '-')
|
||||||
|
} finally {
|
||||||
|
await cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
69
scripts/tests/agentRechargeService.test.mjs
Normal file
69
scripts/tests/agentRechargeService.test.mjs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
|
||||||
|
test('agent recharge rejection posts the rejection reason to the order rejection endpoint', async () => {
|
||||||
|
const calls = []
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), 'agent-recharge-service-'))
|
||||||
|
const bundlePath = join(tempDir, 'agentRechargeService.mjs')
|
||||||
|
const httpMockPath = join(tempDir, 'httpMock.mjs')
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
httpMockPath,
|
||||||
|
`
|
||||||
|
export const calls = globalThis.__agentRechargeHttpCalls
|
||||||
|
export default {
|
||||||
|
post(options) {
|
||||||
|
calls.push(options)
|
||||||
|
return Promise.resolve({ code: 0, data: undefined })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
globalThis.__agentRechargeHttpCalls = calls
|
||||||
|
|
||||||
|
try {
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ['src/api/modules/agentRecharge.ts'],
|
||||||
|
bundle: true,
|
||||||
|
outfile: bundlePath,
|
||||||
|
format: 'esm',
|
||||||
|
platform: 'node',
|
||||||
|
absWorkingDir: process.cwd(),
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'mock-http-boundary',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^@\/utils\/http$/ }, () => ({
|
||||||
|
path: httpMockPath
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const { AgentRechargeService } = await import(pathToFileURL(bundlePath).href)
|
||||||
|
|
||||||
|
await AgentRechargeService.rejectAgentRecharge(42, {
|
||||||
|
rejection_reason: '支付凭证不符合要求'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
{
|
||||||
|
url: '/api/admin/agent-recharges/42/reject',
|
||||||
|
data: {
|
||||||
|
rejection_reason: '支付凭证不符合要求'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
} finally {
|
||||||
|
delete globalThis.__agentRechargeHttpCalls
|
||||||
|
await rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
AgentRechargeListResponse,
|
AgentRechargeListResponse,
|
||||||
CreateAgentRechargeRequest,
|
CreateAgentRechargeRequest,
|
||||||
ConfirmOfflinePaymentRequest,
|
ConfirmOfflinePaymentRequest,
|
||||||
|
RejectAgentRechargeRequest,
|
||||||
BaseResponse
|
BaseResponse
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
@@ -55,4 +56,16 @@ export class AgentRechargeService extends BaseService {
|
|||||||
data
|
data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拒绝代理充值订单
|
||||||
|
* @param id 充值订单ID
|
||||||
|
* @param data 拒绝请求参数
|
||||||
|
*/
|
||||||
|
static rejectAgentRecharge(
|
||||||
|
id: number,
|
||||||
|
data: RejectAgentRechargeRequest
|
||||||
|
): Promise<BaseResponse<void>> {
|
||||||
|
return this.post<BaseResponse<void>>(`/api/admin/agent-recharges/${id}/reject`, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,9 +165,8 @@ export class AssetService extends BaseService {
|
|||||||
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
||||||
*/
|
*/
|
||||||
static stopAsset(identifier: string): Promise<BaseResponse<DeviceStopResponse | void>> {
|
static stopAsset(identifier: string): Promise<BaseResponse<DeviceStopResponse | void>> {
|
||||||
return this.post<BaseResponse<DeviceStopResponse | void>>(
|
return runRateLimitedAssetAction('stop', identifier, () =>
|
||||||
`/api/admin/assets/${identifier}/stop`,
|
this.post<BaseResponse<DeviceStopResponse | void>>(`/api/admin/assets/${identifier}/stop`, {})
|
||||||
{}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface AgentRecharge {
|
|||||||
payment_config_id: number | null
|
payment_config_id: number | null
|
||||||
payment_transaction_id: string
|
payment_transaction_id: string
|
||||||
payment_voucher_key?: string[] | string // 凭证附件列表;历史数据可能为单字符串或逗号字符串
|
payment_voucher_key?: string[] | string // 凭证附件列表;历史数据可能为单字符串或逗号字符串
|
||||||
|
rejection_reason?: string | null // 拒绝原因
|
||||||
remark?: string // 运营备注
|
remark?: string // 运营备注
|
||||||
created_at: string
|
created_at: string
|
||||||
paid_at: string | null
|
paid_at: string | null
|
||||||
@@ -72,3 +73,8 @@ export interface CreateAgentRechargeRequest {
|
|||||||
export interface ConfirmOfflinePaymentRequest {
|
export interface ConfirmOfflinePaymentRequest {
|
||||||
operation_password: string
|
operation_password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拒绝代理充值订单请求
|
||||||
|
export interface RejectAgentRechargeRequest {
|
||||||
|
rejection_reason: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const FIVE_MINUTES_MS = 5 * 60 * 1000
|
const FIVE_MINUTES_MS = 5 * 60 * 1000
|
||||||
const STORAGE_KEY_PREFIX = 'asset-action-rate-limit'
|
const STORAGE_KEY_PREFIX = 'asset-action-rate-limit'
|
||||||
|
|
||||||
export type AssetRateLimitedAction = 'refresh' | 'start'
|
export type AssetRateLimitedAction = 'refresh' | 'start' | 'stop'
|
||||||
|
|
||||||
export class FrontendRateLimitError extends Error {
|
export class FrontendRateLimitError extends Error {
|
||||||
readonly remainingMs: number
|
readonly remainingMs: number
|
||||||
|
|||||||
@@ -184,7 +184,7 @@
|
|||||||
<el-icon
|
<el-icon
|
||||||
class="copy-icon"
|
class="copy-icon"
|
||||||
@click="handleCopyICCID(scope.row.iccid)"
|
@click="handleCopyICCID(scope.row.iccid)"
|
||||||
style=" color: var(--el-color-primary);cursor: pointer"
|
style="color: var(--el-color-primary); cursor: pointer"
|
||||||
>
|
>
|
||||||
<CopyDocument />
|
<CopyDocument />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@@ -239,20 +239,22 @@
|
|||||||
type="success"
|
type="success"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
|
:disabled="isBindingStartDisabled(scope.row)"
|
||||||
@click="handleEnableBindingCard(scope.row)"
|
@click="handleEnableBindingCard(scope.row)"
|
||||||
v-permission="'iot_info:start'"
|
v-permission="'iot_info:start'"
|
||||||
>
|
>
|
||||||
启用此卡
|
{{ getBindingStartButtonText(scope.row) }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="scope.row.network_status === 1"
|
v-if="scope.row.network_status === 1"
|
||||||
type="warning"
|
type="warning"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
|
:disabled="isBindingStopDisabled(scope.row)"
|
||||||
@click="handleDisableBindingCard(scope.row)"
|
@click="handleDisableBindingCard(scope.row)"
|
||||||
v-permission="'iot_info:stop'"
|
v-permission="'iot_info:stop'"
|
||||||
>
|
>
|
||||||
停用此卡
|
{{ getBindingStopButtonText(scope.row) }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -397,17 +399,19 @@
|
|||||||
v-if="cardInfo?.network_status === 0"
|
v-if="cardInfo?.network_status === 0"
|
||||||
v-permission="'iot_info:start'"
|
v-permission="'iot_info:start'"
|
||||||
type="success"
|
type="success"
|
||||||
|
:disabled="startDisabled"
|
||||||
@click="handleEnableCard"
|
@click="handleEnableCard"
|
||||||
>
|
>
|
||||||
启用此卡
|
{{ startRemainingText ? `启用此卡(${startRemainingText})` : '启用此卡' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="cardInfo?.network_status === 1"
|
v-if="cardInfo?.network_status === 1"
|
||||||
v-permission="'iot_info:stop'"
|
v-permission="'iot_info:stop'"
|
||||||
type="warning"
|
type="warning"
|
||||||
|
:disabled="stopDisabled"
|
||||||
@click="handleDisableCard"
|
@click="handleDisableCard"
|
||||||
>
|
>
|
||||||
停用此卡
|
{{ stopRemainingText ? `停用此卡(${stopRemainingText})` : '停用此卡' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<!--<ElButton type="danger" v-permission="'iot_info:handler_stop'" @click="handleManualDeactivate"> 手动停用 </ElButton>-->
|
<!--<ElButton type="danger" v-permission="'iot_info:handler_stop'" @click="handleManualDeactivate"> 手动停用 </ElButton>-->
|
||||||
<!-- 手动停用暂时不开放 -->
|
<!-- 手动停用暂时不开放 -->
|
||||||
@@ -580,10 +584,22 @@
|
|||||||
deviceRealtime: DeviceRealtimeInfo | null
|
deviceRealtime: DeviceRealtimeInfo | null
|
||||||
pollingEnabled: boolean
|
pollingEnabled: boolean
|
||||||
realtimeLoading?: boolean
|
realtimeLoading?: boolean
|
||||||
|
startDisabled?: boolean
|
||||||
|
stopDisabled?: boolean
|
||||||
|
startRemainingText?: string
|
||||||
|
stopRemainingText?: string
|
||||||
|
getStartRemainingText?: (identifier: string) => string
|
||||||
|
getStopRemainingText?: (identifier: string) => string
|
||||||
|
isStartDisabled?: (identifier: string) => boolean
|
||||||
|
isStopDisabled?: (identifier: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
realtimeLoading: false
|
realtimeLoading: false,
|
||||||
|
startDisabled: false,
|
||||||
|
stopDisabled: false,
|
||||||
|
startRemainingText: '',
|
||||||
|
stopRemainingText: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
@@ -717,17 +733,15 @@
|
|||||||
|
|
||||||
// 卡操作
|
// 卡操作
|
||||||
const handleEnableCard = () => {
|
const handleEnableCard = () => {
|
||||||
|
if (props.startDisabled) return
|
||||||
emit('enableCard')
|
emit('enableCard')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisableCard = () => {
|
const handleDisableCard = () => {
|
||||||
|
if (props.stopDisabled) return
|
||||||
emit('disableCard')
|
emit('disableCard')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleManualDeactivate = () => {
|
|
||||||
emit('manualDeactivate')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备操作
|
// 设备操作
|
||||||
const handleRebootDevice = () => {
|
const handleRebootDevice = () => {
|
||||||
emit('rebootDevice')
|
emit('rebootDevice')
|
||||||
@@ -758,7 +772,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 绑定卡操作
|
// 绑定卡操作
|
||||||
|
const getBindingStartRemainingText = (card: BindingCard) =>
|
||||||
|
props.getStartRemainingText?.(card.iccid) || ''
|
||||||
|
|
||||||
|
const getBindingStopRemainingText = (card: BindingCard) =>
|
||||||
|
props.getStopRemainingText?.(card.iccid) || ''
|
||||||
|
|
||||||
|
const isBindingStartDisabled = (card: BindingCard) => props.isStartDisabled?.(card.iccid) || false
|
||||||
|
|
||||||
|
const isBindingStopDisabled = (card: BindingCard) => props.isStopDisabled?.(card.iccid) || false
|
||||||
|
|
||||||
|
const getBindingStartButtonText = (card: BindingCard) => {
|
||||||
|
const remainingText = getBindingStartRemainingText(card)
|
||||||
|
return remainingText ? `启用此卡(${remainingText})` : '启用此卡'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBindingStopButtonText = (card: BindingCard) => {
|
||||||
|
const remainingText = getBindingStopRemainingText(card)
|
||||||
|
return remainingText ? `停用此卡(${remainingText})` : '停用此卡'
|
||||||
|
}
|
||||||
|
|
||||||
const handleEnableBindingCard = (card: BindingCard) => {
|
const handleEnableBindingCard = (card: BindingCard) => {
|
||||||
|
if (isBindingStartDisabled(card)) return
|
||||||
ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', {
|
ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
@@ -771,6 +806,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDisableBindingCard = (card: BindingCard) => {
|
const handleDisableBindingCard = (card: BindingCard) => {
|
||||||
|
if (isBindingStopDisabled(card)) return
|
||||||
ElMessageBox.confirm(`确定要停用此卡(${card.iccid})吗?`, '停用确认', {
|
ElMessageBox.confirm(`确定要停用此卡(${card.iccid})吗?`, '停用确认', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用失败:', error)
|
console.error('停用失败:', error)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -20,6 +20,14 @@
|
|||||||
:device-realtime="deviceRealtime"
|
:device-realtime="deviceRealtime"
|
||||||
v-model:polling-enabled="pollingEnabled"
|
v-model:polling-enabled="pollingEnabled"
|
||||||
:realtime-loading="realtimeLoading"
|
:realtime-loading="realtimeLoading"
|
||||||
|
:start-disabled="startDisabled"
|
||||||
|
:stop-disabled="stopDisabled"
|
||||||
|
:start-remaining-text="startRemainingText"
|
||||||
|
:stop-remaining-text="stopRemainingText"
|
||||||
|
:get-start-remaining-text="getStartRemainingText"
|
||||||
|
:get-stop-remaining-text="getStopRemainingText"
|
||||||
|
:is-start-disabled="isStartActionDisabled"
|
||||||
|
:is-stop-disabled="isStopActionDisabled"
|
||||||
@enable-card="handleEnableCard"
|
@enable-card="handleEnableCard"
|
||||||
@disable-card="handleDisableCard"
|
@disable-card="handleDisableCard"
|
||||||
@manual-deactivate="handleManualDeactivate"
|
@manual-deactivate="handleManualDeactivate"
|
||||||
@@ -176,7 +184,8 @@
|
|||||||
import {
|
import {
|
||||||
formatRemainingTime,
|
formatRemainingTime,
|
||||||
FrontendRateLimitError,
|
FrontendRateLimitError,
|
||||||
getAssetActionRateLimitState
|
getAssetActionRateLimitState,
|
||||||
|
type AssetRateLimitedAction
|
||||||
} from '@/utils/business/apiRateLimit'
|
} from '@/utils/business/apiRateLimit'
|
||||||
|
|
||||||
// 引入子组件
|
// 引入子组件
|
||||||
@@ -248,6 +257,8 @@
|
|||||||
|
|
||||||
// ========== 资产操作方法(使用 composable) ==========
|
// ========== 资产操作方法(使用 composable) ==========
|
||||||
const {
|
const {
|
||||||
|
enableCardLoading,
|
||||||
|
disableCardLoading,
|
||||||
enableCard: enableCardOp,
|
enableCard: enableCardOp,
|
||||||
disableCard: disableCardOp,
|
disableCard: disableCardOp,
|
||||||
manualDeactivateCard: manualDeactivateCardOp,
|
manualDeactivateCard: manualDeactivateCardOp,
|
||||||
@@ -342,6 +353,59 @@
|
|||||||
: ''
|
: ''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const startRateLimitState = computed(() => {
|
||||||
|
const identifier = cardInfo.value?.iccid || cardInfo.value?.identifier
|
||||||
|
if (!identifier) {
|
||||||
|
return {
|
||||||
|
limited: false,
|
||||||
|
remainingMs: 0,
|
||||||
|
retryAt: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAssetActionRateLimitState('start', identifier, rateLimitNow.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const stopRateLimitState = computed(() => {
|
||||||
|
const identifier = cardInfo.value?.iccid || cardInfo.value?.identifier
|
||||||
|
if (!identifier) {
|
||||||
|
return {
|
||||||
|
limited: false,
|
||||||
|
remainingMs: 0,
|
||||||
|
retryAt: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAssetActionRateLimitState('stop', identifier, rateLimitNow.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const startDisabled = computed(() => enableCardLoading.value || startRateLimitState.value.limited)
|
||||||
|
const stopDisabled = computed(() => disableCardLoading.value || stopRateLimitState.value.limited)
|
||||||
|
const startRemainingText = computed(() =>
|
||||||
|
startRateLimitState.value.limited
|
||||||
|
? formatRemainingTime(startRateLimitState.value.remainingMs)
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
const stopRemainingText = computed(() =>
|
||||||
|
stopRateLimitState.value.limited
|
||||||
|
? formatRemainingTime(stopRateLimitState.value.remainingMs)
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
|
||||||
|
const getActionRemainingText = (action: AssetRateLimitedAction, identifier?: string) => {
|
||||||
|
if (!identifier) return ''
|
||||||
|
const state = getAssetActionRateLimitState(action, identifier, rateLimitNow.value)
|
||||||
|
return state.limited ? formatRemainingTime(state.remainingMs) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActionDisabled = (action: AssetRateLimitedAction, identifier?: string) => {
|
||||||
|
if (!identifier) return false
|
||||||
|
return getAssetActionRateLimitState(action, identifier, rateLimitNow.value).limited
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStartRemainingText = (identifier: string) => getActionRemainingText('start', identifier)
|
||||||
|
const getStopRemainingText = (identifier: string) => getActionRemainingText('stop', identifier)
|
||||||
|
const isStartActionDisabled = (identifier: string) => isActionDisabled('start', identifier)
|
||||||
|
const isStopActionDisabled = (identifier: string) => isActionDisabled('stop', identifier)
|
||||||
|
|
||||||
const updateRateLimitNow = () => {
|
const updateRateLimitNow = () => {
|
||||||
rateLimitNow.value = Date.now()
|
rateLimitNow.value = Date.now()
|
||||||
}
|
}
|
||||||
@@ -399,14 +463,24 @@
|
|||||||
* IoT卡操作 - 启用卡
|
* IoT卡操作 - 启用卡
|
||||||
*/
|
*/
|
||||||
const handleEnableCard = async () => {
|
const handleEnableCard = async () => {
|
||||||
await enableCardOp()
|
if (startDisabled.value) return
|
||||||
|
try {
|
||||||
|
await enableCardOp()
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IoT卡操作 - 停用卡
|
* IoT卡操作 - 停用卡
|
||||||
*/
|
*/
|
||||||
const handleDisableCard = async () => {
|
const handleDisableCard = async () => {
|
||||||
await disableCardOp()
|
if (stopDisabled.value) return
|
||||||
|
try {
|
||||||
|
await disableCardOp()
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -550,6 +624,7 @@
|
|||||||
*/
|
*/
|
||||||
const handleEnableBindingCard = async (payload: { card: any }) => {
|
const handleEnableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
|
if (isStartActionDisabled(card.iccid)) return
|
||||||
try {
|
try {
|
||||||
const { AssetService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await AssetService.startAsset(card.iccid)
|
await AssetService.startAsset(card.iccid)
|
||||||
@@ -562,6 +637,8 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error('启用绑定卡失败:', error)
|
console.error('启用绑定卡失败:', error)
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +647,7 @@
|
|||||||
*/
|
*/
|
||||||
const handleDisableBindingCard = async (payload: { card: any }) => {
|
const handleDisableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
|
if (isStopActionDisabled(card.iccid)) return
|
||||||
try {
|
try {
|
||||||
const { AssetService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await AssetService.stopAsset(card.iccid)
|
await AssetService.stopAsset(card.iccid)
|
||||||
@@ -577,7 +655,13 @@
|
|||||||
if (cardInfo.value)
|
if (cardInfo.value)
|
||||||
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用绑定卡失败:', error)
|
console.error('停用绑定卡失败:', error)
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2920,6 +2920,10 @@
|
|||||||
await getTableData()
|
await getTableData()
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用此卡失败:', error)
|
console.error('停用此卡失败:', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
56
src/views/finance/agent-recharge/agentRechargeActions.ts
Normal file
56
src/views/finance/agent-recharge/agentRechargeActions.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { AgentRechargeStatus, type AgentRecharge } from '@/types/api'
|
||||||
|
import { hasVoucherKeys } from '@/utils/business'
|
||||||
|
|
||||||
|
export interface AgentRechargeAction {
|
||||||
|
label: string
|
||||||
|
handler: () => void
|
||||||
|
type: 'primary' | 'danger'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BuildAgentRechargeActionsOptions {
|
||||||
|
hasAuth: (permission: string) => boolean
|
||||||
|
onViewPaymentVoucher: (row: AgentRecharge) => void
|
||||||
|
onConfirmPayment: (row: AgentRecharge) => void
|
||||||
|
onReject: (row: AgentRecharge) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildAgentRechargeActions = (
|
||||||
|
row: AgentRecharge,
|
||||||
|
options: BuildAgentRechargeActionsOptions
|
||||||
|
): AgentRechargeAction[] => {
|
||||||
|
const actions: AgentRechargeAction[] = []
|
||||||
|
|
||||||
|
if (
|
||||||
|
row.payment_method === 'offline' &&
|
||||||
|
hasVoucherKeys(row.payment_voucher_key) &&
|
||||||
|
options.hasAuth('agent_recharge:view_payment_voucher')
|
||||||
|
) {
|
||||||
|
actions.push({
|
||||||
|
label: '查看支付凭证',
|
||||||
|
handler: () => options.onViewPaymentVoucher(row),
|
||||||
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
row.status === AgentRechargeStatus.PENDING &&
|
||||||
|
row.payment_method === 'offline' &&
|
||||||
|
options.hasAuth('agent_recharge:confirm_payment')
|
||||||
|
) {
|
||||||
|
actions.push({
|
||||||
|
label: '确认支付',
|
||||||
|
handler: () => options.onConfirmPayment(row),
|
||||||
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.status === AgentRechargeStatus.PENDING && options.hasAuth('agent_recharge:reject')) {
|
||||||
|
actions.push({
|
||||||
|
label: '拒绝',
|
||||||
|
handler: () => options.onReject(row),
|
||||||
|
type: 'danger'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
}
|
||||||
3
src/views/finance/agent-recharge/agentRechargeDisplay.ts
Normal file
3
src/views/finance/agent-recharge/agentRechargeDisplay.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const formatRejectionReason = (reason?: string | null): string => {
|
||||||
|
return reason || '-'
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
||||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||||
|
import { formatRejectionReason } from './agentRechargeDisplay'
|
||||||
|
|
||||||
defineOptions({ name: 'AgentRechargeDetail' })
|
defineOptions({ name: 'AgentRechargeDetail' })
|
||||||
|
|
||||||
@@ -111,6 +112,12 @@
|
|||||||
h(ElTag, { type: getStatusType(data.status) }, () =>
|
h(ElTag, { type: getStatusType(data.status) }, () =>
|
||||||
getStatusText(data.status, data.status_name)
|
getStatusText(data.status, data.status_name)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '驳回原因',
|
||||||
|
prop: 'rejection_reason',
|
||||||
|
formatter: (value) => formatRejectionReason(value),
|
||||||
|
fullWidth: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -167,6 +167,38 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 拒绝充值订单对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="rejectDialogVisible"
|
||||||
|
title="拒绝充值订单"
|
||||||
|
width="500px"
|
||||||
|
@closed="handleRejectDialogClosed"
|
||||||
|
>
|
||||||
|
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
|
||||||
|
<ElFormItem label="充值单号">
|
||||||
|
<span>{{ currentRecharge?.recharge_no }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="拒绝原因" prop="rejection_reason">
|
||||||
|
<ElInput
|
||||||
|
v-model="rejectForm.rejection_reason"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入拒绝原因"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="rejectDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="danger" @click="handleRejectRecharge" :loading="rejectLoading">
|
||||||
|
确认拒绝
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 支付凭证预览 -->
|
<!-- 支付凭证预览 -->
|
||||||
<PaymentVoucherDialog
|
<PaymentVoucherDialog
|
||||||
:file-keys="paymentVoucherFileKeys"
|
:file-keys="paymentVoucherFileKeys"
|
||||||
@@ -198,7 +230,8 @@
|
|||||||
AgentRechargeStatus,
|
AgentRechargeStatus,
|
||||||
AgentRechargePaymentMethod,
|
AgentRechargePaymentMethod,
|
||||||
CreateAgentRechargeRequest,
|
CreateAgentRechargeRequest,
|
||||||
ConfirmOfflinePaymentRequest
|
ConfirmOfflinePaymentRequest,
|
||||||
|
RejectAgentRechargeRequest
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
@@ -207,6 +240,8 @@
|
|||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||||
|
import { buildAgentRechargeActions } from './agentRechargeActions'
|
||||||
|
import { formatRejectionReason } from './agentRechargeDisplay'
|
||||||
|
|
||||||
defineOptions({ name: 'AgentRechargeList' })
|
defineOptions({ name: 'AgentRechargeList' })
|
||||||
|
|
||||||
@@ -217,9 +252,11 @@
|
|||||||
const createLoading = ref(false)
|
const createLoading = ref(false)
|
||||||
const voucherUploading = ref(false)
|
const voucherUploading = ref(false)
|
||||||
const confirmPayLoading = ref(false)
|
const confirmPayLoading = ref(false)
|
||||||
|
const rejectLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const createDialogVisible = ref(false)
|
const createDialogVisible = ref(false)
|
||||||
const confirmPayDialogVisible = ref(false)
|
const confirmPayDialogVisible = ref(false)
|
||||||
|
const rejectDialogVisible = ref(false)
|
||||||
const currentRecharge = ref<AgentRecharge | null>(null)
|
const currentRecharge = ref<AgentRecharge | null>(null)
|
||||||
const paymentVoucherFileKeys = ref<string[]>([])
|
const paymentVoucherFileKeys = ref<string[]>([])
|
||||||
|
|
||||||
@@ -330,6 +367,7 @@
|
|||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '支付方式', prop: 'payment_method' },
|
{ label: '支付方式', prop: 'payment_method' },
|
||||||
{ label: '支付通道', prop: 'payment_channel' },
|
{ label: '支付通道', prop: 'payment_channel' },
|
||||||
|
{ label: '驳回原因', prop: 'rejection_reason' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
{ label: '支付时间', prop: 'paid_at' },
|
{ label: '支付时间', prop: 'paid_at' },
|
||||||
{ label: '完成时间', prop: 'completed_at' }
|
{ label: '完成时间', prop: 'completed_at' }
|
||||||
@@ -337,6 +375,7 @@
|
|||||||
|
|
||||||
const createFormRef = ref<FormInstance>()
|
const createFormRef = ref<FormInstance>()
|
||||||
const confirmPayFormRef = ref<FormInstance>()
|
const confirmPayFormRef = ref<FormInstance>()
|
||||||
|
const rejectFormRef = ref<FormInstance>()
|
||||||
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
const uploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||||
const MIN_RECHARGE_AMOUNT = 0.01
|
const MIN_RECHARGE_AMOUNT = 0.01
|
||||||
|
|
||||||
@@ -374,6 +413,10 @@
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rejectRules = reactive<FormRules>({
|
||||||
|
rejection_reason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
const createForm = reactive<{
|
const createForm = reactive<{
|
||||||
amount: number
|
amount: number
|
||||||
payment_method: string
|
payment_method: string
|
||||||
@@ -392,6 +435,10 @@
|
|||||||
operation_password: ''
|
operation_password: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rejectForm = reactive<RejectAgentRechargeRequest>({
|
||||||
|
rejection_reason: ''
|
||||||
|
})
|
||||||
|
|
||||||
const rechargeList = ref<AgentRecharge[]>([])
|
const rechargeList = ref<AgentRecharge[]>([])
|
||||||
|
|
||||||
// 格式化货币 - 将分转换为元
|
// 格式化货币 - 将分转换为元
|
||||||
@@ -496,6 +543,13 @@
|
|||||||
showOverflowTooltip: true,
|
showOverflowTooltip: true,
|
||||||
formatter: (row: AgentRecharge) => row.remark || '-'
|
formatter: (row: AgentRecharge) => row.remark || '-'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'rejection_reason',
|
||||||
|
label: '驳回原因',
|
||||||
|
minWidth: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: AgentRecharge) => formatRejectionReason(row.rejection_reason)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'created_at',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
@@ -735,6 +789,44 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示拒绝对话框
|
||||||
|
const handleShowReject = (row: AgentRecharge) => {
|
||||||
|
currentRecharge.value = row
|
||||||
|
rejectDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拒绝对话框关闭后的清理
|
||||||
|
const handleRejectDialogClosed = () => {
|
||||||
|
rejectFormRef.value?.resetFields()
|
||||||
|
rejectForm.rejection_reason = ''
|
||||||
|
currentRecharge.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拒绝代理充值订单
|
||||||
|
const handleRejectRecharge = async () => {
|
||||||
|
const formRef = rejectFormRef.value
|
||||||
|
const recharge = currentRecharge.value
|
||||||
|
if (!formRef || !recharge) return
|
||||||
|
|
||||||
|
await formRef.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
rejectLoading.value = true
|
||||||
|
try {
|
||||||
|
await AgentRechargeService.rejectAgentRecharge(recharge.id, {
|
||||||
|
rejection_reason: rejectForm.rejection_reason
|
||||||
|
})
|
||||||
|
ElMessage.success('拒绝成功')
|
||||||
|
rejectDialogVisible.value = false
|
||||||
|
await getTableData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
rejectLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 处理名称点击
|
// 处理名称点击
|
||||||
const handleNameClick = (row: AgentRecharge) => {
|
const handleNameClick = (row: AgentRecharge) => {
|
||||||
if (hasAuth('agent_recharge:detail_page')) {
|
if (hasAuth('agent_recharge:detail_page')) {
|
||||||
@@ -754,35 +846,12 @@
|
|||||||
|
|
||||||
// 获取操作按钮
|
// 获取操作按钮
|
||||||
const getActions = (row: AgentRecharge) => {
|
const getActions = (row: AgentRecharge) => {
|
||||||
const actions: any[] = []
|
return buildAgentRechargeActions(row, {
|
||||||
|
hasAuth,
|
||||||
// 线下支付且有凭证可以查看
|
onViewPaymentVoucher: handleViewPaymentVoucher,
|
||||||
if (
|
onConfirmPayment: handleShowConfirmPay,
|
||||||
row.payment_method === 'offline' &&
|
onReject: handleShowReject
|
||||||
hasVoucherKeys(row.payment_voucher_key) &&
|
})
|
||||||
hasAuth('agent_recharge:view_payment_voucher')
|
|
||||||
) {
|
|
||||||
actions.push({
|
|
||||||
label: '查看支付凭证',
|
|
||||||
handler: () => handleViewPaymentVoucher(row),
|
|
||||||
type: 'primary'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 待支付且线下转账的订单可以确认支付
|
|
||||||
if (
|
|
||||||
row.status === 1 &&
|
|
||||||
row.payment_method === 'offline' &&
|
|
||||||
hasAuth('agent_recharge:confirm_payment')
|
|
||||||
) {
|
|
||||||
actions.push({
|
|
||||||
label: '确认支付',
|
|
||||||
handler: () => handleShowConfirmPay(row),
|
|
||||||
type: 'primary'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看支付凭证
|
// 查看支付凭证
|
||||||
|
|||||||
Reference in New Issue
Block a user