This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user