This commit is contained in:
张成
2026-03-25 19:01:28 +08:00
parent 5b654824b4
commit 7d0a921805
27 changed files with 560 additions and 245 deletions

View File

@@ -1,4 +1,5 @@
// 组件映射表:后端菜单返回的 component 路径需与此处 key 一致(不含 .vue
import HomeIndex from '../views/home/index.vue'
import TestPage from '../views/test/test.vue'
import SubscriptionDashboard from '../views/subscription/dashboard.vue'
import SubscriptionUsers from '../views/subscription/users.vue'
@@ -10,6 +11,9 @@ import SubscriptionUsage from '../views/subscription/usage.vue'
import SubscriptionAuditLog from '../views/subscription/audit_log.vue'
const componentMap = {
// 与 sys_menu.component 一致:库中常见为 home/index 或 home/index.vue
'home/index': HomeIndex,
'home/index.vue': HomeIndex,
'test/test': TestPage,
'subscription/dashboard': SubscriptionDashboard,
'subscription/user': SubscriptionUsers,

View File

@@ -0,0 +1,53 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">首页</h2>
</div>
</div>
<div class="table-body home-body">
<Card dis-hover>
<p class="home-tip">欢迎使用管理后台</p>
<p class="home-muted">菜单由 <code>sys_menu</code> 配置<code>path</code> 与路由一致<code>component</code> <code>component-map.js</code> key 一致 <code>home/index</code></p>
</Card>
</div>
</div>
</template>
<script>
export default {
name: 'HomeIndex',
}
</script>
<style scoped>
.content-view {
padding: 16px;
}
.table-head-tool {
margin-bottom: 12px;
}
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.home-body {
max-width: 720px;
}
.home-tip {
margin: 0 0 8px;
font-size: 15px;
}
.home-muted {
margin: 0;
color: #808695;
font-size: 13px;
line-height: 1.6;
}
</style>

View File

@@ -1,32 +1,38 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">审计日志</h2>
<Button type="primary" @click="load(1)">刷新</Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">审计日志</h2>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="动作">
<Select v-model="param.seachOption.key" style="width: 140px">
<Option value="action">action</Option>
<Option value="resource_type">resource_type</Option>
</Select>
<Input v-model="param.seachOption.value" class="ml8" style="width: 220px" placeholder="模糊/精确" />
<Input v-model="param.seachOption.value" class="ml10" style="width: 220px" placeholder="模糊/精确" />
</FormItem>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
<Button type="primary" @click="load(1)">查询</Button>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
</div>
</template>
@@ -105,26 +111,43 @@ export default {
this.$Message.error((res && res.message) || '导出失败')
}
},
resetQuery() {
this.param.seachOption = { key: 'action', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
font-size: 18px;
}
.ml8 {
margin-left: 8px;
}
.sub-search {
.table-head-tool {
margin-bottom: 12px;
}
.sub-page-bar {
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.ml10 {
margin-left: 10px;
}
.table-body {
}
.table-page-bar {
margin-top: 12px;
text-align: right;
}

View File

@@ -1,13 +1,17 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">订阅运营看板</h2>
<Button type="primary" @click="load">刷新</Button>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">订阅运营看板</h2>
<Button type="primary" @click="load">刷新</Button>
</div>
</div>
<Row :gutter="16" v-if="stats">
<Col span="6">
<Card dis-hover><p class="lbl">业务用户总数</p><p class="num">{{ stats.users.total }}</p></Card>
</Col>
<div class="table-body">
<Row :gutter="16" v-if="stats">
<Col span="6">
<Card dis-hover><p class="lbl">业务用户总数</p><p class="num">{{ stats.users.total }}</p></Card>
</Col>
<Col span="6">
<Card dis-hover><p class="lbl">正常用户</p><p class="num">{{ stats.users.active }}</p></Card>
</Col>
@@ -31,9 +35,10 @@
><p class="lbl">7 天内到期活跃</p><p class="num warn">{{ stats.subscriptions.renew_within_7d }}</p></Card
>
</Col>
</Row>
<p v-else class="muted">加载中</p>
<p class="muted small">数据时间{{ stats && stats.server_time }}</p>
</Row>
<p v-else class="muted">加载中</p>
<p class="muted small">数据时间{{ stats && stats.server_time }}</p>
</div>
</div>
</template>
@@ -64,12 +69,23 @@ export default {
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
.table-head-tool {
margin-bottom: 12px;
}
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.lbl {

View File

@@ -1,5 +1,5 @@
<template>
<div class="sub-page">
<div class="content-view">
<h2 class="sub-title">支付确认轻量</h2>
<p class="sub-desc"> <code>pending</code> 订阅置为 <code>active</code>并写入支付单号</p>
<Card dis-hover title="线下确认" style="max-width: 520px; margin-bottom: 16px">
@@ -86,7 +86,7 @@ export default {
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-title {

View File

@@ -1,34 +1,46 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">套餐</h2>
<Button type="primary" @click="openEdit(null)">新增套餐</Button>
<Button class="ml8" @click="load(1)"></Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline :label-width="70">
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">套餐</h2>
<Button type="primary" @click="openEdit(null)">增套餐</Button>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="条件">
<Select v-model="param.seachOption.key" style="width: 140px">
<Option value="plan_code">编码</Option>
<Option value="plan_name">名称</Option>
<Option value="status">状态</Option>
</Select>
<Input v-model="param.seachOption.value" class="ml8" style="width: 220px" search @on-search="load(1)" />
<Input
v-model="param.seachOption.value"
class="ml10"
style="width: 220px"
search
@on-search="load(1)"
/>
</FormItem>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
<Button type="primary" @click="load(1)">查询</Button>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
<Modal v-model="modal" :title="form.id ? '编辑套餐' : '新增套餐'" width="720" :loading="saving" @on-ok="save">
@@ -239,30 +251,47 @@ export default {
this.$Message.error((res && res.message) || '导出失败')
}
},
resetQuery() {
this.param.seachOption = { key: 'plan_code', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-toolbar {
.table-head-tool {
margin-bottom: 12px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
font-size: 18px;
vertical-align: middle;
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.ml10 {
margin-left: 10px;
}
.ml8 {
margin-left: 8px;
}
.sub-search {
margin-bottom: 12px;
.table-body {
}
.sub-page-bar {
.table-page-bar {
margin-top: 12px;
text-align: right;
}

View File

@@ -1,15 +1,14 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">订阅</h2>
<Button type="primary" @click="openOpen">开通订阅</Button>
<Button class="ml8" @click="load(1)">刷新</Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">订阅</h2>
<Button type="primary" @click="openOpen">开通订阅</Button>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="用户ID">
<Input v-model="param.seachOption.value" style="width: 140px" placeholder="筛选 user_id" />
<Input v-model="param.seachOption.value" style="width: 140px" placeholder="筛选 user_id" class="ml10" />
</FormItem>
<FormItem>
<Select v-model="param.seachOption.key" style="width: 120px">
@@ -17,19 +16,26 @@
<Option value="status">状态</Option>
</Select>
</FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
<Modal v-model="openModal" title="开通订阅" width="640" :loading="saving" @on-ok="submitOpen">
@@ -264,27 +270,44 @@ export default {
this.$Message.error((res && res.message) || '导出失败')
}
},
resetQuery() {
this.param.seachOption = { key: 'user_id', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-toolbar {
.table-head-tool {
margin-bottom: 12px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
font-size: 18px;
vertical-align: middle;
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.ml10 {
margin-left: 10px;
}
.ml8 {
margin-left: 8px;
}
.sub-page-bar {
.table-page-bar {
margin-top: 12px;
text-align: right;
}

View File

@@ -1,15 +1,14 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">API Token</h2>
<Button type="primary" @click="openCreate">创建 Token</Button>
<Button class="ml8" @click="load(1)">刷新</Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">API Token</h2>
<Button type="primary" @click="openCreate">创建 Token</Button>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="用户ID">
<Input v-model="param.seachOption.value" style="width: 140px" />
<Input v-model="param.seachOption.value" style="width: 140px" class="ml10" />
</FormItem>
<FormItem>
<Select v-model="param.seachOption.key" style="width: 120px">
@@ -17,19 +16,26 @@
<Option value="status">状态</Option>
</Select>
</FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
<Modal v-model="createModal" title="创建 Token" width="560" :loading="saving" @on-ok="submitCreate">
@@ -177,27 +183,43 @@ export default {
this.$Message.error((res && res.message) || '导出失败')
}
},
resetQuery() {
this.param.seachOption = { key: 'user_id', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-toolbar {
.table-head-tool {
margin-bottom: 12px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
vertical-align: middle;
}
.ml8 {
margin-left: 8px;
.ml10 {
margin-left: 10px;
}
.sub-page-bar {
.table-body {
}
.table-page-bar {
margin-top: 12px;
text-align: right;
}

View File

@@ -1,34 +1,40 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">月用量</h2>
<Button type="primary" @click="openEdit(null)">新增</Button>
<Button class="ml8" @click="load(1)"></Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline>
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">月用量</h2>
<Button type="primary" @click="openEdit(null)"></Button>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="条件">
<Select v-model="param.seachOption.key" style="width: 140px">
<Option value="user_id">用户ID</Option>
<Option value="stat_month">月份</Option>
<Option value="plan_id">套餐ID</Option>
</Select>
<Input v-model="param.seachOption.value" class="ml8" style="width: 200px" />
<Input v-model="param.seachOption.value" class="ml10" style="width: 200px" />
</FormItem>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
<Button type="primary" @click="load(1)">查询</Button>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
<Modal v-model="modal" :title="form.id ? '编辑用量' : '新增用量'" width="640" :loading="saving" @on-ok="save">
@@ -182,26 +188,47 @@ export default {
this.$Message.error((res && res.message) || '导出失败')
}
},
resetQuery() {
this.param.seachOption = { key: 'stat_month', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
.table-head-tool {
margin-bottom: 12px;
}
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.ml10 {
margin-left: 10px;
}
.ml8 {
margin-left: 8px;
}
.sub-search {
margin-bottom: 12px;
.table-body {
}
.sub-page-bar {
.table-page-bar {
margin-top: 12px;
text-align: right;
}

View File

@@ -1,36 +1,47 @@
<template>
<div class="sub-page">
<div class="sub-toolbar">
<h2 class="sub-title">业务用户</h2>
<Button type="primary" @click="openEdit(null)">新增</Button>
<Button class="ml8" @click="load(1)"></Button>
<Button class="ml8" @click="doExport">导出 CSV</Button>
</div>
<div class="sub-search">
<Form inline :label-width="70">
<div class="content-view">
<div class="table-head-tool">
<div class="table-title-row">
<h2 class="table-title">业务用户</h2>
<Button type="primary" @click="openEdit(null)"></Button>
</div>
<Form ref="formInline" :model="param.seachOption" inline :label-width="80">
<FormItem label="条件">
<Select v-model="param.seachOption.key" style="width: 140px">
<Option value="mobile">手机</Option>
<Option value="company_name">公司</Option>
<Option value="status">状态</Option>
</Select>
<Input v-model="param.seachOption.value" placeholder="关键字" style="width: 220px" class="ml8" search @on-search="load(1)" />
<Input
v-model="param.seachOption.value"
placeholder="关键字"
style="width: 220px"
class="ml10"
search
@on-search="load(1)"
/>
</FormItem>
<FormItem>
<Button type="primary" @click="load(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
<Button type="default" @click="doExport" class="ml10">导出 CSV</Button>
</FormItem>
</Form>
</div>
<Table :columns="columns" :data="rows" border stripe />
<div class="sub-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
<div class="table-body">
<Table :columns="columns" :data="rows" border stripe />
<div class="table-page-bar">
<Page
:total="total"
:current="param.pageOption.page"
:page-size="param.pageOption.pageSize"
show-total
@on-change="onPage"
@on-page-size-change="onSize"
/>
</div>
</div>
<Modal v-model="modal" :title="form.id ? '编辑用户' : '新增用户'" width="640" :loading="saving" @on-ok="save">
@@ -276,30 +287,47 @@ export default {
},
})
},
resetQuery() {
this.param.seachOption = { key: 'mobile', value: '' }
this.load(1)
},
},
}
</script>
<style scoped>
.sub-page {
.content-view {
padding: 16px;
}
.sub-toolbar {
.table-head-tool {
margin-bottom: 12px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
font-size: 18px;
vertical-align: middle;
.table-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
margin: 0;
font-size: 18px;
}
.ml10 {
margin-left: 10px;
}
.ml8 {
margin-left: 8px;
}
.sub-search {
margin-bottom: 12px;
.table-body {
}
.sub-page-bar {
.table-page-bar {
margin-top: 12px;
text-align: right;
}