This commit is contained in:
@@ -280,6 +280,11 @@
|
||||
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 {
|
||||
|
||||
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
|
||||
dialogType.value = type
|
||||
|
||||
@@ -411,6 +411,10 @@
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
if (type === 'add') {
|
||||
await loadAllRoles()
|
||||
}
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
formData.id = row.id
|
||||
formData.username = row.username
|
||||
@@ -590,7 +594,8 @@
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: keyword ? 100 : 20 // 搜索时加载更多,默认20条
|
||||
page_size: keyword ? 100 : 20, // 搜索时加载更多,默认20条
|
||||
role_type: 1
|
||||
}
|
||||
if (keyword) {
|
||||
params.role_name = keyword
|
||||
@@ -604,48 +609,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性:新建账号时可选的角色(根据账号类型过滤)
|
||||
// 计算属性:新建账号时可选的角色
|
||||
const availableRolesForCreate = computed(() => {
|
||||
let roles = 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
|
||||
return allRoles.value
|
||||
})
|
||||
|
||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||||
// 计算属性:过滤后的可分配角色
|
||||
const filteredAvailableRoles = computed(() => {
|
||||
let roles = 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
|
||||
return allRoles.value
|
||||
})
|
||||
|
||||
// 搜索防抖定时器
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ data.package_name }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐使用记录ID">
|
||||
{{ data.package_usage_id }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="总使用流量" :span="2">
|
||||
{{ formatDataSize(data.total_usage_mb) }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
@@ -171,20 +171,25 @@ export function useAssetInfo() {
|
||||
const loadCurrentPackage = async (identifier: string) => {
|
||||
try {
|
||||
currentPackageLoading.value = true
|
||||
// 清空之前的错误信息
|
||||
currentPackageErrorMsg.value = ''
|
||||
|
||||
const response = await AssetService.getCurrentPackage(identifier)
|
||||
if (response.code === 0) {
|
||||
if (response.data) {
|
||||
const pkg = response.data
|
||||
const [currentPkgResponse, packageListResponse] = await Promise.all([
|
||||
AssetService.getCurrentPackage(identifier),
|
||||
AssetService.getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||
])
|
||||
|
||||
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 = {
|
||||
id: pkg.package_usage_id || 0,
|
||||
package_id: pkg.package_id || 0,
|
||||
package_name: pkg.package_name || '',
|
||||
package_price: 0, // API 没有返回价格
|
||||
package_price: activePaidAmount,
|
||||
package_total_data: pkg.data_limit_mb || 0,
|
||||
real_data_used: 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 || '',
|
||||
status: pkg.status || 0,
|
||||
status_name: pkg.status_name,
|
||||
duration_days: undefined, // API 没有返回时长
|
||||
virtual_ratio: pkg.virtual_ratio, // 虚流量比例
|
||||
package_type: pkg.package_type, // 套餐类型
|
||||
enable_virtual_data: pkg.enable_virtual_data, // 是否启用虚流量
|
||||
duration_days: undefined,
|
||||
virtual_ratio: pkg.virtual_ratio,
|
||||
package_type: pkg.package_type,
|
||||
enable_virtual_data: pkg.enable_virtual_data,
|
||||
data_limit_mb: pkg.data_limit_mb,
|
||||
virtual_limit_mb: pkg.virtual_limit_mb,
|
||||
virtual_remain_mb: pkg.virtual_remain_mb,
|
||||
|
||||
@@ -338,34 +338,47 @@
|
||||
label-width="80"
|
||||
>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="10">
|
||||
<ElFormItem label="IoT卡" prop="iot_card_id">
|
||||
<ElCol :span="6">
|
||||
<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
|
||||
v-model="bindCardForm.iot_card_id"
|
||||
:placeholder="
|
||||
iotCardList.length === 0 && !iotCardSearchLoading
|
||||
? '当前没有未绑定设备的卡'
|
||||
: '请选择或搜索IoT卡(支持ICCID搜索)'
|
||||
bindCardForm.search_type === 'carrier_name'
|
||||
? '请选择或搜索运营商名称'
|
||||
: '请选择或搜索ICCID'
|
||||
"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchIotCards"
|
||||
:loading="iotCardSearchLoading"
|
||||
:disabled="iotCardList.length === 0 && !iotCardSearchLoading"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="card in iotCardList"
|
||||
:key="card.id"
|
||||
:label="`${card.iccid} ${card.msisdn ? '(' + card.msisdn + ')' : ''}`"
|
||||
:label="`${card.iccid} ${card.carrier_name ? '(' + card.carrier_name + ')' : ''}`"
|
||||
:value="card.id"
|
||||
/>
|
||||
<ElOption
|
||||
v-if="iotCardList.length === 0 && !iotCardSearchLoading"
|
||||
label="未找到相关数据"
|
||||
:value="undefined"
|
||||
disabled
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="10">
|
||||
<ElCol :span="6">
|
||||
<ElFormItem label="插槽位置" prop="slot_position">
|
||||
<ElSelect
|
||||
v-model="bindCardForm.slot_position"
|
||||
@@ -561,7 +574,8 @@
|
||||
ShopService,
|
||||
CardService,
|
||||
PackageSeriesService,
|
||||
AssetService
|
||||
AssetService,
|
||||
CarrierService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElIcon, ElRadioGroup, ElRadio } from 'element-plus'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
@@ -656,11 +670,13 @@
|
||||
const iotCardSearchLoading = ref(false)
|
||||
const iotCardHasSearched = ref(false) // 标记是否已执行过搜索
|
||||
const bindCardForm = reactive({
|
||||
search_type: 'iccid' as 'iccid' | 'carrier_name',
|
||||
iot_card_id: undefined as number | undefined,
|
||||
slot_position: 1
|
||||
})
|
||||
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' }]
|
||||
})
|
||||
|
||||
@@ -855,10 +871,14 @@
|
||||
deviceCards.value = []
|
||||
deviceCardsDialogVisible.value = true
|
||||
// 重置绑定卡表单
|
||||
bindCardForm.search_type = 'iccid'
|
||||
bindCardForm.iot_card_id = undefined
|
||||
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) => {
|
||||
if (!query) {
|
||||
await loadDefaultIotCards()
|
||||
@@ -906,12 +926,17 @@
|
||||
iotCardSearchLoading.value = true
|
||||
iotCardHasSearched.value = true
|
||||
try {
|
||||
const res = await CardService.getStandaloneIotCards({
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
iccid: query,
|
||||
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) {
|
||||
iotCardList.value = res.data.items
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
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 { Loading } from '@element-plus/icons-vue'
|
||||
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
||||
@@ -627,6 +627,38 @@
|
||||
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 cardDetailLoading = ref(false)
|
||||
@@ -730,8 +762,8 @@
|
||||
// 搜索表单初始值
|
||||
const initialSearchState: {
|
||||
status: undefined | number
|
||||
carrier_id: undefined | number
|
||||
iccid: string
|
||||
carrier_name: string
|
||||
msisdn: string
|
||||
virtual_no: string
|
||||
device_virtual_no: string
|
||||
@@ -740,8 +772,8 @@
|
||||
[key: string]: any
|
||||
} = {
|
||||
status: undefined,
|
||||
carrier_id: undefined,
|
||||
iccid: '',
|
||||
carrier_name: '',
|
||||
msisdn: '',
|
||||
virtual_no: '',
|
||||
device_virtual_no: '',
|
||||
@@ -863,20 +895,6 @@
|
||||
{ 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',
|
||||
prop: 'iccid',
|
||||
@@ -886,6 +904,24 @@
|
||||
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: 'msisdn',
|
||||
@@ -1180,20 +1216,22 @@
|
||||
const getActions = (row: StandaloneIotCard) => {
|
||||
const actions: any[] = []
|
||||
|
||||
if (hasAuth('iot_card:start_card')) {
|
||||
actions.push({
|
||||
label: '启用此卡',
|
||||
handler: () => handleCardOperation('start-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:stop_card')) {
|
||||
actions.push({
|
||||
label: '停用此卡',
|
||||
handler: () => handleCardOperation('stop-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
if (row.network_status === 1) {
|
||||
if (hasAuth('iot_card:stop_card')) {
|
||||
actions.push({
|
||||
label: '停用此卡',
|
||||
handler: () => handleCardOperation('stop-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (hasAuth('iot_card:start_card')) {
|
||||
actions.push({
|
||||
label: '启用此卡',
|
||||
handler: () => handleCardOperation('start-card', row.iccid),
|
||||
type: 'primary'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAuth('iot_card:realname_policy')) {
|
||||
@@ -1216,6 +1254,7 @@
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchCarrierOptions()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
@@ -1228,7 +1267,6 @@
|
||||
page_size: pagination.pageSize,
|
||||
...formFilters
|
||||
}
|
||||
// 清理空值
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === '' || params[key] === undefined) {
|
||||
delete params[key]
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||
<p>4. <strong>重要:列顺序固定,不可调整。</strong>系统按位置读取,不识别列名</p>
|
||||
<p>5. 必填列:虚拟号(第1列)</p>
|
||||
<p style="color: var(--el-color-primary)">5. 必填列:虚拟号(第1列)</p>
|
||||
<p
|
||||
>6.
|
||||
可选列:设备名称、设备型号、设备类型、IMEI、制造商、最大SIM槽数(默认4,有效范围1-4)、卡1~卡4
|
||||
@@ -443,77 +443,16 @@
|
||||
// 下载模板
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
// 动态导入 xlsx 库
|
||||
const XLSX = await import('xlsx')
|
||||
|
||||
// 创建示例数据(按固定列顺序)
|
||||
const templateData = [
|
||||
{
|
||||
虚拟号: '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')
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = new URL('@/template/设备导入模板.xlsx', import.meta.url).href
|
||||
link.download = '设备导入模板.xlsx'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
ElMessage.success('设备导入模板下载成功')
|
||||
} catch (error) {
|
||||
console.error('下载模板失败:', error)
|
||||
ElMessage.error('下载模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,10 +105,8 @@
|
||||
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||
<p>4. <strong>必填字段:ICCID、MSISDN(手机号)、virtual_no(虚拟号)</strong></p>
|
||||
<p>5. <strong>Excel 文件必须包含 virtual_no 列,否则整批导入将失败</strong></p>
|
||||
<p>6. virtual_no 为空将导入失败</p>
|
||||
<p>7. 必须选择运营商</p>
|
||||
<p>4. <span style="color: var(--el-color-primary)">必填字段:ICCID、MSISDN</span></p>
|
||||
<p>5. 必须选择运营商</p>
|
||||
</div>
|
||||
</template>
|
||||
</ElAlert>
|
||||
@@ -642,58 +640,16 @@
|
||||
// 下载模板
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
// 动态导入 xlsx 库
|
||||
const XLSX = await import('xlsx')
|
||||
|
||||
// 创建示例数据(按固定列顺序:ICCID、MSISDN、虚拟号)
|
||||
const templateData = [
|
||||
{
|
||||
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')
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = new URL('@/template/IoT卡导入模板.xlsx', import.meta.url).href
|
||||
link.download = 'IoT卡导入模板.xlsx'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
ElMessage.success('IoT卡导入模板下载成功')
|
||||
} catch (error) {
|
||||
console.error('下载模板失败:', error)
|
||||
ElMessage.error('下载模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -582,7 +582,8 @@
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20
|
||||
page_size: 20,
|
||||
role_type: 1
|
||||
}
|
||||
if (keyword) {
|
||||
params.role_name = keyword
|
||||
@@ -607,23 +608,9 @@
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||||
// 计算属性:过滤后的可分配角色
|
||||
const filteredAvailableRoles = computed(() => {
|
||||
let roles = 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
|
||||
return allRoles.value
|
||||
})
|
||||
|
||||
// 计算属性:过滤后的已分配角色
|
||||
|
||||
@@ -94,9 +94,7 @@
|
||||
title: '订单信息',
|
||||
fields: [
|
||||
{ label: '充值单号', prop: 'recharge_no' },
|
||||
{ label: '充值记录ID', prop: 'id' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '店铺ID', prop: 'shop_id' },
|
||||
{
|
||||
label: '充值金额',
|
||||
formatter: (_, data) => formatCurrency(data.amount)
|
||||
@@ -122,15 +120,6 @@
|
||||
prop: 'payment_channel',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: '支付配置ID',
|
||||
prop: 'payment_config_id',
|
||||
formatter: (value) => value || '-'
|
||||
},
|
||||
{
|
||||
label: '代理钱包ID',
|
||||
prop: 'agent_wallet_id'
|
||||
},
|
||||
{
|
||||
label: '第三方支付流水号',
|
||||
prop: 'payment_transaction_id',
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
<ElTag :type="getStatusType(refund.status)">{{ getStatusText(refund.status) }}</ElTag>
|
||||
</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="申请退款金额">
|
||||
{{ formatCurrency(refund.requested_refund_amount) }}
|
||||
</ElDescriptionsItem>
|
||||
@@ -42,34 +36,29 @@
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="退款原因" :span="2">
|
||||
<ElDescriptionsItem label="退款原因">
|
||||
{{ refund.refund_reason || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="拒绝原因" :span="2">
|
||||
<ElDescriptionsItem label="拒绝原因">
|
||||
{{ refund.reject_reason || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="审批备注" :span="2">
|
||||
{{ refund.remark || '-' }}
|
||||
<ElDescriptionsItem label="审批时间">
|
||||
{{ refund.processed_at ? formatDateTime(refund.processed_at) : '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="创建人ID">{{ refund.creator }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="审批人ID">
|
||||
{{ refund.processor_id || '-' }}
|
||||
<ElDescriptionsItem label="审批备注">
|
||||
{{ refund.remark || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="创建时间">
|
||||
{{ formatDateTime(refund.created_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="审批时间">
|
||||
{{ refund.processed_at ? formatDateTime(refund.processed_at) : '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="更新时间">
|
||||
{{ formatDateTime(refund.updated_at) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="更新人ID">{{ refund.updater }}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<div class="action-buttons" v-if="refund">
|
||||
|
||||
@@ -63,11 +63,12 @@
|
||||
placeholder="请输入订单号搜索"
|
||||
style="width: 100%"
|
||||
@focus="handleOrderSelectFocus"
|
||||
@change="handleOrderSelectChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="order in orderSearchOptions"
|
||||
:key="order.id"
|
||||
:label="`订单号: ${order.order_no} (ID: ${order.id})`"
|
||||
:label="order.order_no"
|
||||
:value="order.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
@@ -75,21 +76,18 @@
|
||||
<ElFormItem label="申请退款金额" prop="requested_refund_amount">
|
||||
<ElInputNumber
|
||||
v-model="createForm.requested_refund_amount"
|
||||
:min="1"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
placeholder="请输入申请退款金额(元)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="实收金额" prop="actual_received_amount">
|
||||
<ElInputNumber
|
||||
v-model="createForm.actual_received_amount"
|
||||
:min="1"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
<ElFormItem label="实收金额">
|
||||
<ElInput
|
||||
:model-value="selectedOrderActualPaid"
|
||||
disabled
|
||||
style="width: 100%"
|
||||
placeholder="请输入实收金额(元)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="退款原因">
|
||||
@@ -434,8 +432,7 @@
|
||||
|
||||
const createRules = reactive<FormRules>({
|
||||
order_id: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
|
||||
requested_refund_amount: [{ required: true, message: '请输入申请退款金额', trigger: 'blur' }],
|
||||
actual_received_amount: [{ required: true, message: '请输入实收金额', trigger: 'blur' }]
|
||||
requested_refund_amount: [{ required: true, message: '请输入申请退款金额', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const approveRules = reactive<FormRules>({})
|
||||
@@ -485,6 +482,15 @@
|
||||
|
||||
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 => {
|
||||
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 () => {
|
||||
loading.value = true
|
||||
|
||||
Reference in New Issue
Block a user