Compare commits

...

2 Commits

  1. 1
      src/components/DockEditModal.vue
  2. 7
      src/config/urls.js
  3. 2
      src/stores/modules/devicesStore.js
  4. 51
      src/views/AccountView/AccountView.vue
  5. 6
      src/views/DevicesView/DevicesView.vue
  6. 46
      src/views/TrafficOpsView/TrafficOpsView.vue

1
src/components/DockEditModal.vue

@ -15,7 +15,6 @@
<form novalidate @submit.prevent="submit"> <form novalidate @submit.prevent="submit">
<label><span>机巢名称<b>*</b></span><t-input v-model="form.name" :maxlength="64" placeholder="例如:青山湖 03 号机巢" /></label> <label><span>机巢名称<b>*</b></span><t-input v-model="form.name" :maxlength="64" placeholder="例如:青山湖 03 号机巢" /></label>
<label><span>机巢 SN</span><t-input :model-value="dock?.sn || ''" readonly /></label> <label><span>机巢 SN</span><t-input :model-value="dock?.sn || ''" readonly /></label>
<label><span>通信卡 ICCID</span><t-input :model-value="dock?.iccid || ''" readonly /></label>
<div class="form-row"> <div class="form-row">
<label><span>经度<b>*</b></span><t-input-number v-model="form.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" placeholder="例如:120.219375" style="width: 100%" /></label> <label><span>经度<b>*</b></span><t-input-number v-model="form.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" placeholder="例如:120.219375" style="width: 100%" /></label>
<label><span>纬度<b>*</b></span><t-input-number v-model="form.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" placeholder="例如:30.259244" style="width: 100%" /></label> <label><span>纬度<b>*</b></span><t-input-number v-model="form.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" placeholder="例如:30.259244" style="width: 100%" /></label>

7
src/config/urls.js

@ -70,6 +70,7 @@ export const INVOICE_REQUESTS = '/v1/account/invoice-requests'
export const PAYMENTS = '/v1/account/payments' export const PAYMENTS = '/v1/account/payments'
export const PAYMENT = (id) => `/v1/account/payments/${id}` export const PAYMENT = (id) => `/v1/account/payments/${id}`
export const SIM_CARDS = '/v1/account/sim-cards' export const SIM_CARDS = '/v1/account/sim-cards'
export const SIM_CARD_CREATE = '/v1/account/sim-cards'
export const SIM_CARD = (id) => `/v1/account/sim-cards/${id}` export const SIM_CARD = (id) => `/v1/account/sim-cards/${id}`
export const SIM_PACKAGES = '/v1/account/sim-packages' export const SIM_PACKAGES = '/v1/account/sim-packages'
export const SIM_CARD_RECHARGE_ORDERS = (id) => `/v1/account/sim-cards/${id}/recharge-orders` export const SIM_CARD_RECHARGE_ORDERS = (id) => `/v1/account/sim-cards/${id}/recharge-orders`
@ -84,7 +85,11 @@ export const ADMIN_TRAFFIC_PACKAGE = (id) => `/v1/admin/traffic/packages/${id}`
export const ADMIN_TRAFFIC_LEDGER = '/v1/admin/traffic/ledger' export const ADMIN_TRAFFIC_LEDGER = '/v1/admin/traffic/ledger'
export const ADMIN_TRAFFIC_TEST_GRANTS = '/v1/admin/traffic/test-grants' export const ADMIN_TRAFFIC_TEST_GRANTS = '/v1/admin/traffic/test-grants'
// Users
// Sim 通信卡套餐管理
export const ADMIN_SIM_PACKAGES = '/v1/admin/sim-packages'
export const ADMIN_SIM_PROVIDER_PLANS = '/v1/admin/sim-packages/provider-plans'
export const ADMIN_SIM_PACKAGE = (id) => `/v1/admin/sim-packages/${id}`
export const USERS = '/v1/users' export const USERS = '/v1/users'
// Firmwares // Firmwares

2
src/stores/modules/devicesStore.js

@ -217,7 +217,7 @@ export const useDeviceStore = defineStore('devices', () => {
name, name,
code: d.code || d.sn || d.dockId, code: d.code || d.sn || d.dockId,
sn: d.sn || d.dockId || '--', sn: d.sn || d.dockId || '--',
iccid: d.iccid || '--',
iccid: '--',
longitude: coordinate(d.longitude, -180, 180), longitude: coordinate(d.longitude, -180, 180),
latitude: coordinate(d.latitude, -90, 90), latitude: coordinate(d.latitude, -90, 90),
location: d.location || '--', location: d.location || '--',

51
src/views/AccountView/AccountView.vue

@ -137,6 +137,7 @@
<div class="business-panel account-panel" :class="{ active: tab === 'sim' }" style="padding:16px"> <div class="business-panel account-panel" :class="{ active: tab === 'sim' }" style="padding:16px">
<div class="communication-status-grid"><div><strong>{{ simTotal }}</strong><span>通信卡总数</span></div><div><strong>{{ activeSimCount }}</strong><span>正常</span></div><div><strong>{{ warnSimCount }}</strong><span>异常或未同步</span></div></div> <div class="communication-status-grid"><div><strong>{{ simTotal }}</strong><span>通信卡总数</span></div><div><strong>{{ activeSimCount }}</strong><span>正常</span></div><div><strong>{{ warnSimCount }}</strong><span>异常或未同步</span></div></div>
<div class="account-card-actions"><t-button theme="primary" @click="openSimAdd">新增通信卡</t-button></div>
<t-table row-key="id" :data="simCards" :columns="simColumns" empty="暂无通信卡"> <t-table row-key="id" :data="simCards" :columns="simColumns" empty="暂无通信卡">
<template #carrier="{ row }"><strong>{{ row.carrier || '--' }}</strong></template> <template #carrier="{ row }"><strong>{{ row.carrier || '--' }}</strong></template>
<template #iccid="{ row }"><strong>{{ row.iccid || '--' }}</strong><small>{{ row.phone || '--' }}</small></template> <template #iccid="{ row }"><strong>{{ row.iccid || '--' }}</strong><small>{{ row.phone || '--' }}</small></template>
@ -145,7 +146,13 @@
<template #status="{ row }"><span class="carrier-status" :class="simStatus(row).cls"><i></i>{{ simStatus(row).name }}</span></template> <template #status="{ row }"><span class="carrier-status" :class="simStatus(row).cls"><i></i>{{ simStatus(row).name }}</span></template>
<template #lastSyncAt="{ row }">{{ fmtTime(row.lastSyncAt) }}</template> <template #lastSyncAt="{ row }">{{ fmtTime(row.lastSyncAt) }}</template>
<template #actions="{ row }"> <template #actions="{ row }">
<t-button variant="text" theme="primary" @click="openSimRecharge(row)">充值</t-button>
<t-button
variant="text"
theme="primary"
:loading="syncingSimId === String(row.id)"
@click="syncSimCard(row)"
>同步</t-button>
<t-button variant="text" theme="danger" @click="deleteSimCard(row)">删除</t-button>
</template> </template>
</t-table> </t-table>
<div class="table-footer" v-if="simTotal"><t-pagination v-model:current="simPageNum" :total="simTotal" :page-size="10" @current-change="loadSim" /></div> <div class="table-footer" v-if="simTotal"><t-pagination v-model:current="simPageNum" :total="simTotal" :page-size="10" @current-change="loadSim" /></div>
@ -180,7 +187,7 @@
</div> </div>
<div class="account-subsection-title"><h3>通信卡充值记录</h3><span> {{ simRechargeTotal }} </span></div> <div class="account-subsection-title"><h3>通信卡充值记录</h3><span> {{ simRechargeTotal }} </span></div>
<t-table row-key="id" :data="simRechargeLogs" :columns="simRechargeColumns" empty="暂无通信卡充值记录"> <t-table row-key="id" :data="simRechargeLogs" :columns="simRechargeColumns" empty="暂无通信卡充值记录">
<template #simCardId="{ row }">{{ cardLabel(row.simCardId) }}</template>
<template #simCardId="{ row }">{{ row.carrierSnapshot || '--' }} · {{ row.iccidSnapshot || row.simCardId || '--' }}</template>
<template #amountGb="{ row }"><strong>{{ row.amountGb }} GB</strong></template> <template #amountGb="{ row }"><strong>{{ row.amountGb }} GB</strong></template>
<template #totalPrice="{ row }"><strong>{{ money(row.totalPrice) }}</strong></template> <template #totalPrice="{ row }"><strong>{{ money(row.totalPrice) }}</strong></template>
<template #payStatus="{ row }"><em>{{ payStatusName(row.payStatus || row.paymentStatus) }}</em></template> <template #payStatus="{ row }"><em>{{ payStatusName(row.payStatus || row.paymentStatus) }}</em></template>
@ -266,6 +273,7 @@
<t-button theme="primary" @click="changePassword">确认修改</t-button> <t-button theme="primary" @click="changePassword">确认修改</t-button>
</template> </template>
</t-dialog> </t-dialog>
<t-dialog v-model:visible="simAddOpen" header="新增通信卡" width="440px" destroy-on-close><label><span>ICCID<b>*</b></span><t-input v-model="simAddForm.iccid" placeholder="请输入通信卡 ICCID" /></label><label><span>绑定机巢</span><t-input v-model="simAddForm.dockId" placeholder="可选" /></label><template #footer><t-button variant="outline" @click="simAddOpen=false">取消</t-button><t-button theme="primary" @click="submitSimAdd">确认添加</t-button></template></t-dialog>
<t-dialog <t-dialog
v-model:visible="simRechargeOpen" v-model:visible="simRechargeOpen"
header="通信卡充值" header="通信卡充值"
@ -380,7 +388,9 @@ const usage = ref([]); const usageTotal = ref(0); const usagePages = ref(1); con
const simCards = ref([]); const simTotal = ref(0); const simPages = ref(1); const simPageNum = ref(1) const simCards = ref([]); const simTotal = ref(0); const simPages = ref(1); const simPageNum = ref(1)
const simRechargeLogs = ref([]); const simRechargeTotal = ref(0); const simRechargePages = ref(1); const simRechargePageNum = ref(1) const simRechargeLogs = ref([]); const simRechargeTotal = ref(0); const simRechargePages = ref(1); const simRechargePageNum = ref(1)
const simPackages = ref([]) const simPackages = ref([])
const simRechargeOpen = ref(false)
const syncingSimId = ref(null)
const simAddOpen = ref(false)
const simAddForm = ref({ iccid: '', dockId: '' })
const simRechargeCard = ref(null) const simRechargeCard = ref(null)
const selectedSimPackageId = ref(0) const selectedSimPackageId = ref(0)
const simRecharging = ref(false) const simRecharging = ref(false)
@ -618,7 +628,40 @@ async function createInvoiceRequest() { try { await request.post(urls.INVOICE_RE
async function loadOrders() { try { const page = await request.get(urls.TRAFFIC_ORDERS, { params: { pageNum: orderPageNum.value, pageSize: 10 } }); const meta = applyPage(orders, page); orderTotal.value = meta.total; orderPages.value = meta.pages } catch { orders.value = [] } } async function loadOrders() { try { const page = await request.get(urls.TRAFFIC_ORDERS, { params: { pageNum: orderPageNum.value, pageSize: 10 } }); const meta = applyPage(orders, page); orderTotal.value = meta.total; orderPages.value = meta.pages } catch { orders.value = [] } }
async function loadUsage() { try { const page = await request.get(urls.TRAFFIC_USAGE, { params: { pageNum: usagePageNum.value, pageSize: 10 } }); const meta = applyPage(usage, page); usageTotal.value = meta.total; usagePages.value = meta.pages } catch { usage.value = [] } } async function loadUsage() { try { const page = await request.get(urls.TRAFFIC_USAGE, { params: { pageNum: usagePageNum.value, pageSize: 10 } }); const meta = applyPage(usage, page); usageTotal.value = meta.total; usagePages.value = meta.pages } catch { usage.value = [] } }
async function loadSim() { const [cards, logs] = await Promise.all([request.get(urls.SIM_CARDS, { params: { pageNum: simPageNum.value, pageSize: 10 } }), request.get(urls.SIM_RECHARGE_LOGS, { params: { pageNum: simRechargePageNum.value, pageSize: 10 } })]); const cardMeta = applyPage(simCards, cards); const logMeta = applyPage(simRechargeLogs, logs); simTotal.value = cardMeta.total; simPages.value = cardMeta.pages; simRechargeTotal.value = logMeta.total; simRechargePages.value = logMeta.pages }
async function syncSimCard(card) {
if (!card?.id || syncingSimId.value) return
syncingSimId.value = String(card.id)
try {
await request.get(urls.SIM_CARD(card.id), { params: { refresh: true } })
await loadSim()
ui.toast('通信卡信息已同步')
} catch (error) {
// HTTP 便
} finally {
syncingSimId.value = null
}
}
function openSimAdd() { simAddForm.value = { iccid: '', dockId: '' }; simAddOpen.value = true }
async function submitSimAdd() {
if (!simAddForm.value.iccid.trim()) { ui.toast('请输入 ICCID'); return }
try { await request.post(urls.SIM_CARD_CREATE, simAddForm.value); simAddOpen.value = false; await loadSim(); ui.toast('通信卡已添加') } catch {}
}
async function deleteSimCard(card) { const ok = await ui.confirm('确认删除该通信卡?', '删除只移除通信卡主数据,充值订单、用量快照等历史记录会保留。'); if (!ok) return; try { await request.delete(urls.SIM_CARD(card.id)); await loadSim(); ui.toast('通信卡已删除') } catch {} }
async function loadSim() {
const [cards, logs] = await Promise.all([
request.get(urls.SIM_CARDS, { params: { pageNum: simPageNum.value, pageSize: 10 } }),
request.get(urls.SIM_RECHARGE_LOGS, { params: { pageNum: simRechargePageNum.value, pageSize: 10 } })
])
const cardMeta = applyPage(simCards, cards)
const logMeta = applyPage(simRechargeLogs, logs)
simTotal.value = cardMeta.total
simPages.value = cardMeta.pages
simRechargeTotal.value = logMeta.total
simRechargePages.value = logMeta.pages
}
async function loadDeviceCount() { try { await deviceStore.load(); deviceCount.value = deviceStore.docks.length + deviceStore.drones.length } catch { deviceLoadFailed.value = true } } async function loadDeviceCount() { try { await deviceStore.load(); deviceCount.value = deviceStore.docks.length + deviceStore.drones.length } catch { deviceLoadFailed.value = true } }
async function loadTrafficPackages() { async function loadTrafficPackages() {
try { try {

6
src/views/DevicesView/DevicesView.vue

@ -172,7 +172,7 @@
<form novalidate @submit.prevent="submitAddDock"> <form novalidate @submit.prevent="submitAddDock">
<label><span>机巢名称<b>*</b></span><t-input v-model="dockForm.name" :maxlength="30" placeholder="例如:青山湖 03 号机巢" /></label> <label><span>机巢名称<b>*</b></span><t-input v-model="dockForm.name" :maxlength="30" placeholder="例如:青山湖 03 号机巢" /></label>
<label><span>机巢 SN<b>*</b></span><t-input v-model="dockForm.sn" :maxlength="40" placeholder="请输入机身铭牌 SN" /></label> <label><span>机巢 SN<b>*</b></span><t-input v-model="dockForm.sn" :maxlength="40" placeholder="请输入机身铭牌 SN" /></label>
<label><span>通信卡 ICCID<b>*</b></span><t-input v-model="dockForm.iccid" :maxlength="22" placeholder="输入通信卡 ICCID" /></label>
<label><span>通信卡 ICCID<b>*</b></span><t-input v-model="dockForm.iccid" :maxlength="32" placeholder="输入 ICCID 后自动创建通信卡并查询供应商信息" /></label>
<div class="form-row"> <div class="form-row">
<label><span>经度<b>*</b></span><t-input-number v-model="dockForm.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" placeholder="例如:120.219375" style="width: 100%" /></label> <label><span>经度<b>*</b></span><t-input-number v-model="dockForm.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" placeholder="例如:120.219375" style="width: 100%" /></label>
<label><span>纬度<b>*</b></span><t-input-number v-model="dockForm.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" placeholder="例如:30.259244" style="width: 100%" /></label> <label><span>纬度<b>*</b></span><t-input-number v-model="dockForm.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" placeholder="例如:30.259244" style="width: 100%" /></label>
@ -692,8 +692,8 @@ async function submitAddDock() {
addDockError.value = '纬度应在 -90~90 之间。' addDockError.value = '纬度应在 -90~90 之间。'
return return
} }
if (devices.docks.some((dock) => dock.sn.toUpperCase() === sn || dock.iccid.replace(/--/g, '') === iccid)) {
addDockError.value = '机巢 SN 或通信卡 ICCID 已存在。'
if (devices.docks.some((dock) => dock.sn.toUpperCase() === sn)) {
addDockError.value = '机巢 SN 已存在。'
return return
} }

46
src/views/TrafficOpsView/TrafficOpsView.vue

@ -15,6 +15,7 @@
<div class="business-workspace"> <div class="business-workspace">
<t-tabs v-model="tab" class="business-tdesign-tabs"> <t-tabs v-model="tab" class="business-tdesign-tabs">
<t-tab-panel value="packages" label="套餐管理" :destroy-on-hide="false" /> <t-tab-panel value="packages" label="套餐管理" :destroy-on-hide="false" />
<t-tab-panel value="sim-packages" label="通信卡套餐" :destroy-on-hide="false" />
<t-tab-panel value="purchases" label="采购入池" :destroy-on-hide="false" /> <t-tab-panel value="purchases" label="采购入池" :destroy-on-hide="false" />
<t-tab-panel value="ledger" label="账务流水" :destroy-on-hide="false" /> <t-tab-panel value="ledger" label="账务流水" :destroy-on-hide="false" />
</t-tabs> </t-tabs>
@ -74,6 +75,13 @@
</div> </div>
</div> </div>
<div v-show="tab === 'sim-packages'" class="packages-panel">
<div class="asset-toolbar"><div class="toolbar-group"><t-select v-model="simPkgStatus" :options="pkgStatusFilterOptions" style="width:140px" @change="reloadSimPackages" /></div><div class="page-actions"><t-button shape="square" variant="outline" @click="loadSimPackages">刷新</t-button><t-button variant="outline" @click="openProviderPlans">从 SIMBOSS 查询套餐</t-button><t-button theme="primary" @click="openSimPkgAdd">新建通信卡套餐</t-button></div></div>
<div class="asset-table-wrap"><t-table row-key="id" :data="simPkgList" :columns="simPkgColumns" empty="暂无通信卡套餐"><template #status="{ row }"><span class="table-status" :class="row.status === 'active' ? 'online' : 'offline'"><i></i>{{ row.status === 'active' ? '上架' : '下架' }}</span></template><template #actions="{ row }"><t-button variant="text" theme="primary" @click="openSimPkgEdit(row)">编辑</t-button><t-button v-if="row.status === 'active'" variant="text" theme="danger" @click="disableSimPkg(row)">下架</t-button></template></t-table></div>
<div class="table-footer"><t-pagination v-model:current="simPkgPage" :total="simPkgTotal" :page-size="10" @current-change="loadSimPackages" /></div>
</div>
<div v-show="tab === 'purchases'" class="purchases-panel"> <div v-show="tab === 'purchases'" class="purchases-panel">
<div v-if="poolMissing" class="pool-empty"> <div v-if="poolMissing" class="pool-empty">
尚未建立资源池首次录入采购后自动创建 尚未建立资源池首次录入采购后自动创建
@ -195,6 +203,8 @@
</div> </div>
</div> </div>
<t-dialog v-model:visible="simPkgModal" :header="simPkgEditing ? '编辑通信卡套餐' : '新建通信卡套餐'" width="520px" destroy-on-close dialog-class-name="add-dock-dialog"><form novalidate @submit.prevent="submitSimPkg"><label><span>套餐编码<b>*</b></span><t-input v-model="simPkgForm.code" :disabled="simPkgEditing" /></label><label><span>套餐名称<b>*</b></span><t-input v-model="simPkgForm.name" /></label><div class="form-row"><label><span>运营商</span><t-input v-model="simPkgForm.carrier" disabled /></label><label><span>供应商套餐ID</span><t-input-number v-model="simPkgForm.providerRatePlanId" :min="1" style="width:100%" disabled /></label></div><div class="form-row"><label><span>流量(GB)<b>*</b></span><t-input-number v-model="simPkgForm.amountGb" :min="1" style="width:100%" /></label><label><span>有效期(月)<b>*</b></span><t-input-number v-model="simPkgForm.validityMonths" :min="1" style="width:100%" /></label></div><div class="form-row"><label><span>销售价(元)<b>*</b></span><t-input-number v-model="simPkgForm.saleFeeFen" :min="0.01" :step="0.01" :decimal-places="2" style="width:100%" /></label><label><span>成本价(元)</span><t-input-number v-model="simPkgForm.costFeeFen" :min="0" :step="0.01" :decimal-places="2" style="width:100%" /></label></div><label v-if="simPkgEditing"><span>状态</span><t-select v-model="simPkgForm.status" :options="pkgStatusOptions" /></label><div class="form-error">{{ simPkgError }}</div></form><template #footer><t-button variant="outline" @click="simPkgModal=false">取消</t-button><t-button theme="primary" @click="submitSimPkg">保存</t-button></template></t-dialog>
<t-dialog <t-dialog
v-model:visible="pkgModalVisible" v-model:visible="pkgModalVisible"
:header="pkgEditingId ? '编辑套餐' : '新建套餐'" :header="pkgEditingId ? '编辑套餐' : '新建套餐'"
@ -274,6 +284,8 @@
</template> </template>
</t-dialog> </t-dialog>
<t-dialog v-model:visible="providerPlansModal" header="SIMBOSS 供应商套餐" width="760px" destroy-on-close><div class="asset-toolbar"><t-input v-model="providerIccid" placeholder="输入通信卡 ICCID" /><t-button theme="primary" :loading="providerPlansLoading" @click="queryProviderPlans">查询</t-button></div><t-table row-key="ratePlanId" :data="providerPlans" :columns="providerPlanColumns" empty="暂无供应商套餐"><template #actions="{ row }"><t-button variant="text" theme="primary" @click="useProviderPlan(row)">使用此套餐</t-button></template></t-table></t-dialog>
<t-dialog <t-dialog
v-model:visible="purchaseModalVisible" v-model:visible="purchaseModalVisible"
header="录入采购" header="录入采购"
@ -598,6 +610,39 @@ async function disablePackage(row) {
} }
} }
const providerPlansModal = ref(false)
const providerIccid = ref('')
const providerPlans = ref([])
const providerPlansLoading = ref(false)
const providerPlanColumns = [{ colKey: 'ratePlanId', title: '供应商套餐ID' }, { colKey: 'name', title: '名称' }, { colKey: 'dataVolume', title: '流量' }, { colKey: 'timeLength', title: '时长' }, { colKey: 'actions', title: '操作' }]
async function queryProviderPlans() { if (!providerIccid.value.trim()) { ui.toast('请输入 ICCID'); return }; providerPlansLoading.value = true; try { providerPlans.value = await request.post(urls.ADMIN_SIM_PROVIDER_PLANS, { iccid: providerIccid.value.trim() }) } catch (e) { ui.toast(e.message || '查询供应商套餐失败') } finally { providerPlansLoading.value = false } }
function openProviderPlans() { providerIccid.value = ''; providerPlans.value = []; providerPlansModal.value = true }
function useProviderPlan(row) { Object.assign(simPkgForm, { providerRatePlanId: row.ratePlanId, name: row.name || simPkgForm.name, amountGb: Math.max(1, Math.round(Number(row.dataVolume) || 1)), validityMonths: Math.max(1, Math.round(Number(row.timeLength) / 30) || 1) }); simPkgModal.value = true; providerPlansModal.value = false; simPkgEditing.value = null; simPkgError.value = '' }
const simPkgList = ref([])
const simPkgTotal = ref(0)
const simPkgPage = ref(1)
const simPkgStatus = ref('')
const simPkgModal = ref(false)
const simPkgEditing = ref(null)
const simPkgError = ref('')
const simPkgForm = reactive({ code: '', name: '', carrier: '', providerRatePlanId: 0, amountGb: 1, validityMonths: 1, costFeeFen: 0, saleFeeFen: 0.01, status: 'active' })
const simPkgColumns = [
{ colKey: 'code', title: '编码' }, { colKey: 'name', title: '名称' }, { colKey: 'carrier', title: '运营商' },
{ colKey: 'providerRatePlanId', title: '供应商套餐ID' }, { colKey: 'amountGb', title: '流量(GB)' }, { colKey: 'validityMonths', title: '有效期(月)' },
{ colKey: 'saleFeeText', title: '售价' }, { colKey: 'status', title: '状态' }, { colKey: 'actions', title: '操作' }
]
async function loadSimPackages() {
try { const page = await request.get(urls.ADMIN_SIM_PACKAGES, { params: { pageNum: simPkgPage.value, pageSize: 10, status: simPkgStatus.value || undefined } }); simPkgTotal.value = page?.total || 0; simPkgList.value = (page?.records || []).map((p) => ({ ...p, id: String(p.id), saleFeeText: `¥${(Number(p.saleFeeFen || 0) / 100).toFixed(2)}` })) } catch (e) { ui.toast(e.message || '加载通信卡套餐失败') }
}
function reloadSimPackages() { simPkgPage.value = 1; loadSimPackages() }
function openSimPkgAdd() { Object.assign(simPkgForm, { code: '', name: '', carrier: '', providerRatePlanId: 0, amountGb: 1, validityMonths: 1, costFeeFen: 0, saleFeeFen: 0.01, status: 'active' }); simPkgEditing.value = null; simPkgError.value = ''; simPkgModal.value = true }
function openSimPkgEdit(row) { Object.assign(simPkgForm, { ...row, costFeeFen: Number(row.costFeeFen || 0) / 100, saleFeeFen: Number(row.saleFeeFen || 0) / 100 }); simPkgEditing.value = row.id; simPkgError.value = ''; simPkgModal.value = true }
async function submitSimPkg() { simPkgError.value = ''; if (!simPkgForm.name || !simPkgForm.carrier || !simPkgForm.providerRatePlanId || !simPkgForm.amountGb || !simPkgForm.validityMonths || !(Number(simPkgForm.saleFeeFen) > 0) || (!simPkgEditing.value && !simPkgForm.code)) { simPkgError.value = '请完整填写套餐信息'; return }; try { const payload = { ...simPkgForm, providerRatePlanId: Number(simPkgForm.providerRatePlanId), amountGb: Number(simPkgForm.amountGb), validityMonths: Number(simPkgForm.validityMonths), costFeeFen: Math.round(Number(simPkgForm.costFeeFen || 0) * 100), saleFeeFen: Math.round(Number(simPkgForm.saleFeeFen) * 100) }; if (simPkgEditing.value) await request.put(urls.ADMIN_SIM_PACKAGE(simPkgEditing.value), payload); else await request.post(urls.ADMIN_SIM_PACKAGES, payload); simPkgModal.value = false; ui.toast('通信卡套餐已保存'); await loadSimPackages() } catch (e) { simPkgError.value = e.message || '保存失败' } }
async function disableSimPkg(row) { const ok = await ui.confirm(`确认下架套餐 ${row.name}`, '下架后用户侧不可购买。'); if (!ok) return; try { await request.delete(urls.ADMIN_SIM_PACKAGE(row.id)); await loadSimPackages(); ui.toast('套餐已下架') } catch (e) { ui.toast(e.message || '下架失败') } }
const pool = ref(null) const pool = ref(null)
const poolMissing = ref(false) const poolMissing = ref(false)
const purchaseList = ref([]) const purchaseList = ref([])
@ -924,6 +969,7 @@ function reloadLedger() {
watch(tab, (v) => { watch(tab, (v) => {
if (v === 'packages') loadPackages() if (v === 'packages') loadPackages()
if (v === 'sim-packages') loadSimPackages()
if (v === 'purchases') loadPurchasesTab() if (v === 'purchases') loadPurchasesTab()
if (v === 'ledger') loadLedger() if (v === 'ledger') loadLedger()
}) })

Loading…
Cancel
Save