feat: 资产管理新增字段
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 1m47s

This commit is contained in:
sexygoat
2026-05-20 11:06:57 +08:00
parent f9fe76ff99
commit 3e42c4e98c
4 changed files with 176 additions and 12 deletions

View File

@@ -26,6 +26,7 @@ export interface EnterpriseCardsParams {
carrier_id?: number // 运营商ID carrier_id?: number // 运营商ID
iccid?: string // ICCID (模糊查询) iccid?: string // ICCID (模糊查询)
virtual_no?: string // 虚拟号 (模糊查询) virtual_no?: string // 虚拟号 (模糊查询)
msisdn?: string // 接入号 (模糊查询)
} }
/** /**
@@ -34,7 +35,10 @@ export interface EnterpriseCardsParams {
export interface EnterpriseDevicesParams { export interface EnterpriseDevicesParams {
page?: number page?: number
page_size?: number page_size?: number
status?: number
device_name?: string // 设备号 / 设备名称 (模糊搜索)
virtual_no?: string // 虚拟号 (模糊搜索) virtual_no?: string // 虚拟号 (模糊搜索)
imei?: string // IMEI (模糊搜索)
} }
/** /**

View File

@@ -10,6 +10,8 @@ interface Props {
show: boolean show: boolean
options: PickerOption[] options: PickerOption[]
title?: string title?: string
autoConfirm?: boolean
selectedValue?: any
} }
interface Emits { interface Emits {
@@ -19,13 +21,42 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
title: '请选择' title: '请选择',
autoConfirm: false,
}) })
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const selectedIndex = ref(0) const selectedIndex = ref(0)
function syncSelectedIndex() {
if (!props.options.length) {
selectedIndex.value = 0
return
}
const matchedIndex = props.options.findIndex(option => option.value === props.selectedValue)
selectedIndex.value = matchedIndex >= 0 ? matchedIndex : 0
}
watch(() => props.show, (show) => {
if (show) {
syncSelectedIndex()
}
}, { immediate: true })
watch(() => props.selectedValue, () => {
if (props.show) {
syncSelectedIndex()
}
})
watch(() => props.options, () => {
if (props.show) {
syncSelectedIndex()
}
}, { deep: true })
// 关闭弹窗 // 关闭弹窗
function close() { function close() {
emit('update:show', false) emit('update:show', false)
@@ -48,6 +79,11 @@ function handleConfirm() {
// 选择选项 // 选择选项
function selectOption(index: number) { function selectOption(index: number) {
selectedIndex.value = index selectedIndex.value = index
if (props.autoConfirm && props.options[index]) {
emit('confirm', props.options[index])
close()
}
} }
</script> </script>
@@ -69,9 +105,17 @@ function selectOption(index: number) {
<view class="simple-picker-title"> <view class="simple-picker-title">
<text class="text-base font-600 text-[#1e293b]">{{ title }}</text> <text class="text-base font-600 text-[#1e293b]">{{ title }}</text>
</view> </view>
<view class="simple-picker-confirm" @click="handleConfirm"> <view
v-if="!autoConfirm"
class="simple-picker-confirm"
@click="handleConfirm"
>
<text class="text-sm text-brand font-600">确定</text> <text class="text-sm text-brand font-600">确定</text>
</view> </view>
<view
v-else
class="simple-picker-confirm"
/>
</view> </view>
<!-- 选项列表 --> <!-- 选项列表 -->

View File

@@ -454,7 +454,7 @@
</view> </view>
<!-- 最后同步时间 --> <!-- 最后同步时间 -->
<view v-if="realtimeStatus.last_sync_time" <view
class="flex items-center justify-between pt-1 border-t-1px border-[#e5e7eb]"> class="flex items-center justify-between pt-1 border-t-1px border-[#e5e7eb]">
<text class="text-11px text-[#999]">最后同步</text> <text class="text-11px text-[#999]">最后同步</text>
<text class="text-11px text-[#999]"> <text class="text-11px text-[#999]">

View File

@@ -80,13 +80,54 @@
deviceStart: 'asset:device_start_h5', deviceStart: 'asset:device_start_h5',
} as const } as const
type CardSearchType = 'iccid' | 'virtual_no' | 'msisdn'
type DeviceSearchType = 'device_name' | 'virtual_no' | 'imei'
interface SearchTypeOption {
label : string
value : string
}
interface StandaloneCardsParams {
page : number
page_size : number
carrier_id ?: number
status ?: number
iccid ?: string
virtual_no ?: string
msisdn ?: string
shop_id ?: number
}
interface DevicesParams {
page : number
page_size : number
status ?: number
device_name ?: string
virtual_no ?: string
imei ?: string
shop_id ?: number
}
const cardSearchTypeOptions : SearchTypeOption[] = [
{ label: 'ICCID', value: 'iccid' },
{ label: '虚拟号', value: 'virtual_no' },
{ label: '接入号', value: 'msisdn' },
]
const deviceSearchTypeOptions : SearchTypeOption[] = [
{ label: '设备号', value: 'device_name' },
{ label: '虚拟号', value: 'virtual_no' },
{ label: 'IMEI', value: 'imei' },
]
// 获取单卡列表(代理端) // 获取单卡列表(代理端)
function getStandaloneCards(params : any) { function getStandaloneCards(params : StandaloneCardsParams) {
return get<{ items : CardInfo[], page : number, size : number, total : number }>('/api/admin/iot-cards/standalone', { params }) return get<{ items : CardInfo[], page : number, size : number, total : number }>('/api/admin/iot-cards/standalone', { params })
} }
// 获取设备列表(代理端) // 获取设备列表(代理端)
function getDevices(params : any) { function getDevices(params : DevicesParams) {
return get<{ items : DeviceInfo[], page : number, size : number, total : number }>('/api/admin/devices', { params }) return get<{ items : DeviceInfo[], page : number, size : number, total : number }>('/api/admin/devices', { params })
} }
@@ -131,13 +172,35 @@
const carrierFilter = ref<number | undefined>(undefined) const carrierFilter = ref<number | undefined>(undefined)
const statusFilter = ref<number | undefined>(undefined) const statusFilter = ref<number | undefined>(undefined)
const selectedShopFilter = ref<SelectedShopFilter | null>(null) const selectedShopFilter = ref<SelectedShopFilter | null>(null)
const cardSearchType = ref<CardSearchType>('iccid')
const deviceSearchType = ref<DeviceSearchType>('device_name')
const showCarrierPicker = ref(false) const showCarrierPicker = ref(false)
const showSearchTypePicker = ref(false)
const showStatusPicker = ref(false) const showStatusPicker = ref(false)
const showShopPicker = ref(false) const showShopPicker = ref(false)
// 运营商列表 // 运营商列表
const carrierList = ref<CarrierInfo[]>([]) const carrierList = ref<CarrierInfo[]>([])
const isAgent = computed(() => userInfo.value?.user_type === 3) const isAgent = computed(() => userInfo.value?.user_type === 3)
const searchPlaceholder = computed(() => activeTab.value === 'card' ? '搜索ICCID' : '搜索设备号') const currentSearchType = computed(() => activeTab.value === 'card' ? cardSearchType.value : deviceSearchType.value)
const searchTypeOptions = computed(() => activeTab.value === 'card' ? cardSearchTypeOptions : deviceSearchTypeOptions)
const selectedSearchTypeLabel = computed(() => {
return searchTypeOptions.value.find(option => option.value === currentSearchType.value)?.label
|| (activeTab.value === 'card' ? 'ICCID' : '设备号')
})
const isDefaultSearchType = computed(() => {
return activeTab.value === 'card' ? cardSearchType.value === 'iccid' : deviceSearchType.value === 'device_name'
})
const searchPlaceholder = computed(() => {
if (activeTab.value === 'card') {
if (cardSearchType.value === 'virtual_no') return '搜索虚拟号'
if (cardSearchType.value === 'msisdn') return '搜索接入号'
return '搜索ICCID'
}
if (deviceSearchType.value === 'imei') return '搜索IMEI'
if (deviceSearchType.value === 'virtual_no') return '搜索虚拟号'
return '搜索设备号'
})
const canShowDeviceStopButton = computed(() => hasActionButtonPermission(ASSET_ACTION_BUTTON_CODES.deviceStop)) const canShowDeviceStopButton = computed(() => hasActionButtonPermission(ASSET_ACTION_BUTTON_CODES.deviceStop))
const canShowDeviceStartButton = computed(() => hasActionButtonPermission(ASSET_ACTION_BUTTON_CODES.deviceStart)) const canShowDeviceStartButton = computed(() => hasActionButtonPermission(ASSET_ACTION_BUTTON_CODES.deviceStart))
@@ -192,6 +255,34 @@
: 'bg-[#fff7e6] text-[#d46b08]' : 'bg-[#fff7e6] text-[#d46b08]'
} }
function applyCardSearchKeyword(params : StandaloneCardsParams, keyword : string) {
if (cardSearchType.value === 'virtual_no') {
params.virtual_no = keyword
return
}
if (cardSearchType.value === 'msisdn') {
params.msisdn = keyword
return
}
params.iccid = keyword
}
function applyDeviceSearchKeyword(params : DevicesParams, keyword : string) {
if (deviceSearchType.value === 'imei') {
params.imei = keyword
return
}
if (deviceSearchType.value === 'device_name') {
params.device_name = keyword
return
}
params.virtual_no = keyword
}
// 合并后的资产列表 // 合并后的资产列表
const assetList = computed<AssetItem[]>(() => { const assetList = computed<AssetItem[]>(() => {
const cards : AssetItem[] = cardList.value.map(card => ({ const cards : AssetItem[] = cardList.value.map(card => ({
@@ -259,7 +350,7 @@
cardHasMore.value = true cardHasMore.value = true
} }
const params : any = { const params : StandaloneCardsParams = {
page: cardPage.value, page: cardPage.value,
page_size: pageSize, page_size: pageSize,
} }
@@ -272,7 +363,7 @@
params.status = statusFilter.value params.status = statusFilter.value
} }
if (searchKeyword.value.trim()) { if (searchKeyword.value.trim()) {
params.iccid = searchKeyword.value.trim() applyCardSearchKeyword(params, searchKeyword.value.trim())
} }
if (userInfo.value?.user_type === 3 && selectedShopFilter.value) { if (userInfo.value?.user_type === 3 && selectedShopFilter.value) {
params.shop_id = selectedShopFilter.value.id params.shop_id = selectedShopFilter.value.id
@@ -326,13 +417,13 @@
deviceHasMore.value = true deviceHasMore.value = true
} }
const params : any = { const params : DevicesParams = {
page: devicePage.value, page: devicePage.value,
page_size: pageSize, page_size: pageSize,
} }
if (searchKeyword.value.trim()) { if (searchKeyword.value.trim()) {
params.virtual_no = searchKeyword.value.trim() applyDeviceSearchKeyword(params, searchKeyword.value.trim())
} }
if (statusFilter.value !== undefined) { if (statusFilter.value !== undefined) {
params.status = statusFilter.value params.status = statusFilter.value
@@ -416,6 +507,16 @@
refreshList() refreshList()
} }
function onSearchTypeConfirm(option : SearchTypeOption) {
if (activeTab.value === 'card') {
cardSearchType.value = option.value as CardSearchType
} else {
deviceSearchType.value = option.value as DeviceSearchType
}
refreshList()
}
// 获取当前选中的运营商名称 // 获取当前选中的运营商名称
const selectedCarrierName = computed(() => { const selectedCarrierName = computed(() => {
if (carrierFilter.value === undefined) return '运营商' if (carrierFilter.value === undefined) return '运营商'
@@ -661,6 +762,14 @@
</view> </view>
</view> </view>
<view class="flex items-center gap-1.5 px-3 py-1.5 bg-[#f8fafc] rounded-full flex-shrink-0"
@click="showSearchTypePicker = true">
<text class="text-12px" :class="isDefaultSearchType ? 'text-[#64748b]' : 'text-brand font-medium'">
{{ selectedSearchTypeLabel }}
</text>
<i class="i-mdi-chevron-down text-12px text-[#94a3b8]" />
</view>
<view class="flex items-center gap-1.5 px-3 py-1.5 bg-[#f8fafc] rounded-full flex-shrink-0 max-w-36" <view class="flex items-center gap-1.5 px-3 py-1.5 bg-[#f8fafc] rounded-full flex-shrink-0 max-w-36"
@click="showCarrierPicker = true"> @click="showCarrierPicker = true">
<text class="text-12px truncate" <text class="text-12px truncate"
@@ -818,11 +927,18 @@
</view> </view>
<!-- 运营商选择器 --> <!-- 运营商选择器 -->
<SimplePicker v-model:show="showCarrierPicker" :options="carrierOptions" title="选择运营商" <SimplePicker v-model:show="showCarrierPicker" :options="carrierOptions" :selected-value="carrierFilter"
auto-confirm title="选择运营商"
@confirm="onCarrierConfirm" /> @confirm="onCarrierConfirm" />
<!-- 搜索字段选择器 -->
<SimplePicker v-model:show="showSearchTypePicker" :options="searchTypeOptions" :selected-value="currentSearchType"
auto-confirm title="选择搜索字段"
@confirm="onSearchTypeConfirm" />
<!-- 状态选择器 --> <!-- 状态选择器 -->
<SimplePicker v-model:show="showStatusPicker" :options="statusOptions" title="选择状态" @confirm="onStatusConfirm" /> <SimplePicker v-model:show="showStatusPicker" :options="statusOptions" :selected-value="statusFilter"
auto-confirm title="选择状态" @confirm="onStatusConfirm" />
<ShopCascadePicker v-model:show="showShopPicker" :selected-shop="selectedShopFilter" title="选择店铺" <ShopCascadePicker v-model:show="showShopPicker" :selected-shop="selectedShopFilter" title="选择店铺"
@confirm="onShopConfirm" /> @confirm="onShopConfirm" />