This commit is contained in:
@@ -280,6 +280,11 @@
|
|||||||
transform: scale(0.6) !important;
|
transform: scale(0.6) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-checkbox__input.is-disabled .el-checkbox__inner {
|
||||||
|
background-color: var(--el-color-primary) !important;
|
||||||
|
border-color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-notification .el-notification__icon {
|
.el-notification .el-notification__icon {
|
||||||
|
|||||||
BIN
src/template/IoT卡导入模板.xlsx
Normal file
BIN
src/template/IoT卡导入模板.xlsx
Normal file
Binary file not shown.
BIN
src/template/设备导入模板.xlsx
Normal file
BIN
src/template/设备导入模板.xlsx
Normal file
Binary file not shown.
@@ -402,7 +402,7 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 显示对话框
|
// 显示对话框
|
||||||
const showDialog = (type: string, row?: any) => {
|
const showDialog = async (type: string, row?: any) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
dialogType.value = type
|
dialogType.value = type
|
||||||
|
|
||||||
@@ -411,6 +411,10 @@
|
|||||||
formRef.value.resetFields()
|
formRef.value.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'add') {
|
||||||
|
await loadAllRoles()
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
if (type === 'edit' && row) {
|
||||||
formData.id = row.id
|
formData.id = row.id
|
||||||
formData.username = row.username
|
formData.username = row.username
|
||||||
@@ -590,7 +594,8 @@
|
|||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: keyword ? 100 : 20 // 搜索时加载更多,默认20条
|
page_size: keyword ? 100 : 20, // 搜索时加载更多,默认20条
|
||||||
|
role_type: 1
|
||||||
}
|
}
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
params.role_name = keyword
|
params.role_name = keyword
|
||||||
@@ -604,48 +609,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性:新建账号时可选的角色(根据账号类型过滤)
|
// 计算属性:新建账号时可选的角色
|
||||||
const availableRolesForCreate = computed(() => {
|
const availableRolesForCreate = computed(() => {
|
||||||
let roles = allRoles.value
|
return allRoles.value
|
||||||
|
|
||||||
// 根据账号类型过滤角色
|
|
||||||
if (formData.user_type === 1) {
|
|
||||||
// 超级管理员:不能分配角色
|
|
||||||
return []
|
|
||||||
} else if (formData.user_type === 2) {
|
|
||||||
// 平台用户:只显示平台角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 1)
|
|
||||||
} else if (formData.user_type === 3) {
|
|
||||||
// 代理账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
} else if (formData.user_type === 4) {
|
|
||||||
// 企业账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
// 计算属性:过滤后的可分配角色
|
||||||
const filteredAvailableRoles = computed(() => {
|
const filteredAvailableRoles = computed(() => {
|
||||||
let roles = allRoles.value
|
return allRoles.value
|
||||||
|
|
||||||
// 根据账号类型过滤角色
|
|
||||||
if (currentAccountType.value === 1) {
|
|
||||||
// 超级管理员:不能分配任何角色
|
|
||||||
return []
|
|
||||||
} else if (currentAccountType.value === 3) {
|
|
||||||
// 代理账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
} else if (currentAccountType.value === 4) {
|
|
||||||
// 企业账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
} else if (currentAccountType.value === 2) {
|
|
||||||
// 平台用户:只显示平台角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 搜索防抖定时器
|
// 搜索防抖定时器
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
<ElDescriptionsItem label="套餐名称">
|
<ElDescriptionsItem label="套餐名称">
|
||||||
{{ data.package_name }}
|
{{ data.package_name }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="套餐使用记录ID">
|
|
||||||
{{ data.package_usage_id }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="总使用流量" :span="2">
|
<ElDescriptionsItem label="总使用流量" :span="2">
|
||||||
{{ formatDataSize(data.total_usage_mb) }}
|
{{ formatDataSize(data.total_usage_mb) }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|||||||
@@ -171,20 +171,25 @@ export function useAssetInfo() {
|
|||||||
const loadCurrentPackage = async (identifier: string) => {
|
const loadCurrentPackage = async (identifier: string) => {
|
||||||
try {
|
try {
|
||||||
currentPackageLoading.value = true
|
currentPackageLoading.value = true
|
||||||
// 清空之前的错误信息
|
|
||||||
currentPackageErrorMsg.value = ''
|
currentPackageErrorMsg.value = ''
|
||||||
|
|
||||||
const response = await AssetService.getCurrentPackage(identifier)
|
const [currentPkgResponse, packageListResponse] = await Promise.all([
|
||||||
if (response.code === 0) {
|
AssetService.getCurrentPackage(identifier),
|
||||||
if (response.data) {
|
AssetService.getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||||
const pkg = response.data
|
])
|
||||||
|
|
||||||
|
const activePackage = packageListResponse.data?.items?.find((p: any) => p.status === 1)
|
||||||
|
const activePaidAmount = activePackage?.paid_amount || 0
|
||||||
|
|
||||||
|
if (currentPkgResponse.code === 0) {
|
||||||
|
if (currentPkgResponse.data) {
|
||||||
|
const pkg = currentPkgResponse.data
|
||||||
|
|
||||||
// 转换为组件期望的格式
|
|
||||||
currentPackage.value = {
|
currentPackage.value = {
|
||||||
id: pkg.package_usage_id || 0,
|
id: pkg.package_usage_id || 0,
|
||||||
package_id: pkg.package_id || 0,
|
package_id: pkg.package_id || 0,
|
||||||
package_name: pkg.package_name || '',
|
package_name: pkg.package_name || '',
|
||||||
package_price: 0, // API 没有返回价格
|
package_price: activePaidAmount,
|
||||||
package_total_data: pkg.data_limit_mb || 0,
|
package_total_data: pkg.data_limit_mb || 0,
|
||||||
real_data_used: pkg.data_usage_mb || 0,
|
real_data_used: pkg.data_usage_mb || 0,
|
||||||
real_data_remaining: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0),
|
real_data_remaining: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0),
|
||||||
@@ -196,10 +201,10 @@ export function useAssetInfo() {
|
|||||||
expire_time: pkg.expires_at || '',
|
expire_time: pkg.expires_at || '',
|
||||||
status: pkg.status || 0,
|
status: pkg.status || 0,
|
||||||
status_name: pkg.status_name,
|
status_name: pkg.status_name,
|
||||||
duration_days: undefined, // API 没有返回时长
|
duration_days: undefined,
|
||||||
virtual_ratio: pkg.virtual_ratio, // 虚流量比例
|
virtual_ratio: pkg.virtual_ratio,
|
||||||
package_type: pkg.package_type, // 套餐类型
|
package_type: pkg.package_type,
|
||||||
enable_virtual_data: pkg.enable_virtual_data, // 是否启用虚流量
|
enable_virtual_data: pkg.enable_virtual_data,
|
||||||
data_limit_mb: pkg.data_limit_mb,
|
data_limit_mb: pkg.data_limit_mb,
|
||||||
virtual_limit_mb: pkg.virtual_limit_mb,
|
virtual_limit_mb: pkg.virtual_limit_mb,
|
||||||
virtual_remain_mb: pkg.virtual_remain_mb,
|
virtual_remain_mb: pkg.virtual_remain_mb,
|
||||||
|
|||||||
@@ -338,34 +338,47 @@
|
|||||||
label-width="80"
|
label-width="80"
|
||||||
>
|
>
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="10">
|
<ElCol :span="6">
|
||||||
<ElFormItem label="IoT卡" prop="iot_card_id">
|
<ElFormItem label="搜索类型" prop="search_type">
|
||||||
|
<ElSelect v-model="bindCardForm.search_type" placeholder="请选择搜索类型" style="width: 100%">
|
||||||
|
<ElOption label="ICCID" value="iccid" />
|
||||||
|
<ElOption label="运营商名称" value="carrier_name" />
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="8">
|
||||||
|
<ElFormItem label="ICCID" prop="iot_card_id">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="bindCardForm.iot_card_id"
|
v-model="bindCardForm.iot_card_id"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
iotCardList.length === 0 && !iotCardSearchLoading
|
bindCardForm.search_type === 'carrier_name'
|
||||||
? '当前没有未绑定设备的卡'
|
? '请选择或搜索运营商名称'
|
||||||
: '请选择或搜索IoT卡(支持ICCID搜索)'
|
: '请选择或搜索ICCID'
|
||||||
"
|
"
|
||||||
filterable
|
filterable
|
||||||
remote
|
remote
|
||||||
reserve-keyword
|
reserve-keyword
|
||||||
:remote-method="searchIotCards"
|
:remote-method="searchIotCards"
|
||||||
:loading="iotCardSearchLoading"
|
:loading="iotCardSearchLoading"
|
||||||
:disabled="iotCardList.length === 0 && !iotCardSearchLoading"
|
|
||||||
clearable
|
clearable
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="card in iotCardList"
|
v-for="card in iotCardList"
|
||||||
:key="card.id"
|
:key="card.id"
|
||||||
:label="`${card.iccid} ${card.msisdn ? '(' + card.msisdn + ')' : ''}`"
|
:label="`${card.iccid} ${card.carrier_name ? '(' + card.carrier_name + ')' : ''}`"
|
||||||
:value="card.id"
|
:value="card.id"
|
||||||
/>
|
/>
|
||||||
|
<ElOption
|
||||||
|
v-if="iotCardList.length === 0 && !iotCardSearchLoading"
|
||||||
|
label="未找到相关数据"
|
||||||
|
:value="undefined"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="10">
|
<ElCol :span="6">
|
||||||
<ElFormItem label="插槽位置" prop="slot_position">
|
<ElFormItem label="插槽位置" prop="slot_position">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="bindCardForm.slot_position"
|
v-model="bindCardForm.slot_position"
|
||||||
@@ -561,7 +574,8 @@
|
|||||||
ShopService,
|
ShopService,
|
||||||
CardService,
|
CardService,
|
||||||
PackageSeriesService,
|
PackageSeriesService,
|
||||||
AssetService
|
AssetService,
|
||||||
|
CarrierService
|
||||||
} from '@/api/modules'
|
} from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElIcon, ElRadioGroup, ElRadio } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElIcon, ElRadioGroup, ElRadio } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
@@ -656,11 +670,13 @@
|
|||||||
const iotCardSearchLoading = ref(false)
|
const iotCardSearchLoading = ref(false)
|
||||||
const iotCardHasSearched = ref(false) // 标记是否已执行过搜索
|
const iotCardHasSearched = ref(false) // 标记是否已执行过搜索
|
||||||
const bindCardForm = reactive({
|
const bindCardForm = reactive({
|
||||||
|
search_type: 'iccid' as 'iccid' | 'carrier_name',
|
||||||
iot_card_id: undefined as number | undefined,
|
iot_card_id: undefined as number | undefined,
|
||||||
slot_position: 1
|
slot_position: 1
|
||||||
})
|
})
|
||||||
const bindCardRules = reactive<FormRules>({
|
const bindCardRules = reactive<FormRules>({
|
||||||
iot_card_id: [{ required: true, message: '请选择IoT卡', trigger: 'change' }],
|
search_type: [{ required: true, message: '请选择搜索类型', trigger: 'change' }],
|
||||||
|
iot_card_id: [{ required: true, message: '请选择ICCID', trigger: 'change' }],
|
||||||
slot_position: [{ required: true, message: '请选择插槽位置', trigger: 'change' }]
|
slot_position: [{ required: true, message: '请选择插槽位置', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -855,10 +871,14 @@
|
|||||||
deviceCards.value = []
|
deviceCards.value = []
|
||||||
deviceCardsDialogVisible.value = true
|
deviceCardsDialogVisible.value = true
|
||||||
// 重置绑定卡表单
|
// 重置绑定卡表单
|
||||||
|
bindCardForm.search_type = 'iccid'
|
||||||
bindCardForm.iot_card_id = undefined
|
bindCardForm.iot_card_id = undefined
|
||||||
bindCardForm.slot_position = 1
|
bindCardForm.slot_position = 1
|
||||||
// 只加载设备卡列表
|
// 加载未绑定设备的卡列表
|
||||||
await loadDeviceCards(device.virtual_no)
|
await Promise.all([
|
||||||
|
loadDeviceCards(device.virtual_no),
|
||||||
|
loadDefaultIotCards()
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载设备绑定的卡列表
|
// 加载设备绑定的卡列表
|
||||||
@@ -897,7 +917,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索IoT卡(根据ICCID)
|
// 搜索IoT卡(根据ICCID或运营商名称)
|
||||||
const searchIotCards = async (query: string) => {
|
const searchIotCards = async (query: string) => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
await loadDefaultIotCards()
|
await loadDefaultIotCards()
|
||||||
@@ -906,12 +926,17 @@
|
|||||||
iotCardSearchLoading.value = true
|
iotCardSearchLoading.value = true
|
||||||
iotCardHasSearched.value = true
|
iotCardHasSearched.value = true
|
||||||
try {
|
try {
|
||||||
const res = await CardService.getStandaloneIotCards({
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
iccid: query,
|
|
||||||
is_standalone: true
|
is_standalone: true
|
||||||
})
|
}
|
||||||
|
if (bindCardForm.search_type === 'iccid') {
|
||||||
|
params.iccid = query
|
||||||
|
} else if (bindCardForm.search_type === 'carrier_name') {
|
||||||
|
params.carrier_name = query
|
||||||
|
}
|
||||||
|
const res = await CardService.getStandaloneIotCards(params)
|
||||||
if (res.code === 0 && res.data?.items) {
|
if (res.code === 0 && res.data?.items) {
|
||||||
iotCardList.value = res.data.items
|
iotCardList.value = res.data.items
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,7 +562,7 @@
|
|||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
import { CardService, ShopService, PackageSeriesService, AssetService } from '@/api/modules'
|
import { CardService, ShopService, PackageSeriesService, AssetService, CarrierService } from '@/api/modules'
|
||||||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
||||||
@@ -627,6 +627,38 @@
|
|||||||
failed_items: null
|
failed_items: null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 运营商搜索相关
|
||||||
|
const carrierLoading = ref(false)
|
||||||
|
const searchCarrierOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
// 加载搜索栏运营商选项(默认加载10条)
|
||||||
|
const loadSearchCarrierOptions = async (carrierName?: string) => {
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 10
|
||||||
|
}
|
||||||
|
if (carrierName) {
|
||||||
|
params.carrier_name = carrierName
|
||||||
|
}
|
||||||
|
const res = await CarrierService.getCarriers(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
searchCarrierOptions.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载运营商选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索运营商(用于搜索栏)
|
||||||
|
const handleSearchCarrier = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
loadSearchCarrierOptions(query)
|
||||||
|
} else {
|
||||||
|
loadSearchCarrierOptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 卡详情弹窗相关
|
// 卡详情弹窗相关
|
||||||
const cardDetailDialogVisible = ref(false)
|
const cardDetailDialogVisible = ref(false)
|
||||||
const cardDetailLoading = ref(false)
|
const cardDetailLoading = ref(false)
|
||||||
@@ -730,8 +762,8 @@
|
|||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState: {
|
const initialSearchState: {
|
||||||
status: undefined | number
|
status: undefined | number
|
||||||
carrier_id: undefined | number
|
|
||||||
iccid: string
|
iccid: string
|
||||||
|
carrier_name: string
|
||||||
msisdn: string
|
msisdn: string
|
||||||
virtual_no: string
|
virtual_no: string
|
||||||
device_virtual_no: string
|
device_virtual_no: string
|
||||||
@@ -740,8 +772,8 @@
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
} = {
|
} = {
|
||||||
status: undefined,
|
status: undefined,
|
||||||
carrier_id: undefined,
|
|
||||||
iccid: '',
|
iccid: '',
|
||||||
|
carrier_name: '',
|
||||||
msisdn: '',
|
msisdn: '',
|
||||||
virtual_no: '',
|
virtual_no: '',
|
||||||
device_virtual_no: '',
|
device_virtual_no: '',
|
||||||
@@ -863,20 +895,6 @@
|
|||||||
{ label: '已停用', value: 4 }
|
{ label: '已停用', value: 4 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '运营商',
|
|
||||||
prop: 'carrier_id',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
placeholder: '全部'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '中国移动', value: 1 },
|
|
||||||
{ label: '中国联通', value: 2 },
|
|
||||||
{ label: '中国电信', value: 3 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'ICCID',
|
label: 'ICCID',
|
||||||
prop: 'iccid',
|
prop: 'iccid',
|
||||||
@@ -886,6 +904,24 @@
|
|||||||
placeholder: '请输入ICCID'
|
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: '卡接入号',
|
label: '卡接入号',
|
||||||
prop: 'msisdn',
|
prop: 'msisdn',
|
||||||
@@ -1180,20 +1216,22 @@
|
|||||||
const getActions = (row: StandaloneIotCard) => {
|
const getActions = (row: StandaloneIotCard) => {
|
||||||
const actions: any[] = []
|
const actions: any[] = []
|
||||||
|
|
||||||
if (hasAuth('iot_card:start_card')) {
|
if (row.network_status === 1) {
|
||||||
actions.push({
|
if (hasAuth('iot_card:stop_card')) {
|
||||||
label: '启用此卡',
|
actions.push({
|
||||||
handler: () => handleCardOperation('start-card', row.iccid),
|
label: '停用此卡',
|
||||||
type: 'primary'
|
handler: () => handleCardOperation('stop-card', row.iccid),
|
||||||
})
|
type: 'primary'
|
||||||
}
|
})
|
||||||
|
}
|
||||||
if (hasAuth('iot_card:stop_card')) {
|
} else {
|
||||||
actions.push({
|
if (hasAuth('iot_card:start_card')) {
|
||||||
label: '停用此卡',
|
actions.push({
|
||||||
handler: () => handleCardOperation('stop-card', row.iccid),
|
label: '启用此卡',
|
||||||
type: 'primary'
|
handler: () => handleCardOperation('start-card', row.iccid),
|
||||||
})
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAuth('iot_card:realname_policy')) {
|
if (hasAuth('iot_card:realname_policy')) {
|
||||||
@@ -1216,6 +1254,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
loadSearchCarrierOptions()
|
||||||
getTableData()
|
getTableData()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1228,7 +1267,6 @@
|
|||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
...formFilters
|
...formFilters
|
||||||
}
|
}
|
||||||
// 清理空值
|
|
||||||
Object.keys(params).forEach((key) => {
|
Object.keys(params).forEach((key) => {
|
||||||
if (params[key] === '' || params[key] === undefined) {
|
if (params[key] === '' || params[key] === undefined) {
|
||||||
delete params[key]
|
delete params[key]
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||||
<p>4. <strong>重要:列顺序固定,不可调整。</strong>系统按位置读取,不识别列名</p>
|
<p>4. <strong>重要:列顺序固定,不可调整。</strong>系统按位置读取,不识别列名</p>
|
||||||
<p>5. 必填列:虚拟号(第1列)</p>
|
<p style="color: var(--el-color-primary)">5. 必填列:虚拟号(第1列)</p>
|
||||||
<p
|
<p
|
||||||
>6.
|
>6.
|
||||||
可选列:设备名称、设备型号、设备类型、IMEI、制造商、最大SIM槽数(默认4,有效范围1-4)、卡1~卡4
|
可选列:设备名称、设备型号、设备类型、IMEI、制造商、最大SIM槽数(默认4,有效范围1-4)、卡1~卡4
|
||||||
@@ -443,77 +443,16 @@
|
|||||||
// 下载模板
|
// 下载模板
|
||||||
const downloadTemplate = async () => {
|
const downloadTemplate = async () => {
|
||||||
try {
|
try {
|
||||||
// 动态导入 xlsx 库
|
const link = document.createElement('a')
|
||||||
const XLSX = await import('xlsx')
|
link.href = new URL('@/template/设备导入模板.xlsx', import.meta.url).href
|
||||||
|
link.download = '设备导入模板.xlsx'
|
||||||
// 创建示例数据(按固定列顺序)
|
document.body.appendChild(link)
|
||||||
const templateData = [
|
link.click()
|
||||||
{
|
document.body.removeChild(link)
|
||||||
虚拟号: '862639070731999',
|
|
||||||
设备名称: '智能水表01',
|
|
||||||
设备型号: 'WM-2000',
|
|
||||||
设备类型: '智能水表',
|
|
||||||
IMEI: '860123456789012',
|
|
||||||
制造商: '华为',
|
|
||||||
最大SIM槽数: 4,
|
|
||||||
'卡1 ICCID': '89860123456789012345',
|
|
||||||
'卡2 ICCID': '',
|
|
||||||
'卡3 ICCID': '',
|
|
||||||
'卡4 ICCID': ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
虚拟号: '862639070750932',
|
|
||||||
设备名称: 'GPS定位器01',
|
|
||||||
设备型号: 'GPS-3000',
|
|
||||||
设备类型: '定位设备',
|
|
||||||
IMEI: '860123456789013',
|
|
||||||
制造商: '小米',
|
|
||||||
最大SIM槽数: 2,
|
|
||||||
'卡1 ICCID': '89860123456789012346',
|
|
||||||
'卡2 ICCID': '89860123456789012347',
|
|
||||||
'卡3 ICCID': '',
|
|
||||||
'卡4 ICCID': ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 创建工作簿
|
|
||||||
const wb = XLSX.utils.book_new()
|
|
||||||
const ws = XLSX.utils.json_to_sheet(templateData)
|
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
ws['!cols'] = [
|
|
||||||
{ wch: 20 }, // 虚拟号
|
|
||||||
{ wch: 15 }, // 设备名称
|
|
||||||
{ wch: 15 }, // 设备型号
|
|
||||||
{ wch: 15 }, // 设备类型
|
|
||||||
{ wch: 18 }, // IMEI
|
|
||||||
{ wch: 12 }, // 制造商
|
|
||||||
{ wch: 15 }, // 最大SIM槽数
|
|
||||||
{ wch: 25 }, // 卡1 ICCID
|
|
||||||
{ wch: 25 }, // 卡2 ICCID
|
|
||||||
{ wch: 25 }, // 卡3 ICCID
|
|
||||||
{ wch: 25 } // 卡4 ICCID
|
|
||||||
]
|
|
||||||
|
|
||||||
// 将所有单元格设置为文本格式
|
|
||||||
const range = XLSX.utils.decode_range(ws['!ref'] || 'A1')
|
|
||||||
for (let R = range.s.r; R <= range.e.r; ++R) {
|
|
||||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
|
||||||
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C })
|
|
||||||
if (!ws[cellAddress]) continue
|
|
||||||
ws[cellAddress].t = 's' // 设置为字符串类型
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加工作表到工作簿
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, '设备导入模板')
|
|
||||||
|
|
||||||
// 导出文件
|
|
||||||
XLSX.writeFile(wb, '设备导入模板.xlsx')
|
|
||||||
|
|
||||||
ElMessage.success('设备导入模板下载成功')
|
ElMessage.success('设备导入模板下载成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载模板失败:', error)
|
console.error('下载模板失败:', error)
|
||||||
|
ElMessage.error('下载模板失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,10 +105,8 @@
|
|||||||
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
||||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||||
<p>4. <strong>必填字段:ICCID、MSISDN(手机号)、virtual_no(虚拟号)</strong></p>
|
<p>4. <span style="color: var(--el-color-primary)">必填字段:ICCID、MSISDN</span></p>
|
||||||
<p>5. <strong>Excel 文件必须包含 virtual_no 列,否则整批导入将失败</strong></p>
|
<p>5. 必须选择运营商</p>
|
||||||
<p>6. virtual_no 为空将导入失败</p>
|
|
||||||
<p>7. 必须选择运营商</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElAlert>
|
</ElAlert>
|
||||||
@@ -642,58 +640,16 @@
|
|||||||
// 下载模板
|
// 下载模板
|
||||||
const downloadTemplate = async () => {
|
const downloadTemplate = async () => {
|
||||||
try {
|
try {
|
||||||
// 动态导入 xlsx 库
|
const link = document.createElement('a')
|
||||||
const XLSX = await import('xlsx')
|
link.href = new URL('@/template/IoT卡导入模板.xlsx', import.meta.url).href
|
||||||
|
link.download = 'IoT卡导入模板.xlsx'
|
||||||
// 创建示例数据(按固定列顺序:ICCID、MSISDN、虚拟号)
|
document.body.appendChild(link)
|
||||||
const templateData = [
|
link.click()
|
||||||
{
|
document.body.removeChild(link)
|
||||||
ICCID: '89860123456789012345',
|
|
||||||
'MSISDN(接入号)': '13800138000',
|
|
||||||
虚拟号: 'V001'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ICCID: '89860123456789012346',
|
|
||||||
'MSISDN(接入号)': '13800138001',
|
|
||||||
虚拟号: 'V002'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ICCID: '89860123456789012347',
|
|
||||||
'MSISDN(接入号)': '13800138002',
|
|
||||||
虚拟号: 'V003'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 创建工作簿
|
|
||||||
const wb = XLSX.utils.book_new()
|
|
||||||
const ws = XLSX.utils.json_to_sheet(templateData)
|
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
ws['!cols'] = [
|
|
||||||
{ wch: 25 }, // ICCID
|
|
||||||
{ wch: 20 }, // MSISDN(接入号)
|
|
||||||
{ wch: 15 } // 虚拟号
|
|
||||||
]
|
|
||||||
|
|
||||||
// 将所有单元格设置为文本格式,防止科学计数法
|
|
||||||
const range = XLSX.utils.decode_range(ws['!ref'] || 'A1')
|
|
||||||
for (let R = range.s.r; R <= range.e.r; ++R) {
|
|
||||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
|
||||||
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C })
|
|
||||||
if (!ws[cellAddress]) continue
|
|
||||||
ws[cellAddress].t = 's' // 设置为字符串类型
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加工作表到工作簿
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, 'IoT卡导入模板')
|
|
||||||
|
|
||||||
// 导出文件
|
|
||||||
XLSX.writeFile(wb, 'IoT卡导入模板.xlsx')
|
|
||||||
|
|
||||||
ElMessage.success('IoT卡导入模板下载成功')
|
ElMessage.success('IoT卡导入模板下载成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载模板失败:', error)
|
console.error('下载模板失败:', error)
|
||||||
|
ElMessage.error('下载模板失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -582,7 +582,8 @@
|
|||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20
|
page_size: 20,
|
||||||
|
role_type: 1
|
||||||
}
|
}
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
params.role_name = keyword
|
params.role_name = keyword
|
||||||
@@ -607,23 +608,9 @@
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
// 计算属性:过滤后的可分配角色
|
||||||
const filteredAvailableRoles = computed(() => {
|
const filteredAvailableRoles = computed(() => {
|
||||||
let roles = allRoles.value
|
return allRoles.value
|
||||||
|
|
||||||
// 根据账号类型过滤角色
|
|
||||||
if (currentAccountType.value === 3) {
|
|
||||||
// 代理账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
} else if (currentAccountType.value === 2) {
|
|
||||||
// 平台用户:只显示平台角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 1)
|
|
||||||
} else if (currentAccountType.value === 4) {
|
|
||||||
// 企业账号:只显示客户角色
|
|
||||||
roles = roles.filter((role) => role.role_type === 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算属性:过滤后的已分配角色
|
// 计算属性:过滤后的已分配角色
|
||||||
|
|||||||
@@ -94,9 +94,7 @@
|
|||||||
title: '订单信息',
|
title: '订单信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: '充值单号', prop: 'recharge_no' },
|
{ label: '充值单号', prop: 'recharge_no' },
|
||||||
{ label: '充值记录ID', prop: 'id' },
|
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{ label: '店铺ID', prop: 'shop_id' },
|
|
||||||
{
|
{
|
||||||
label: '充值金额',
|
label: '充值金额',
|
||||||
formatter: (_, data) => formatCurrency(data.amount)
|
formatter: (_, data) => formatCurrency(data.amount)
|
||||||
@@ -122,15 +120,6 @@
|
|||||||
prop: 'payment_channel',
|
prop: 'payment_channel',
|
||||||
formatter: (value) => value || '-'
|
formatter: (value) => value || '-'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '支付配置ID',
|
|
||||||
prop: 'payment_config_id',
|
|
||||||
formatter: (value) => value || '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '代理钱包ID',
|
|
||||||
prop: 'agent_wallet_id'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '第三方支付流水号',
|
label: '第三方支付流水号',
|
||||||
prop: 'payment_transaction_id',
|
prop: 'payment_transaction_id',
|
||||||
|
|||||||
@@ -14,12 +14,6 @@
|
|||||||
<ElTag :type="getStatusType(refund.status)">{{ getStatusText(refund.status) }}</ElTag>
|
<ElTag :type="getStatusType(refund.status)">{{ getStatusText(refund.status) }}</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="订单ID">{{ refund.order_id }}</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="店铺ID">{{ refund.shop_id }}</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="套餐使用记录ID">
|
|
||||||
{{ refund.package_usage_id || '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="申请退款金额">
|
<ElDescriptionsItem label="申请退款金额">
|
||||||
{{ formatCurrency(refund.requested_refund_amount) }}
|
{{ formatCurrency(refund.requested_refund_amount) }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -42,34 +36,29 @@
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="退款原因" :span="2">
|
<ElDescriptionsItem label="退款原因">
|
||||||
{{ refund.refund_reason || '-' }}
|
{{ refund.refund_reason || '-' }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="拒绝原因" :span="2">
|
<ElDescriptionsItem label="拒绝原因">
|
||||||
{{ refund.reject_reason || '-' }}
|
{{ refund.reject_reason || '-' }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="审批备注" :span="2">
|
<ElDescriptionsItem label="审批时间">
|
||||||
{{ refund.remark || '-' }}
|
{{ refund.processed_at ? formatDateTime(refund.processed_at) : '-' }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="创建人ID">{{ refund.creator }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="审批备注">
|
||||||
<ElDescriptionsItem label="审批人ID">
|
{{ refund.remark || '-' }}
|
||||||
{{ refund.processor_id || '-' }}
|
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="创建时间">
|
<ElDescriptionsItem label="创建时间">
|
||||||
{{ formatDateTime(refund.created_at) }}
|
{{ formatDateTime(refund.created_at) }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="审批时间">
|
|
||||||
{{ refund.processed_at ? formatDateTime(refund.processed_at) : '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
|
|
||||||
<ElDescriptionsItem label="更新时间">
|
<ElDescriptionsItem label="更新时间">
|
||||||
{{ formatDateTime(refund.updated_at) }}
|
{{ formatDateTime(refund.updated_at) }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="更新人ID">{{ refund.updater }}</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
|
||||||
<div class="action-buttons" v-if="refund">
|
<div class="action-buttons" v-if="refund">
|
||||||
|
|||||||
@@ -63,11 +63,12 @@
|
|||||||
placeholder="请输入订单号搜索"
|
placeholder="请输入订单号搜索"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@focus="handleOrderSelectFocus"
|
@focus="handleOrderSelectFocus"
|
||||||
|
@change="handleOrderSelectChange"
|
||||||
>
|
>
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="order in orderSearchOptions"
|
v-for="order in orderSearchOptions"
|
||||||
:key="order.id"
|
:key="order.id"
|
||||||
:label="`订单号: ${order.order_no} (ID: ${order.id})`"
|
:label="order.order_no"
|
||||||
:value="order.id"
|
:value="order.id"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
@@ -75,21 +76,18 @@
|
|||||||
<ElFormItem label="申请退款金额" prop="requested_refund_amount">
|
<ElFormItem label="申请退款金额" prop="requested_refund_amount">
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="createForm.requested_refund_amount"
|
v-model="createForm.requested_refund_amount"
|
||||||
:min="1"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="1"
|
:step="1"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入申请退款金额(元)"
|
placeholder="请输入申请退款金额(元)"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="实收金额" prop="actual_received_amount">
|
<ElFormItem label="实收金额">
|
||||||
<ElInputNumber
|
<ElInput
|
||||||
v-model="createForm.actual_received_amount"
|
:model-value="selectedOrderActualPaid"
|
||||||
:min="1"
|
disabled
|
||||||
:precision="2"
|
|
||||||
:step="1"
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入实收金额(元)"
|
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="退款原因">
|
<ElFormItem label="退款原因">
|
||||||
@@ -434,8 +432,7 @@
|
|||||||
|
|
||||||
const createRules = reactive<FormRules>({
|
const createRules = reactive<FormRules>({
|
||||||
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
||||||
requested_refund_amount: [{ required: true, message: '请输入申请退款金额', trigger: 'blur' }],
|
requested_refund_amount: [{ required: true, message: '请输入申请退款金额', trigger: 'blur' }]
|
||||||
actual_received_amount: [{ required: true, message: '请输入实收金额', trigger: 'blur' }]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const approveRules = reactive<FormRules>({})
|
const approveRules = reactive<FormRules>({})
|
||||||
@@ -485,6 +482,15 @@
|
|||||||
|
|
||||||
const refundList = ref<Refund[]>([])
|
const refundList = ref<Refund[]>([])
|
||||||
|
|
||||||
|
// 选中的订单实收金额
|
||||||
|
const selectedOrderActualPaid = computed(() => {
|
||||||
|
if (!createForm.order_id) return 0
|
||||||
|
const selectedOrder = orderSearchOptions.value.find((o) => o.id === createForm.order_id)
|
||||||
|
if (!selectedOrder?.actual_paid_amount) return 0
|
||||||
|
const amount = selectedOrder.actual_paid_amount / 100
|
||||||
|
return isNaN(amount) ? 0 : amount
|
||||||
|
})
|
||||||
|
|
||||||
// 格式化货币 - 将分转换为元
|
// 格式化货币 - 将分转换为元
|
||||||
const formatCurrency = (amount: number): string => {
|
const formatCurrency = (amount: number): string => {
|
||||||
return `¥${(amount / 100).toFixed(2)}`
|
return `¥${(amount / 100).toFixed(2)}`
|
||||||
@@ -684,6 +690,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 订单选择变化 - 自动填充实收金额
|
||||||
|
const handleOrderSelectChange = (orderId: number | null) => {
|
||||||
|
if (!orderId) {
|
||||||
|
createForm.actual_received_amount = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selectedOrder = orderSearchOptions.value.find((o) => o.id === orderId)
|
||||||
|
if (selectedOrder) {
|
||||||
|
createForm.actual_received_amount = selectedOrder.actual_paid_amount / 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取退款申请列表
|
// 获取退款申请列表
|
||||||
const getTableData = async () => {
|
const getTableData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user