diff --git a/src/api/modules/asset.ts b/src/api/modules/asset.ts index b28c6ea..8ffb13f 100644 --- a/src/api/modules/asset.ts +++ b/src/api/modules/asset.ts @@ -201,4 +201,19 @@ export class AssetService extends BaseService { params ) } + + /** + * 更新资产实名认证策略 + * PATCH /api/admin/assets/:identifier/realname-mode + * @param identifier 资产标识符(ICCID 或 VirtualNo) + * @param realname_policy 实名认证策略 (none:无需实名, before_order:先实名后充值/购买, after_order:先充值/购买后实名) + */ + static updateRealnamePolicy( + identifier: string, + realname_policy: string + ): Promise { + return this.patch(`/api/admin/assets/${identifier}/realname-mode`, { + realname_policy + }) + } } diff --git a/src/components/device/RealnamePolicyDialog.vue b/src/components/device/RealnamePolicyDialog.vue new file mode 100644 index 0000000..94591da --- /dev/null +++ b/src/components/device/RealnamePolicyDialog.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/types/api/device.ts b/src/types/api/device.ts index 360a11e..6ebda84 100644 --- a/src/types/api/device.ts +++ b/src/types/api/device.ts @@ -53,6 +53,7 @@ export interface Device { last_gateway_sync_at?: string | null // 最后 sync-info 同步时间 imei?: string // IMEI(设备国际移动设备识别码) sn?: string // 设备序列号 + realname_policy?: string // 实名认证策略 } // 设备查询参数 diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 3761f1e..0ba3055 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -138,6 +138,7 @@ declare module 'vue' { MenuStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue')['default'] OperatorSelect: typeof import('./../components/business/OperatorSelect.vue')['default'] PackageSelector: typeof import('./../components/business/PackageSelector.vue')['default'] + RealnamePolicyDialog: typeof import('./../components/device/RealnamePolicyDialog.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SectionTitle: typeof import('./../components/core/layouts/art-settings-panel/widget/SectionTitle.vue')['default'] diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue index d143501..41f92eb 100644 --- a/src/views/asset-management/asset-information/components/BasicInfoCard.vue +++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue @@ -197,11 +197,6 @@ - - - @@ -420,6 +417,7 @@ network_status?: number real_name_status?: number real_name_at?: string + realname_policy?: string } interface AssetInfo { @@ -509,6 +507,7 @@ (e: 'showSwitchCard'): void (e: 'showSwitchMode'): void (e: 'show-set-wifi'): void + (e: 'showRealnamePolicy'): void (e: 'enableBindingCard', payload: { card: BindingCard }): void (e: 'disableBindingCard', payload: { card: BindingCard }): void (e: 'navigateToCard', iccid: string): void @@ -610,6 +609,10 @@ emit('show-set-wifi') } + const handleShowRealnamePolicy = () => { + emit('showRealnamePolicy') + } + // 绑定卡操作 const handleEnableBindingCard = (card: BindingCard) => { ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', { diff --git a/src/views/asset-management/asset-information/components/CurrentPackageCard.vue b/src/views/asset-management/asset-information/components/CurrentPackageCard.vue index eb5e40e..cd082a7 100644 --- a/src/views/asset-management/asset-information/components/CurrentPackageCard.vue +++ b/src/views/asset-management/asset-information/components/CurrentPackageCard.vue @@ -98,7 +98,10 @@ {{ getPackageTypeName(currentPackage.package_type) }} - + {{ currentPackage.virtual_ratio || '-' }} @@ -110,11 +113,6 @@ {{ formatDateTime(currentPackage.expire_time) || '-' }} - - - {{ currentPackage.status_name || '-' }} - - diff --git a/src/views/asset-management/asset-information/composables/useAssetFormatters.ts b/src/views/asset-management/asset-information/composables/useAssetFormatters.ts index f61a110..f3f94d9 100644 --- a/src/views/asset-management/asset-information/composables/useAssetFormatters.ts +++ b/src/views/asset-management/asset-information/composables/useAssetFormatters.ts @@ -67,13 +67,13 @@ export function useAssetFormatters(deviceRealtime?: Ref) { // 获取实名认证策略名称 const getRealnamePolicyName = (policy?: string) => { - if (!policy) return '-' + if (!policy || policy === '') return '未知' const policyMap: Record = { none: '无需实名', before_order: '先实名后充值/购买', after_order: '先充值/购买后实名' } - return policyMap[policy] || policy + return policyMap[policy] || '未知' } // 获取资产状态名称 diff --git a/src/views/asset-management/asset-information/index.vue b/src/views/asset-management/asset-information/index.vue index 8fec977..74944d2 100644 --- a/src/views/asset-management/asset-information/index.vue +++ b/src/views/asset-management/asset-information/index.vue @@ -25,6 +25,7 @@ @show-switch-card="showSwitchCardDialog" @show-switch-mode="showSwitchModeDialog" @show-set-wifi="showSetWiFiDialog" + @show-realname-policy="showRealnamePolicyDialog" @enable-binding-card="handleEnableBindingCard" @disable-binding-card="handleDisableBindingCard" @navigate-to-card="handleNavigateToCard" @@ -116,6 +117,12 @@ :asset-identifier="cardInfo?.identifier" :asset-type="cardInfo?.asset_type" /> + @@ -140,6 +147,7 @@ DailyRecordsDialog, OrderHistoryDialog } from './components/dialogs' + import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue' // 引入 composables import { useAssetInfo } from './composables/useAssetInfo' @@ -196,6 +204,7 @@ const rechargeDialogVisible = ref(false) const dailyRecordsDialogVisible = ref(false) const orderHistoryDialogVisible = ref(false) + const realnamePolicyDialogVisible = ref(false) const selectedPackageUsageId = ref(null) // ========== 套餐列表分页状态 ========== @@ -343,6 +352,34 @@ setWiFiDialogVisible.value = false } + /** + * 显示实名认证策略对话框 + */ + const showRealnamePolicyDialog = () => { + realnamePolicyDialogVisible.value = true + } + + /** + * 确认实名认证策略 + */ + const handleConfirmRealnamePolicy = async (form: { realname_policy: string }) => { + try { + const { AssetService } = await import('@/api/modules') + const identifier = cardInfo.value?.identifier || '' + const res = await AssetService.updateRealnamePolicy(identifier, form.realname_policy) + if (res.code === 0) { + ElMessage.success('设置实名认证策略成功') + realnamePolicyDialogVisible.value = false + await refreshAsset(cardInfo.value!.identifier, cardInfo.value!.asset_type) + } else { + ElMessage.error(res.msg || '设置实名认证策略失败') + } + } catch (error: any) { + console.error('设置实名认证策略失败:', error) + ElMessage.error('设置实名认证策略失败') + } + } + /** * 绑定卡操作 - 启用绑定卡 */ diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index 853f520..40b908f 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -592,6 +592,14 @@ + + + @@ -623,6 +631,7 @@ import { useAuth } from '@/composables/useAuth' import { formatDateTime } from '@/utils/business/format' import type { PackageSeriesResponse } from '@/types/api' + import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue' defineOptions({ name: 'DeviceList' }) @@ -755,6 +764,11 @@ switch_mode: [{ required: true, message: '请选择切卡模式', trigger: 'change' }] }) + // 实名认证策略相关 + const realnamePolicyDialogVisible = ref(false) + const currentRealnamePolicyAsset = ref('') + const currentRealnamePolicy = ref('none') + // 搜索表单初始值 const initialSearchState = { virtual_no: '', @@ -1601,6 +1615,35 @@ case 'switch-mode': showSwitchModeDialog(deviceNo, device?.switch_mode) break + case 'realname-policy': + showRealnamePolicyDialog(deviceNo, device?.realname_policy) + break + } + } + + // 显示实名认证策略对话框 + const showRealnamePolicyDialog = (deviceNo: string, currentPolicy?: string) => { + currentRealnamePolicyAsset.value = deviceNo + currentRealnamePolicy.value = currentPolicy ?? 'none' + realnamePolicyDialogVisible.value = true + } + + // 确认设置实名认证策略 + const handleConfirmRealnamePolicy = async (data: { realname_policy: string }) => { + try { + const res = await AssetService.updateRealnamePolicy( + currentRealnamePolicyAsset.value, + data.realname_policy + ) + if (res.code === 0) { + ElMessage.success('设置实名认证策略成功') + realnamePolicyDialogVisible.value = false + await getTableData() + } else { + ElMessage.error(res.msg || '设置失败') + } + } catch (error) { + console.error('设置实名认证策略失败:', error) } } @@ -1913,6 +1956,14 @@ }) } + if (hasAuth('asset:realname_policy')) { + moreActions.push({ + label: '实名认证策略', + handler: () => handleDeviceOperation('realname-policy', row.virtual_no, row), + type: 'primary' + }) + } + // if (hasAuth('device:manual_deactivate')) { // moreActions.push({ // label: '手动停用', diff --git a/src/views/asset-management/iot-card-management/index.vue b/src/views/asset-management/iot-card-management/index.vue index e679d42..683e296 100644 --- a/src/views/asset-management/iot-card-management/index.vue +++ b/src/views/asset-management/iot-card-management/index.vue @@ -545,6 +545,14 @@ :menu-width="180" @select="handleMoreMenuSelect" /> + + + @@ -557,6 +565,7 @@ import { CardService, ShopService, PackageSeriesService, AssetService } from '@/api/modules' import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus' import { Loading } from '@element-plus/icons-vue' + import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue' import type { FormInstance, FormRules } from 'element-plus' import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' @@ -636,6 +645,54 @@ const cardStatusLoading = ref(false) const cardStatusData = ref(null) + // 实名认证策略对话框 + const realnamePolicyDialogVisible = ref(false) + const currentRealnamePolicyIccid = ref('') + const currentRealnamePolicy = ref('none') + + // 显示实名认证策略对话框 + const handleShowRealnamePolicy = async (iccid: string, currentPolicy?: string) => { + currentRealnamePolicyIccid.value = iccid + if (currentPolicy !== undefined && currentPolicy !== null) { + currentRealnamePolicy.value = String(currentPolicy) + } else { + try { + const res = await AssetService.resolveAsset(iccid) + if (res.code === 0 && res.data) { + currentRealnamePolicy.value = res.data.realname_policy ?? 'none' + } else { + currentRealnamePolicy.value = 'none' + } + } catch { + currentRealnamePolicy.value = 'none' + } + } + realnamePolicyDialogVisible.value = true + } + + // 确认设置实名认证策略 + const handleConfirmRealnamePolicy = async (data: { realname_policy: string }) => { + if (!currentRealnamePolicyIccid.value) { + ElMessage.warning('未指定卡片') + return + } + try { + const res = await AssetService.updateRealnamePolicy( + currentRealnamePolicyIccid.value, + data.realname_policy + ) + if (res.code === 0) { + ElMessage.success('设置实名认证策略成功') + realnamePolicyDialogVisible.value = false + await getTableData() + } else { + ElMessage.error(res.msg || '设置失败') + } + } catch (error) { + console.error('设置实名认证策略失败:', error) + } + } + // 更多操作右键菜单 const moreMenuRef = ref>() @@ -1153,6 +1210,29 @@ }) } + if (hasAuth('asset:realname_policy')) { + actions.push({ + label: '实名认证策略', + handler: () => handleCardOperation('realname-policy', row.iccid, row), + type: 'primary' + }) + } + + // 更多操作放到"更多"下拉菜单中 + const moreActions: any[] = [] + + if (hasAuth('iot_card:clear_series')) { + moreActions.push({ + label: '清除关联', + handler: () => handleCardOperation('clear-series', row.iccid), + type: 'primary' + }) + } + + if (moreActions.length > 0) { + actions.push(...moreActions) + } + // if (hasAuth('iot_card:manual_deactivate')) { // actions.push({ // label: '手动停用', @@ -1524,13 +1604,6 @@ }) } - if (hasAuth('iot_card:clear_series')) { - items.push({ - key: 'clearSeries', - label: '清除关联' - }) - } - return items }) @@ -1632,7 +1705,7 @@ } // IoT卡操作处理函数 - const handleCardOperation = (command: string, iccid: string) => { + const handleCardOperation = (command: string, iccid: string, row?: any) => { switch (command) { case 'realname-status': showRealnameStatusDialog(iccid) @@ -1646,12 +1719,48 @@ case 'stop-card': handleStopCard(iccid) break + case 'realname-policy': + handleShowRealnamePolicy(iccid, row?.realname_policy) + break case 'manual-deactivate': handleManualDeactivate(iccid) break + case 'clear-series': + handleClearSingleCardSeries(iccid) + break } } + // 清除单卡套餐系列绑定 + const handleClearSingleCardSeries = (iccid: string) => { + ElMessageBox.confirm(`确定要清除该卡(${iccid})的套餐系列关联吗?`, '确认清除', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + .then(async () => { + try { + const res = await CardService.batchSetCardSeriesBinding({ + iccids: [iccid], + series_id: 0 + }) + if (res.code === 0) { + if (res.data.fail_count === 0) { + ElMessage.success('清除关联成功') + } else { + ElMessage.warning(`失败:${res.data.failed_items?.[0]?.reason || '未知原因'}`) + } + await getTableData() + } + } catch (error) { + console.error('清除关联失败:', error) + } + }) + .catch(() => { + // 用户取消 + }) + } + // 查询流量使用 const showFlowUsageDialog = async (iccid: string) => { flowUsageDialogVisible.value = true