This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
"tips": "提示",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"logOutTips": "您是否要退出登录?"
|
||||
"logOutTips": "您是否要退出登录?",
|
||||
"back": "返回"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "搜索页面",
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</ElDialog>
|
||||
|
||||
<!-- 分配角色对话框 -->
|
||||
<ElDialog v-model="roleDialogVisible" width="50%">
|
||||
<ElDialog v-model="roleDialogVisible" width="55%">
|
||||
<template #header>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">分配角色</span>
|
||||
@@ -219,7 +219,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { h, onActivated } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
FormInstance,
|
||||
@@ -589,6 +589,10 @@
|
||||
getAccountList()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
getAccountList()
|
||||
})
|
||||
|
||||
// 加载所有角色列表
|
||||
const loadAllRoles = async (keyword: string = '') => {
|
||||
try {
|
||||
@@ -973,7 +977,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
max-width: 380px;
|
||||
max-width: 440px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { h, onActivated } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { EnterpriseService, ShopService } from '@/api/modules'
|
||||
@@ -536,6 +536,10 @@
|
||||
}
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
getTableData()
|
||||
})
|
||||
|
||||
// 加载店铺列表 - 改用级联查询
|
||||
const loadShopList = async () => {
|
||||
shopLoading.value = true
|
||||
|
||||
@@ -5,19 +5,11 @@
|
||||
<ElCard shadow="never" class="enterprise-info-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>企业信息</span>
|
||||
<ElButton @click="goBack">返回</ElButton>
|
||||
<span>企业信息</span>
|
||||
<span v-if="enterpriseInfo"> - {{ enterpriseInfo.enterprise_name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ElDescriptions :column="3" border v-if="enterpriseInfo">
|
||||
<ElDescriptionsItem label="企业名称">{{
|
||||
enterpriseInfo.enterprise_name
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业编号">{{
|
||||
enterpriseInfo.enterprise_code
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系人">{{ enterpriseInfo.contact_name }}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
@@ -64,13 +56,9 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
@@ -78,9 +66,6 @@
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 授权卡对话框 -->
|
||||
<ElDialog
|
||||
v-model="allocateDialogVisible"
|
||||
@@ -92,13 +77,26 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="cardSearchForm"
|
||||
:items="cardSearchFormItems"
|
||||
label-width="85"
|
||||
label-width="120"
|
||||
@reset="handleCardSearchReset"
|
||||
@search="handleCardSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<!-- 卡列表 -->
|
||||
<div class="card-selection-info"> 已选择 {{ selectedAvailableCards.length }} 张卡 </div>
|
||||
<div
|
||||
class="card-selection-info"
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
>
|
||||
<span>已选择 {{ selectedAvailableCards.length }} 张卡</span>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleAllocate"
|
||||
:loading="allocateLoading"
|
||||
:disabled="selectedAvailableCards.length === 0"
|
||||
>
|
||||
确认授权
|
||||
</ElButton>
|
||||
</div>
|
||||
<ArtTable
|
||||
ref="availableCardsTableRef"
|
||||
row-key="id"
|
||||
@@ -121,20 +119,6 @@
|
||||
/>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="allocateDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleAllocate"
|
||||
:loading="allocateLoading"
|
||||
:disabled="selectedAvailableCards.length === 0"
|
||||
>
|
||||
确认授权
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 批量回收对话框 -->
|
||||
@@ -216,14 +200,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 表格行操作右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="cardOperationMenuRef"
|
||||
:menu-items="cardOperationMenuItems"
|
||||
:menu-width="140"
|
||||
@select="handleCardOperationMenuSelect"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
@@ -232,19 +208,18 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { EnterpriseService, CardService } from '@/api/modules'
|
||||
import {
|
||||
EnterpriseService,
|
||||
CardService,
|
||||
CarrierService,
|
||||
PackageSeriesService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { BgColorEnum } from '@/enums/appEnum'
|
||||
import type {
|
||||
EnterpriseCardItem,
|
||||
AllocateCardsResponse,
|
||||
@@ -260,15 +235,6 @@
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const loading = ref(false)
|
||||
const allocateDialogVisible = ref(false)
|
||||
const allocateLoading = ref(false)
|
||||
@@ -295,17 +261,26 @@
|
||||
const selectedAvailableCards = ref<StandaloneIotCard[]>([])
|
||||
const allocatedCardIccids = ref<Set<string>>(new Set())
|
||||
|
||||
// 右键菜单相关
|
||||
const cardOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentOperatingCard = ref<EnterpriseCardItem | null>(null)
|
||||
// 远程搜索相关
|
||||
const carrierLoading = ref(false)
|
||||
const searchCarrierOptions = ref<any[]>([])
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
const seriesLoading = ref(false)
|
||||
|
||||
// 卡搜索表单初始值
|
||||
const initialCardSearchState = {
|
||||
status: undefined,
|
||||
carrier_id: undefined,
|
||||
iccid: '',
|
||||
carrier_name: '',
|
||||
status: undefined,
|
||||
virtual_no: '',
|
||||
msisdn: '',
|
||||
is_distributed: undefined
|
||||
device_virtual_no: '',
|
||||
series_id: undefined,
|
||||
is_distributed: undefined,
|
||||
is_replaced: undefined,
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
is_standalone: true
|
||||
}
|
||||
|
||||
const cardSearchForm = reactive({ ...initialCardSearchState })
|
||||
@@ -392,8 +367,70 @@
|
||||
}
|
||||
]
|
||||
|
||||
// 搜索运营商
|
||||
const handleSearchCarrier = async (query: string) => {
|
||||
try {
|
||||
const params: any = { page: 1, page_size: 20 }
|
||||
if (query) {
|
||||
params.carrier_name = query
|
||||
}
|
||||
const res = await CarrierService.getCarriers(params)
|
||||
if (res.code === 0) {
|
||||
searchCarrierOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索套餐系列
|
||||
const handleSearchSeries = async (query: string) => {
|
||||
seriesLoading.value = true
|
||||
try {
|
||||
const params: any = { page: 1, page_size: 20, status: 1 }
|
||||
if (query) {
|
||||
params.series_name = query
|
||||
}
|
||||
const res = await PackageSeriesService.getPackageSeries(params)
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
seriesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 卡列表搜索表单配置
|
||||
const cardSearchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: 'ICCID',
|
||||
prop: 'iccid',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入ICCID'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '运营商名称',
|
||||
prop: 'carrier_name',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchCarrier,
|
||||
loading: carrierLoading.value,
|
||||
placeholder: '请选择或搜索运营商名称'
|
||||
},
|
||||
options: () =>
|
||||
searchCarrierOptions.value.map((c) => ({
|
||||
label: c.carrier_name,
|
||||
value: c.carrier_name
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
@@ -410,37 +447,49 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '运营商',
|
||||
prop: 'carrier_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '中国移动', value: 1 },
|
||||
{ label: '中国联通', value: 2 },
|
||||
{ label: '中国电信', value: 3 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'ICCID',
|
||||
prop: 'iccid',
|
||||
label: '卡虚拟号',
|
||||
prop: 'virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入ICCID'
|
||||
placeholder: '请输入卡虚拟号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '卡接入号',
|
||||
label: 'MSISDN',
|
||||
prop: 'msisdn',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入卡接入号'
|
||||
placeholder: '请输入MSISDN'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '绑定设备虚拟号',
|
||||
prop: 'device_virtual_no',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入绑定设备虚拟号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '套餐系列',
|
||||
prop: 'series_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchSeries,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '是否已分销',
|
||||
prop: 'is_distributed',
|
||||
@@ -453,6 +502,37 @@
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '是否有换卡记录',
|
||||
prop: 'is_replaced',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '有换卡记录', value: true },
|
||||
{ label: '无换卡记录', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'ICCID起始号',
|
||||
prop: 'iccid_start',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入ICCID起始号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'ICCID结束号',
|
||||
prop: 'iccid_end',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入ICCID结束号'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -523,28 +603,46 @@
|
||||
},
|
||||
{
|
||||
prop: 'msisdn',
|
||||
label: '卡接入号',
|
||||
width: 130
|
||||
label: 'MSISDN',
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '设备号',
|
||||
width: 150
|
||||
label: '卡虚拟号',
|
||||
width: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: EnterpriseCardItem) => row.virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'carrier_id',
|
||||
label: '运营商ID',
|
||||
width: 100
|
||||
prop: 'device_virtual_no',
|
||||
label: '绑定设备虚拟号',
|
||||
width: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: EnterpriseCardItem) => (row as any).device_virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'card_category',
|
||||
label: '卡业务类型',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseCardItem) => getCardCategoryText((row as any).card_category)
|
||||
},
|
||||
{
|
||||
prop: 'carrier_name',
|
||||
label: '运营商',
|
||||
width: 120
|
||||
width: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'package_name',
|
||||
label: '套餐名称',
|
||||
width: 150
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 150,
|
||||
formatter: (row: EnterpriseCardItem) => (row as any).shop_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '套餐系列',
|
||||
width: 150,
|
||||
formatter: (row: EnterpriseCardItem) => (row as any).series_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
@@ -555,9 +653,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status_name',
|
||||
label: '状态名称',
|
||||
width: 100
|
||||
prop: 'activation_status',
|
||||
label: '激活状态',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseCardItem) => {
|
||||
const type = (row as any).activation_status === 1 ? 'success' : 'info'
|
||||
const text = (row as any).activation_status === 1 ? '已激活' : '未激活'
|
||||
return h(ElTag, { type }, () => text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'network_status',
|
||||
@@ -570,9 +673,94 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'network_status_name',
|
||||
label: '网络状态名称',
|
||||
width: 130
|
||||
prop: 'real_name_status',
|
||||
label: '实名状态',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseCardItem) => {
|
||||
const type = (row as any).real_name_status === 1 ? 'success' : 'warning'
|
||||
const text = (row as any).real_name_status === 1 ? '已实名' : '未实名'
|
||||
return h(ElTag, { type }, () => text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'realname_policy',
|
||||
label: '实名认证策略',
|
||||
width: 150,
|
||||
formatter: (row: EnterpriseCardItem) => {
|
||||
const policy = (row as any).realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
const text = policyMap[policy] || (policy ? policy : '未知')
|
||||
return text
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'activated_at',
|
||||
label: '激活时间',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseCardItem) =>
|
||||
(row as any).activated_at ? formatDateTime((row as any).activated_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'current_month_usage_mb',
|
||||
label: '自然月累计流量(MB)',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseCardItem) =>
|
||||
((row as any).current_month_usage_mb)?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'last_month_total_mb',
|
||||
label: '上月流量总量(MB)',
|
||||
width: 150,
|
||||
formatter: (row: EnterpriseCardItem) =>
|
||||
((row as any).last_month_total_mb)?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'data_usage_mb',
|
||||
label: '累计流量(MB)',
|
||||
width: 120,
|
||||
formatter: (row: EnterpriseCardItem) => (row as any).data_usage_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'last_data_check_at',
|
||||
label: '最后流量检查时间',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseCardItem) =>
|
||||
(row as any).last_data_check_at ? formatDateTime((row as any).last_data_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_real_name_check_at',
|
||||
label: '最后实名检查时间',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseCardItem) =>
|
||||
(row as any).last_real_name_check_at ? formatDateTime((row as any).last_real_name_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'enable_polling',
|
||||
label: '启用轮询',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseCardItem) => ((row as any).enable_polling ? '是' : '否')
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
width: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'supplier',
|
||||
label: '供应商',
|
||||
width: 150,
|
||||
formatter: (row: EnterpriseCardItem) => (row as any).supplier || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: EnterpriseCardItem) => formatDateTime(row.created_at)
|
||||
}
|
||||
])
|
||||
|
||||
@@ -708,22 +896,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 获取卡业务类型文本
|
||||
const getCardCategoryText = (category: string) => {
|
||||
const categoryMap: Record<string, string> = {
|
||||
normal: '普通卡',
|
||||
industry: '行业卡'
|
||||
}
|
||||
return categoryMap[category] || category
|
||||
}
|
||||
|
||||
// 可用卡列表列配置
|
||||
const availableCardColumns = computed(() => [
|
||||
{
|
||||
prop: 'iccid',
|
||||
label: 'ICCID',
|
||||
minWidth: 180
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: 'msisdn',
|
||||
label: '卡接入号',
|
||||
width: 130
|
||||
label: 'MSISDN',
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '卡虚拟号',
|
||||
width: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: StandaloneIotCard) => row.virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_virtual_no',
|
||||
label: '绑定设备虚拟号',
|
||||
width: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: StandaloneIotCard) => row.device_virtual_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'card_category',
|
||||
label: '卡业务类型',
|
||||
width: 100,
|
||||
formatter: (row: StandaloneIotCard) => getCardCategoryText(row.card_category)
|
||||
},
|
||||
{
|
||||
prop: 'carrier_name',
|
||||
label: '运营商',
|
||||
width: 100
|
||||
width: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.shop_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '套餐系列',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.series_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
@@ -766,25 +996,82 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'data_usage_mb',
|
||||
label: '累计流量(MB)',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'first_commission_paid',
|
||||
label: '首次佣金',
|
||||
width: 100,
|
||||
prop: 'realname_policy',
|
||||
label: '实名认证策略',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => {
|
||||
const type = row.first_commission_paid ? 'success' : 'info'
|
||||
const text = row.first_commission_paid ? '已支付' : '未支付'
|
||||
return h(ElTag, { type, size: 'small' }, () => text)
|
||||
const policy = row.realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
const text = policyMap[policy] || (policy ? policy : '未知')
|
||||
return text
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'accumulated_recharge',
|
||||
label: '累计充值',
|
||||
prop: 'activated_at',
|
||||
label: '激活时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.activated_at ? formatDateTime(row.activated_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'current_month_usage_mb',
|
||||
label: '自然月累计流量(MB)',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) => row.current_month_usage_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'last_month_total_mb',
|
||||
label: '上月流量总量(MB)',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.last_month_total_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'data_usage_mb',
|
||||
label: '累计流量(MB)',
|
||||
width: 120,
|
||||
formatter: (row: StandaloneIotCard) => row.data_usage_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'last_data_check_at',
|
||||
label: '最后流量检查时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.last_data_check_at ? formatDateTime(row.last_data_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_real_name_check_at',
|
||||
label: '最后实名检查时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.last_real_name_check_at ? formatDateTime(row.last_real_name_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'enable_polling',
|
||||
label: '启用轮询',
|
||||
width: 100,
|
||||
formatter: (row: StandaloneIotCard) => `¥${(row.accumulated_recharge / 100).toFixed(2)}`
|
||||
formatter: (row: StandaloneIotCard) => (row.enable_polling ? '是' : '否')
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
width: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'supplier',
|
||||
label: '供应商',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.supplier || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) => formatDateTime(row.created_at)
|
||||
}
|
||||
])
|
||||
|
||||
@@ -853,7 +1140,7 @@
|
||||
}
|
||||
|
||||
// 显示授权对话框
|
||||
const showAllocateDialog = () => {
|
||||
const showAllocateDialog = async () => {
|
||||
allocateDialogVisible.value = true
|
||||
selectedAvailableCards.value = []
|
||||
// 收集已授权的卡的ICCID
|
||||
@@ -862,8 +1149,25 @@
|
||||
Object.assign(cardSearchForm, { ...initialCardSearchState })
|
||||
cardPagination.page = 1
|
||||
cardPagination.pageSize = 20
|
||||
// 预加载下拉选项
|
||||
handleSearchCarrier('')
|
||||
handleSearchSeries('')
|
||||
// 加载可用卡列表
|
||||
getAvailableCardsList()
|
||||
await getAvailableCardsList()
|
||||
// 预选已授权的卡
|
||||
const preSelectedCards = availableCardsList.value.filter((card) =>
|
||||
allocatedCardIccids.value.has(card.iccid)
|
||||
)
|
||||
selectedAvailableCards.value = preSelectedCards
|
||||
// 等待表格渲染完成后,设置已选项
|
||||
nextTick(() => {
|
||||
preSelectedCards.forEach((card) => {
|
||||
const row = availableCardsTableRef.value?.tableRef?.getRowByKey(card.iccid)
|
||||
if (row) {
|
||||
availableCardsTableRef.value?.tableRef?.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 执行授权
|
||||
@@ -959,56 +1263,6 @@
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 右键菜单项配置
|
||||
const cardOperationMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
|
||||
if (!currentOperatingCard.value) return items
|
||||
|
||||
if (currentOperatingCard.value.network_status === 0) {
|
||||
// 停机状态 - 显示复机
|
||||
if (hasAuth('enterprise_cards:resume')) {
|
||||
items.push({
|
||||
key: 'resume',
|
||||
label: '复机'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 开机状态 - 显示停机
|
||||
if (hasAuth('enterprise_cards:suspend')) {
|
||||
items.push({
|
||||
key: 'suspend',
|
||||
label: '停机'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 处理右键菜单
|
||||
const handleRowContextMenu = (row: EnterpriseCardItem, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
currentOperatingCard.value = row
|
||||
nextTick(() => {
|
||||
cardOperationMenuRef.value?.show(event)
|
||||
})
|
||||
}
|
||||
|
||||
// 处理菜单选择
|
||||
const handleCardOperationMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentOperatingCard.value) return
|
||||
|
||||
switch (item.key) {
|
||||
case 'suspend':
|
||||
handleSuspend(currentOperatingCard.value)
|
||||
break
|
||||
case 'resume':
|
||||
handleResume(currentOperatingCard.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 停机卡
|
||||
const handleSuspend = (row: EnterpriseCardItem) => {
|
||||
ElMessageBox.confirm('确定要停机该卡吗?', '停机卡', {
|
||||
@@ -1054,12 +1308,15 @@
|
||||
.enterprise-cards-page {
|
||||
.enterprise-info-card {
|
||||
margin-bottom: 16px;
|
||||
:deep(.el-card__body) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-selection-info {
|
||||
|
||||
@@ -2,22 +2,14 @@
|
||||
<ArtTableFullScreen>
|
||||
<div class="enterprise-devices-page" id="table-full-screen">
|
||||
<!-- 企业信息卡片 -->
|
||||
<ElCard shadow="never" style="margin-bottom: 16px">
|
||||
<ElCard shadow="never" class="enterprise-info-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<ElButton @click="goBack">{{ $t('common.back') }}</ElButton>
|
||||
<span>企业设备列表</span>
|
||||
<ElButton @click="goBack">{{ $t('common.cancel') }}</ElButton>
|
||||
<span v-if="enterpriseInfo"> - {{ enterpriseInfo.enterprise_name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<ElDescriptions :column="3" border v-if="enterpriseInfo">
|
||||
<ElDescriptionsItem label="企业名称">{{
|
||||
enterpriseInfo.enterprise_name
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业编号">{{
|
||||
enterpriseInfo.enterprise_code
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系人">{{ enterpriseInfo.contact_name }}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</ElCard>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
@@ -87,14 +79,24 @@
|
||||
v-model:filter="deviceSearchForm"
|
||||
:items="deviceSearchFormItems"
|
||||
label-width="85"
|
||||
:show-expand="false"
|
||||
@reset="handleDeviceSearchReset"
|
||||
@search="handleDeviceSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<!-- 设备列表 -->
|
||||
<div class="device-selection-info">
|
||||
已选择 {{ selectedAvailableDevices.length }} 台设备
|
||||
<div
|
||||
class="device-selection-info"
|
||||
style="display: flex; justify-content: space-between; align-items: center"
|
||||
>
|
||||
<span>已选择 {{ selectedAvailableDevices.length }} 台设备</span>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleAllocate"
|
||||
:loading="allocateLoading"
|
||||
:disabled="selectedAvailableDevices.length === 0"
|
||||
>
|
||||
{{ $t('common.confirm') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<ArtTable
|
||||
ref="availableDevicesTableRef"
|
||||
@@ -118,20 +120,6 @@
|
||||
/>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="allocateDialogVisible = false">{{ $t('common.cancel') }}</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleAllocate"
|
||||
:loading="allocateLoading"
|
||||
:disabled="selectedAvailableDevices.length === 0"
|
||||
>
|
||||
{{ $t('common.confirm') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 撤销授权对话框 -->
|
||||
@@ -215,7 +203,13 @@
|
||||
import { h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { EnterpriseService, DeviceService } from '@/api/modules'
|
||||
import {
|
||||
EnterpriseService,
|
||||
DeviceService,
|
||||
CardService,
|
||||
ShopService,
|
||||
PackageSeriesService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
@@ -261,12 +255,23 @@
|
||||
const selectedAvailableDevices = ref<Device[]>([])
|
||||
const allocatedDeviceVirtualNos = ref<Set<string>>(new Set())
|
||||
|
||||
// 远程搜索相关
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
const searchBatchNoOptions = ref<any[]>([])
|
||||
const seriesLoading = ref(false)
|
||||
const shopOptions = ref<any[]>([])
|
||||
|
||||
// 设备搜索表单初始值
|
||||
const initialDeviceSearchState = {
|
||||
status: undefined,
|
||||
virtual_no: '',
|
||||
device_name: '',
|
||||
shop_id: undefined
|
||||
device_type: '',
|
||||
manufacturer: '',
|
||||
batch_no: undefined,
|
||||
shop_id: undefined,
|
||||
series_id: undefined,
|
||||
dateRange: undefined
|
||||
}
|
||||
|
||||
const deviceSearchForm = reactive({ ...initialDeviceSearchState })
|
||||
@@ -312,21 +317,6 @@
|
||||
|
||||
// 设备列表搜索表单配置
|
||||
const deviceSearchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '设备状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '在库', value: 1 },
|
||||
{ label: '已分销', value: 2 },
|
||||
{ label: '已激活', value: 3 },
|
||||
{ label: '已停用', value: 4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '设备号',
|
||||
prop: 'virtual_no',
|
||||
@@ -344,6 +334,105 @@
|
||||
clearable: true,
|
||||
placeholder: '请输入设备名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '在库', value: 1 },
|
||||
{ label: '已分销', value: 2 },
|
||||
{ label: '已激活', value: 3 },
|
||||
{ label: '已停用', value: 4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '批次号',
|
||||
prop: 'batch_no',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => handleSearchBatchNo(query),
|
||||
loading: false,
|
||||
placeholder: '请选择或搜索批次号'
|
||||
},
|
||||
options: () =>
|
||||
searchBatchNoOptions.value.map((b) => ({
|
||||
label: b.batch_no,
|
||||
value: b.batch_no
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '设备类型',
|
||||
prop: 'device_type',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入设备类型'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '制造商',
|
||||
prop: 'manufacturer',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入制造商'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query),
|
||||
loading: false,
|
||||
placeholder: '请选择或搜索店铺名称'
|
||||
},
|
||||
options: () =>
|
||||
shopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '套餐系列',
|
||||
prop: 'series_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchSeries(query),
|
||||
loading: seriesLoading.value,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '起始至结束',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -393,25 +482,101 @@
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'device_id',
|
||||
label: '设备ID',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '设备号',
|
||||
minWidth: 150
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'imei',
|
||||
label: 'IMEI',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).imei || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_name',
|
||||
label: '设备名称',
|
||||
minWidth: 150
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 150,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).shop_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '套餐系列',
|
||||
minWidth: 150,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).series_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
width: 120
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).device_type || '-'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseDeviceItem) => {
|
||||
return h(ElTag, { type: getDeviceStatusTag(row.status) }, () =>
|
||||
getDeviceStatusText(row.status)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'online_status',
|
||||
label: '在线状态',
|
||||
width: 100,
|
||||
formatter: (row: EnterpriseDeviceItem) => {
|
||||
const onlineStatusMap: Record<number, { text: string; type: any }> = {
|
||||
0: { text: '未知', type: 'info' },
|
||||
1: { text: '在线', type: 'success' },
|
||||
2: { text: '离线', type: 'danger' }
|
||||
}
|
||||
const status = onlineStatusMap[(row as any).online_status ?? 0] || { text: '未知', type: 'info' }
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
minWidth: 100,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).manufacturer || '-'
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: EnterpriseDeviceItem) => {
|
||||
const count = (row as any).bound_card_count ?? 0
|
||||
const color = count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, count)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'sn',
|
||||
label: '序列号',
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).sn || '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: EnterpriseDeviceItem) => (row as any).batch_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'card_count',
|
||||
@@ -432,16 +597,40 @@
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '设备号',
|
||||
minWidth: 150
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'imei',
|
||||
label: 'IMEI',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.imei || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_name',
|
||||
label: '设备名称',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'series_name',
|
||||
label: '套餐系列',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
@@ -455,14 +644,46 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '所属店铺',
|
||||
width: 150
|
||||
prop: 'online_status',
|
||||
label: '在线状态',
|
||||
width: 100,
|
||||
formatter: (row: Device) => {
|
||||
const onlineStatusMap: Record<number, { text: string; type: any }> = {
|
||||
0: { text: '未知', type: 'info' },
|
||||
1: { text: '在线', type: 'success' },
|
||||
2: { text: '离线', type: 'danger' }
|
||||
}
|
||||
const status = onlineStatusMap[row.online_status ?? 0] || { text: '未知', type: 'info' }
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '绑定卡数',
|
||||
width: 100
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: Device) => {
|
||||
const color = row.bound_card_count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, row.bound_card_count)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'sn',
|
||||
label: '序列号',
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.sn || '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.batch_no || '-'
|
||||
}
|
||||
])
|
||||
|
||||
@@ -513,7 +734,7 @@
|
||||
|
||||
const res = await EnterpriseService.getEnterpriseDevices(enterpriseId.value, params)
|
||||
if (res.code === 0) {
|
||||
deviceList.value = res.data.list || []
|
||||
deviceList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -608,6 +829,57 @@
|
||||
getAvailableDevicesList()
|
||||
}
|
||||
|
||||
// 搜索批次号
|
||||
const handleSearchBatchNo = async (query: string) => {
|
||||
try {
|
||||
const params: any = { page: 1, page_size: 20 }
|
||||
if (query) {
|
||||
params.batch_no = query
|
||||
}
|
||||
const res = await CardService.getIotCardImportTasks(params)
|
||||
if (res.code === 0) {
|
||||
searchBatchNoOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索店铺
|
||||
const searchShops = async (query: string) => {
|
||||
try {
|
||||
const params: any = { page: 1, page_size: 20 }
|
||||
if (query) {
|
||||
params.shop_name = query
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
shopOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索套餐系列
|
||||
const searchSeries = async (query: string) => {
|
||||
seriesLoading.value = true
|
||||
try {
|
||||
const params: any = { page: 1, page_size: 20, status: 1 }
|
||||
if (query) {
|
||||
params.series_name = query
|
||||
}
|
||||
const res = await PackageSeriesService.getPackageSeries(params)
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
seriesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 设备列表分页大小变化
|
||||
const handleDevicePageSizeChange = (newPageSize: number) => {
|
||||
devicePagination.pageSize = newPageSize
|
||||
@@ -626,7 +898,7 @@
|
||||
}
|
||||
|
||||
// 显示授权对话框
|
||||
const showAllocateDialog = () => {
|
||||
const showAllocateDialog = async () => {
|
||||
allocateDialogVisible.value = true
|
||||
selectedAvailableDevices.value = []
|
||||
// 收集已授权的设备的 virtual_no
|
||||
@@ -635,8 +907,26 @@
|
||||
Object.assign(deviceSearchForm, { ...initialDeviceSearchState })
|
||||
devicePagination.page = 1
|
||||
devicePagination.pageSize = 20
|
||||
// 预加载下拉选项
|
||||
handleSearchBatchNo('')
|
||||
searchShops('')
|
||||
searchSeries('')
|
||||
// 加载可用设备列表
|
||||
getAvailableDevicesList()
|
||||
await getAvailableDevicesList()
|
||||
// 预选已授权的设备
|
||||
const preSelectedDevices = availableDevicesList.value.filter((device) =>
|
||||
allocatedDeviceVirtualNos.value.has(device.virtual_no)
|
||||
)
|
||||
selectedAvailableDevices.value = preSelectedDevices
|
||||
// 等待表格渲染完成后,设置已选项
|
||||
nextTick(() => {
|
||||
preSelectedDevices.forEach((device) => {
|
||||
const row = availableDevicesTableRef.value?.tableRef?.getRowByKey(device.id)
|
||||
if (row) {
|
||||
availableDevicesTableRef.value?.tableRef?.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 执行授权
|
||||
@@ -669,7 +959,7 @@
|
||||
ElMessage.error('设备授权失败')
|
||||
}
|
||||
|
||||
getTableData()
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -736,7 +1026,7 @@
|
||||
tableRef.value.clearSelection()
|
||||
}
|
||||
selectedDevices.value = []
|
||||
getTableData()
|
||||
await getTableData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -761,7 +1051,14 @@
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.enterprise-info-card {
|
||||
margin-bottom: 16px;
|
||||
:deep(.el-card__body) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection-info {
|
||||
|
||||
@@ -1162,8 +1162,8 @@
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '未绑定设备', value: true },
|
||||
{ label: '已绑定设备', value: false }
|
||||
{ label: '未绑定设备', value: false },
|
||||
{ label: '已绑定设备', value: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,30 +3,16 @@
|
||||
<ArtTableFullScreen>
|
||||
<div class="agent-commission-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ElCard shadow="never" class="search-card">
|
||||
<ElForm :inline="true" :model="searchForm" class="search-form">
|
||||
<ElFormItem label="店铺名称">
|
||||
<ElInput
|
||||
v-model="searchForm.shop_name"
|
||||
placeholder="请输入店铺名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="店铺编码">
|
||||
<ElInput
|
||||
v-model="searchForm.shop_code"
|
||||
placeholder="请输入店铺编码"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem>
|
||||
<ElButton type="primary" @click="handleSearch">查询</ElButton>
|
||||
<ElButton @click="handleReset">重置</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:gutter="16"
|
||||
:el-col-span="6"
|
||||
label-width="100"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
@@ -388,6 +374,7 @@
|
||||
CommissionResolveAction,
|
||||
MainWalletTransactionItem
|
||||
} from '@/types/api/commission'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
@@ -413,7 +400,7 @@
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
shop_name: '',
|
||||
shop_code: ''
|
||||
username: ''
|
||||
})
|
||||
|
||||
// 主表格分页
|
||||
@@ -501,6 +488,28 @@
|
||||
{ label: '未提现', prop: 'unwithdraw_commission' }
|
||||
]
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入店铺名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '主账号用户名',
|
||||
prop: 'username',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入主账号用户名'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 处理名称点击
|
||||
const handleNameClick = (row: ShopFundSummaryItem) => {
|
||||
if (hasAuth('agent_commission:detail')) {
|
||||
@@ -515,13 +524,13 @@
|
||||
{
|
||||
prop: 'shop_code',
|
||||
label: '店铺编码',
|
||||
minWidth: 150,
|
||||
minWidth: 140,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 180,
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: ShopFundSummaryItem) => {
|
||||
return h(
|
||||
@@ -545,12 +554,12 @@
|
||||
{
|
||||
prop: 'phone',
|
||||
label: '手机号',
|
||||
width: 130
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'main_balance',
|
||||
label: '预充值余额',
|
||||
width: 130,
|
||||
minWidth: 130,
|
||||
formatter: (row: ShopFundSummaryItem) => {
|
||||
return h(
|
||||
'span',
|
||||
@@ -562,13 +571,13 @@
|
||||
{
|
||||
prop: 'total_commission',
|
||||
label: '总佣金',
|
||||
width: 120,
|
||||
minWidth: 120,
|
||||
formatter: (row: ShopFundSummaryItem) => formatMoney(row.total_commission)
|
||||
},
|
||||
{
|
||||
prop: 'available_commission',
|
||||
label: '可提现',
|
||||
width: 120,
|
||||
minWidth: 120,
|
||||
formatter: (row: ShopFundSummaryItem) => {
|
||||
return h(
|
||||
'span',
|
||||
@@ -580,13 +589,13 @@
|
||||
{
|
||||
prop: 'frozen_commission',
|
||||
label: '冻结中',
|
||||
width: 120,
|
||||
minWidth: 110,
|
||||
formatter: (row: ShopFundSummaryItem) => formatMoney(row.frozen_commission)
|
||||
},
|
||||
{
|
||||
prop: 'withdrawing_commission',
|
||||
label: '提现中',
|
||||
width: 120,
|
||||
minWidth: 110,
|
||||
formatter: (row: ShopFundSummaryItem) => {
|
||||
return h(
|
||||
'span',
|
||||
@@ -598,13 +607,13 @@
|
||||
{
|
||||
prop: 'withdrawn_commission',
|
||||
label: '已提现',
|
||||
width: 120,
|
||||
minWidth: 110,
|
||||
formatter: (row: ShopFundSummaryItem) => formatMoney(row.withdrawn_commission)
|
||||
},
|
||||
{
|
||||
prop: 'unwithdraw_commission',
|
||||
label: '未提现',
|
||||
width: 120,
|
||||
minWidth: 110,
|
||||
formatter: (row: ShopFundSummaryItem) => formatMoney(row.unwithdraw_commission)
|
||||
}
|
||||
])
|
||||
@@ -621,7 +630,7 @@
|
||||
page: pagination.page,
|
||||
page_size: pagination.pageSize,
|
||||
shop_name: searchForm.shop_name || undefined,
|
||||
shop_code: searchForm.shop_code || undefined
|
||||
username: searchForm.username || undefined
|
||||
}
|
||||
const res = await CommissionService.getShopFundSummary(params)
|
||||
if (res.code === 0) {
|
||||
@@ -645,7 +654,7 @@
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.shop_name = ''
|
||||
searchForm.shop_code = ''
|
||||
searchForm.username = ''
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
@@ -907,13 +916,9 @@
|
||||
}
|
||||
|
||||
.agent-commission-page {
|
||||
.search-card {
|
||||
:deep(.art-custom-card) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-tabs {
|
||||
|
||||
@@ -59,9 +59,9 @@
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="isShopType ? '新增店铺账号' : '新增企业账号'"
|
||||
width="500px"
|
||||
width="30%"
|
||||
>
|
||||
<ElForm ref="formRef" :model="accountForm" :rules="accountRules" label-width="100px">
|
||||
<ElForm ref="formRef" :model="accountForm" :rules="accountRules" label-width="80">
|
||||
<ElFormItem label="用户名" prop="username">
|
||||
<ElInput v-model="accountForm.username" placeholder="请输入用户名" />
|
||||
</ElFormItem>
|
||||
@@ -226,7 +226,7 @@
|
||||
} from 'element-plus'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { AccountService, RoleService } from '@/api/modules'
|
||||
import { AccountService, RoleService, ShopService, EnterpriseService } from '@/api/modules'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { PlatformAccount, PlatformRole } from '@/types/api'
|
||||
@@ -243,7 +243,11 @@
|
||||
const isShopType = computed(() => route.query.type === 'shop')
|
||||
|
||||
// 页面标题
|
||||
const pageTitle = computed(() => (isShopType.value ? '店铺账号列表' : '企业客户账号列表'))
|
||||
const entityName = ref('')
|
||||
const pageTitle = computed(() => {
|
||||
const baseTitle = isShopType.value ? '店铺账号列表' : '企业客户账号列表'
|
||||
return entityName.value ? `${baseTitle} - ${entityName.value}` : baseTitle
|
||||
})
|
||||
|
||||
// 过滤参数键名
|
||||
const filterParamKey = computed(() => (isShopType.value ? 'shop_id' : 'enterprise_id'))
|
||||
@@ -425,6 +429,37 @@
|
||||
return baseColumns
|
||||
})
|
||||
|
||||
// 加载店铺/企业名称
|
||||
const loadEntityName = async () => {
|
||||
const entityId = Number(route.params.id)
|
||||
if (!entityId) return
|
||||
|
||||
try {
|
||||
if (isShopType.value) {
|
||||
const res = await ShopService.getShops({ id: entityId, page: 1, page_size: 1 })
|
||||
if (res.code === 0 && res.data.items?.length) {
|
||||
entityName.value = res.data.items[0].shop_name || ''
|
||||
}
|
||||
} else {
|
||||
const res = await EnterpriseService.getEnterprises({ id: entityId, page: 1, page_size: 1 })
|
||||
if (res.code === 0 && res.data.items?.length) {
|
||||
entityName.value = res.data.items[0].enterprise_name || ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取名称失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 从账号数据中提取店铺/企业名称
|
||||
const extractEntityNameFromAccounts = () => {
|
||||
if (entityName.value || !accountList.value.length) return
|
||||
const firstAccount = accountList.value[0]
|
||||
if (firstAccount) {
|
||||
entityName.value = (firstAccount as any).shop_name || (firstAccount as any).enterprise_name || ''
|
||||
}
|
||||
}
|
||||
|
||||
// 获取账号列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
@@ -451,6 +486,7 @@
|
||||
if (res.code === 0) {
|
||||
accountList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
extractEntityNameFromAccounts()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -583,7 +619,7 @@
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
role_type: 1
|
||||
role_type: 2
|
||||
}
|
||||
if (keyword) {
|
||||
params.role_name = keyword
|
||||
@@ -699,6 +735,7 @@
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadEntityName()
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
@@ -755,7 +792,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
max-width: 380px;
|
||||
max-width: 440px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -241,12 +241,14 @@
|
||||
{
|
||||
prop: 'perm_name',
|
||||
label: '权限名称',
|
||||
width: 240
|
||||
width: 240,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'perm_code',
|
||||
label: '权限标识',
|
||||
width: 240
|
||||
width: 240,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'perm_type',
|
||||
@@ -260,7 +262,8 @@
|
||||
},
|
||||
{
|
||||
prop: 'url',
|
||||
label: '菜单路径'
|
||||
label: '菜单路径',
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'platform',
|
||||
@@ -275,24 +278,6 @@
|
||||
return platformMap[row.platform || 'all'] || row.platform
|
||||
}
|
||||
},
|
||||
// {
|
||||
// prop: 'available_for_role_types',
|
||||
// label: '可用角色类型',
|
||||
// width: 160,
|
||||
// formatter: (row: PermissionTreeNode) => {
|
||||
// if (!row.available_for_role_types) {
|
||||
// return '-'
|
||||
// }
|
||||
// const roleTypeMap: Record<string, string> = {
|
||||
// '1': '平台角色',
|
||||
// '2': '客户角色'
|
||||
// }
|
||||
// const types = row.available_for_role_types
|
||||
// .split(',')
|
||||
// .map((type) => roleTypeMap[type.trim()] || type)
|
||||
// return types.join(', ')
|
||||
// }
|
||||
// },
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
|
||||
Reference in New Issue
Block a user