1
This commit is contained in:
11
admin/src/api/subscription/audit_server.js
Normal file
11
admin/src/api/subscription/audit_server.js
Normal file
@@ -0,0 +1,11 @@
|
||||
class AuditServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_audit_log/page", row);
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_audit_log/export", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AuditServer();
|
||||
7
admin/src/api/subscription/dashboard_server.js
Normal file
7
admin/src/api/subscription/dashboard_server.js
Normal file
@@ -0,0 +1,7 @@
|
||||
class DashboardServer {
|
||||
async summary() {
|
||||
return window.framework.http.get("/biz_dashboard/summary", {});
|
||||
}
|
||||
}
|
||||
|
||||
export default new DashboardServer();
|
||||
@@ -1,4 +1,4 @@
|
||||
class BizPaymentServer {
|
||||
class PaymentServer {
|
||||
async confirmOffline(row) {
|
||||
return window.framework.http.post("/biz_payment/confirm-offline", row);
|
||||
}
|
||||
@@ -8,4 +8,4 @@ class BizPaymentServer {
|
||||
}
|
||||
}
|
||||
|
||||
export default new BizPaymentServer();
|
||||
export default new PaymentServer();
|
||||
@@ -1,4 +1,4 @@
|
||||
class BizPlanServer {
|
||||
class PlanServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_plan/page", row);
|
||||
}
|
||||
@@ -22,6 +22,10 @@ class BizPlanServer {
|
||||
async all() {
|
||||
return window.framework.http.get("/biz_plan/all", {});
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_plan/export", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new BizPlanServer();
|
||||
export default new PlanServer();
|
||||
@@ -1,4 +1,4 @@
|
||||
class BizSubscriptionServer {
|
||||
class SubscriptionsServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_subscription/page", row);
|
||||
}
|
||||
@@ -22,6 +22,10 @@ class BizSubscriptionServer {
|
||||
async cancel(row) {
|
||||
return window.framework.http.post("/biz_subscription/cancel", row);
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_subscription/export", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new BizSubscriptionServer();
|
||||
export default new SubscriptionsServer();
|
||||
@@ -1,4 +1,4 @@
|
||||
class BizTokenServer {
|
||||
class TokenServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_token/page", row);
|
||||
}
|
||||
@@ -10,6 +10,10 @@ class BizTokenServer {
|
||||
async revoke(row) {
|
||||
return window.framework.http.post("/biz_token/revoke", row);
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_token/export", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new BizTokenServer();
|
||||
export default new TokenServer();
|
||||
23
admin/src/api/subscription/usage_server.js
Normal file
23
admin/src/api/subscription/usage_server.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class UsageServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_usage/page", row);
|
||||
}
|
||||
|
||||
async add(row) {
|
||||
return window.framework.http.post("/biz_usage/add", row);
|
||||
}
|
||||
|
||||
async edit(row) {
|
||||
return window.framework.http.post("/biz_usage/edit", row);
|
||||
}
|
||||
|
||||
async del(row) {
|
||||
return window.framework.http.post("/biz_usage/del", row);
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_usage/export", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new UsageServer();
|
||||
@@ -1,4 +1,4 @@
|
||||
class BizUserServer {
|
||||
class UserServer {
|
||||
async page(row) {
|
||||
return window.framework.http.post("/biz_user/page", row);
|
||||
}
|
||||
@@ -22,6 +22,14 @@ class BizUserServer {
|
||||
async disable(row) {
|
||||
return window.framework.http.post("/biz_user/disable", row);
|
||||
}
|
||||
|
||||
async exportRows(row) {
|
||||
return window.framework.http.post("/biz_user/export", row);
|
||||
}
|
||||
|
||||
async revokeAllTokens(row) {
|
||||
return window.framework.http.post("/biz_user/revoke_all_tokens", row);
|
||||
}
|
||||
}
|
||||
|
||||
export default new BizUserServer();
|
||||
export default new UserServer();
|
||||
@@ -1,18 +1,24 @@
|
||||
// 组件映射表:后端菜单返回的 component 路径需与此处 key 一致(不含 .vue)
|
||||
import TestPage from '../views/test/test.vue'
|
||||
import BizUsers from '../views/biz/biz_users.vue'
|
||||
import BizPlans from '../views/biz/biz_plans.vue'
|
||||
import BizSubscriptions from '../views/biz/biz_subscriptions.vue'
|
||||
import BizTokens from '../views/biz/biz_tokens.vue'
|
||||
import BizPayment from '../views/biz/biz_payment.vue'
|
||||
import SubscriptionDashboard from '../views/subscription/dashboard.vue'
|
||||
import SubscriptionUsers from '../views/subscription/users.vue'
|
||||
import SubscriptionPlans from '../views/subscription/plans.vue'
|
||||
import SubscriptionRecords from '../views/subscription/subscriptions.vue'
|
||||
import SubscriptionTokens from '../views/subscription/tokens.vue'
|
||||
import SubscriptionPayment from '../views/subscription/payment.vue'
|
||||
import SubscriptionUsage from '../views/subscription/usage.vue'
|
||||
import SubscriptionAuditLog from '../views/subscription/audit_log.vue'
|
||||
|
||||
const componentMap = {
|
||||
'test/test': TestPage,
|
||||
'biz/user': BizUsers,
|
||||
'biz/plan': BizPlans,
|
||||
'biz/subscription': BizSubscriptions,
|
||||
'biz/token': BizTokens,
|
||||
'biz/payment': BizPayment,
|
||||
'subscription/dashboard': SubscriptionDashboard,
|
||||
'subscription/user': SubscriptionUsers,
|
||||
'subscription/plan': SubscriptionPlans,
|
||||
'subscription/subscription': SubscriptionRecords,
|
||||
'subscription/token': SubscriptionTokens,
|
||||
'subscription/payment': SubscriptionPayment,
|
||||
'subscription/usage': SubscriptionUsage,
|
||||
'subscription/audit': SubscriptionAuditLog,
|
||||
}
|
||||
|
||||
export default componentMap;
|
||||
|
||||
24
admin/src/utils/csvExport.js
Normal file
24
admin/src/utils/csvExport.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/** 将对象数组导出为 CSV 并触发浏览器下载(兼容 POST /export 返回的 rows) */
|
||||
export function downloadCsvFromRows(rows, filename = "export.csv") {
|
||||
if (!rows || !rows.length) {
|
||||
return false;
|
||||
}
|
||||
const keys = Object.keys(rows[0]);
|
||||
const esc = (v) => {
|
||||
const s = v === null || v === undefined ? "" : String(v);
|
||||
if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
|
||||
return s;
|
||||
};
|
||||
const lines = [keys.join(",")];
|
||||
for (const r of rows) {
|
||||
lines.push(keys.map((k) => esc(r[k])).join(","));
|
||||
}
|
||||
const blob = new Blob(["\ufeff" + lines.join("\n")], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
return true;
|
||||
}
|
||||
131
admin/src/views/subscription/audit_log.vue
Normal file
131
admin/src/views/subscription/audit_log.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<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>
|
||||
<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="模糊/精确" />
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import auditServer from '@/api/subscription/audit_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'SubscriptionAuditLog',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'action', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 80 },
|
||||
{ title: '后台用户', key: 'admin_user_id', width: 100 },
|
||||
{ title: '业务用户', key: 'biz_user_id', width: 100 },
|
||||
{ title: '动作', key: 'action', minWidth: 160 },
|
||||
{ title: '资源类型', key: 'resource_type', width: 120 },
|
||||
{ title: '资源ID', key: 'resource_id', width: 90 },
|
||||
{
|
||||
title: '详情',
|
||||
key: 'detail',
|
||||
minWidth: 200,
|
||||
render: (h, p) => {
|
||||
const d = p.row.detail
|
||||
const s = d == null ? '' : typeof d === 'string' ? d : JSON.stringify(d)
|
||||
return h('span', { attrs: { title: s } }, s.length > 80 ? s.slice(0, 80) + '…' : s)
|
||||
},
|
||||
},
|
||||
{ title: '时间', key: 'created_at', minWidth: 160 },
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load(1)
|
||||
},
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await auditServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '加载失败')
|
||||
}
|
||||
},
|
||||
onPage(p) {
|
||||
this.param.pageOption.page = p
|
||||
this.load()
|
||||
},
|
||||
onSize(s) {
|
||||
this.param.pageOption.pageSize = s
|
||||
this.load(1)
|
||||
},
|
||||
async doExport() {
|
||||
const res = await auditServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
const rows = res.data.rows.map((r) => ({
|
||||
...r,
|
||||
detail: r.detail == null ? '' : typeof r.detail === 'object' ? JSON.stringify(r.detail) : r.detail,
|
||||
}))
|
||||
downloadCsvFromRows(rows, 'audit_log.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.sub-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
95
admin/src/views/subscription/dashboard.vue
Normal file
95
admin/src/views/subscription/dashboard.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="sub-page">
|
||||
<div class="sub-toolbar">
|
||||
<h2 class="sub-title">订阅运营看板</h2>
|
||||
<Button type="primary" @click="load">刷新</Button>
|
||||
</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>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">正常用户</p><p class="num">{{ stats.users.active }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">上线套餐数</p><p class="num">{{ stats.plans.active }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">有效 Token</p><p class="num">{{ stats.tokens.active }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">待支付订阅</p><p class="num">{{ stats.subscriptions.pending }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">生效中订阅</p><p class="num">{{ stats.subscriptions.active }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover><p class="lbl">已过期订阅</p><p class="num">{{ stats.subscriptions.expired }}</p></Card>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<Card dis-hover
|
||||
><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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dashboardServer from '@/api/subscription/dashboard_server.js'
|
||||
|
||||
export default {
|
||||
name: 'SubscriptionDashboard',
|
||||
data() {
|
||||
return {
|
||||
stats: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
async load() {
|
||||
const res = await dashboardServer.summary()
|
||||
if (res && res.code === 0) {
|
||||
this.stats = res.data
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '加载失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.lbl {
|
||||
margin: 0;
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
}
|
||||
.num {
|
||||
margin: 8px 0 0;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.num.warn {
|
||||
color: #ed4014;
|
||||
}
|
||||
.muted {
|
||||
color: #888;
|
||||
}
|
||||
.small {
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<h2 class="biz-title">支付确认(轻量)</h2>
|
||||
<p class="biz-desc">将 <code>pending</code> 订阅置为 <code>active</code>,并写入支付单号。</p>
|
||||
<div class="sub-page">
|
||||
<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">
|
||||
<Form :label-width="110">
|
||||
<FormItem label="订阅ID">
|
||||
<Input v-model="offline.subscription_id" type="number" placeholder="biz_subscriptions.id" />
|
||||
<Input v-model="offline.subscription_id" type="number" placeholder="订阅记录 id" />
|
||||
</FormItem>
|
||||
<FormItem label="支付单号">
|
||||
<Input v-model="offline.payment_ref" placeholder="流水号/凭证号" />
|
||||
@@ -15,6 +15,10 @@
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
<Alert type="info" show-icon style="max-width: 720px; margin-bottom: 16px">
|
||||
对外鉴权接口(免登录,见白名单):<code>POST /api/auth/verify</code>,body 示例:
|
||||
<code>{"token":"明文","feature":"msg","usage_delta":{"msg":1}}</code>
|
||||
</Alert>
|
||||
<Card dis-hover title="链接支付确认" style="max-width: 520px">
|
||||
<Form :label-width="110">
|
||||
<FormItem label="订阅ID">
|
||||
@@ -32,10 +36,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizPaymentServer from '@/api/biz/biz_payment_server.js'
|
||||
import paymentServer from '@/api/subscription/payment_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizPayment',
|
||||
name: 'SubscriptionPayment',
|
||||
data() {
|
||||
return {
|
||||
offline: { subscription_id: '', payment_ref: '' },
|
||||
@@ -48,7 +52,7 @@ export default {
|
||||
async doOffline() {
|
||||
this.loading1 = true
|
||||
try {
|
||||
const res = await bizPaymentServer.confirmOffline({
|
||||
const res = await paymentServer.confirmOffline({
|
||||
subscription_id: Number(this.offline.subscription_id),
|
||||
payment_ref: this.offline.payment_ref,
|
||||
})
|
||||
@@ -64,7 +68,7 @@ export default {
|
||||
async doLink() {
|
||||
this.loading2 = true
|
||||
try {
|
||||
const res = await bizPaymentServer.confirmLink({
|
||||
const res = await paymentServer.confirmLink({
|
||||
subscription_id: Number(this.link.subscription_id),
|
||||
payment_ref: this.link.payment_ref,
|
||||
})
|
||||
@@ -82,14 +86,14 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-title {
|
||||
.sub-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.biz-desc {
|
||||
.sub-desc {
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">套餐</h2>
|
||||
<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="biz-search">
|
||||
<div class="sub-search">
|
||||
<Form inline :label-width="70">
|
||||
<FormItem label="条件">
|
||||
<Select v-model="param.seachOption.key" style="width: 140px">
|
||||
@@ -19,7 +20,7 @@
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-page-bar">
|
||||
<div class="sub-page-bar">
|
||||
<Page
|
||||
:total="total"
|
||||
:current="param.pageOption.page"
|
||||
@@ -77,10 +78,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizPlanServer from '@/api/biz/biz_plan_server.js'
|
||||
import planServer from '@/api/subscription/plan_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'BizPlans',
|
||||
name: 'SubscriptionPlans',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
@@ -128,7 +130,7 @@ export default {
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizPlanServer.page({ param: this.param })
|
||||
const res = await planServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
@@ -191,7 +193,7 @@ export default {
|
||||
}
|
||||
const payload = { ...this.form, enabled_features }
|
||||
try {
|
||||
const res = this.form.id ? await bizPlanServer.edit(payload) : await bizPlanServer.add(payload)
|
||||
const res = this.form.id ? await planServer.edit(payload) : await planServer.add(payload)
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('保存成功')
|
||||
this.modal = false
|
||||
@@ -205,7 +207,7 @@ export default {
|
||||
})
|
||||
},
|
||||
async toggle(row) {
|
||||
const res = await bizPlanServer.toggle({ id: row.id })
|
||||
const res = await planServer.toggle({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('状态已更新为 ' + (res.data && res.data.status))
|
||||
this.load()
|
||||
@@ -218,7 +220,7 @@ export default {
|
||||
title: '删除套餐',
|
||||
content: '确认删除?若已被订阅引用可能失败。',
|
||||
onOk: async () => {
|
||||
const res = await bizPlanServer.del({ id: row.id })
|
||||
const res = await planServer.del({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已删除')
|
||||
this.load(1)
|
||||
@@ -228,18 +230,27 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
async doExport() {
|
||||
const res = await planServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
downloadCsvFromRows(res.data.rows, 'plans.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
.sub-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
@@ -248,10 +259,10 @@ export default {
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-search {
|
||||
.sub-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">订阅</h2>
|
||||
<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="biz-search">
|
||||
<div class="sub-search">
|
||||
<Form inline>
|
||||
<FormItem label="用户ID">
|
||||
<Input v-model="param.seachOption.value" style="width: 140px" placeholder="筛选 user_id" />
|
||||
@@ -20,7 +21,7 @@
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-page-bar">
|
||||
<div class="sub-page-bar">
|
||||
<Page
|
||||
:total="total"
|
||||
:current="param.pageOption.page"
|
||||
@@ -76,10 +77,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizSubscriptionServer from '@/api/biz/biz_subscription_server.js'
|
||||
import subscriptionsServer from '@/api/subscription/subscriptions_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'BizSubscriptions',
|
||||
name: 'SubscriptionRecords',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
@@ -127,7 +129,7 @@ export default {
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizSubscriptionServer.page({ param: this.param })
|
||||
const res = await subscriptionsServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
@@ -176,7 +178,7 @@ export default {
|
||||
payment_channel: this.openForm.payment_channel || null,
|
||||
payment_ref: this.openForm.payment_ref || null,
|
||||
}
|
||||
const res = await bizSubscriptionServer.open(body)
|
||||
const res = await subscriptionsServer.open(body)
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已创建订阅')
|
||||
this.openModal = false
|
||||
@@ -197,7 +199,7 @@ export default {
|
||||
if (!this.currentRow) return
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizSubscriptionServer.renew({
|
||||
const res = await subscriptionsServer.renew({
|
||||
subscription_id: this.currentRow.id,
|
||||
end_time: this.renewForm.end_time,
|
||||
})
|
||||
@@ -221,7 +223,7 @@ export default {
|
||||
if (!this.currentRow) return
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizSubscriptionServer.upgrade({
|
||||
const res = await subscriptionsServer.upgrade({
|
||||
subscription_id: this.currentRow.id,
|
||||
new_plan_id: Number(this.upgradeForm.new_plan_id),
|
||||
start_time: this.upgradeForm.start_time || undefined,
|
||||
@@ -243,7 +245,7 @@ export default {
|
||||
title: '取消订阅',
|
||||
content: '确认取消?',
|
||||
onOk: async () => {
|
||||
const res = await bizSubscriptionServer.cancel({ subscription_id: row.id })
|
||||
const res = await subscriptionsServer.cancel({ subscription_id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已取消')
|
||||
this.load(1)
|
||||
@@ -253,18 +255,27 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
async doExport() {
|
||||
const res = await subscriptionsServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
downloadCsvFromRows(res.data.rows, 'subscriptions.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
.sub-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
@@ -273,7 +284,7 @@ export default {
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">API Token</h2>
|
||||
<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="biz-search">
|
||||
<div class="sub-search">
|
||||
<Form inline>
|
||||
<FormItem label="用户ID">
|
||||
<Input v-model="param.seachOption.value" style="width: 140px" />
|
||||
@@ -20,7 +21,7 @@
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-page-bar">
|
||||
<div class="sub-page-bar">
|
||||
<Page
|
||||
:total="total"
|
||||
:current="param.pageOption.page"
|
||||
@@ -50,10 +51,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizTokenServer from '@/api/biz/biz_token_server.js'
|
||||
import tokenServer from '@/api/subscription/token_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'BizTokens',
|
||||
name: 'SubscriptionTokens',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
@@ -104,7 +106,7 @@ export default {
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizTokenServer.page({ param: this.param })
|
||||
const res = await tokenServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
@@ -133,7 +135,7 @@ export default {
|
||||
async submitCreate() {
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizTokenServer.create({
|
||||
const res = await tokenServer.create({
|
||||
user_id: Number(this.createForm.user_id),
|
||||
token_name: this.createForm.token_name || 'default',
|
||||
expire_at: this.createForm.expire_at,
|
||||
@@ -156,7 +158,7 @@ export default {
|
||||
title: '吊销 Token',
|
||||
content: '确认吊销?',
|
||||
onOk: async () => {
|
||||
const res = await bizTokenServer.revoke({ id: row.id })
|
||||
const res = await tokenServer.revoke({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已吊销')
|
||||
this.load(1)
|
||||
@@ -166,18 +168,27 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
async doExport() {
|
||||
const res = await tokenServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
downloadCsvFromRows(res.data.rows, 'api_tokens.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
.sub-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
@@ -186,7 +197,7 @@ export default {
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
208
admin/src/views/subscription/usage.vue
Normal file
208
admin/src/views/subscription/usage.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<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>
|
||||
<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" />
|
||||
</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>
|
||||
|
||||
<Modal v-model="modal" :title="form.id ? '编辑用量' : '新增用量'" width="640" :loading="saving" @on-ok="save">
|
||||
<Form ref="formRef" :model="form" :rules="rules" :label-width="120">
|
||||
<FormItem label="用户ID" prop="user_id"><Input v-model="form.user_id" type="number" /></FormItem>
|
||||
<FormItem label="套餐ID" prop="plan_id"><Input v-model="form.plan_id" type="number" /></FormItem>
|
||||
<FormItem label="月份 YYYY-MM" prop="stat_month"><Input v-model="form.stat_month" placeholder="2025-03" /></FormItem>
|
||||
<FormItem label="msg_count"><Input v-model="form.msg_count" type="number" /></FormItem>
|
||||
<FormItem label="mass_count"><Input v-model="form.mass_count" type="number" /></FormItem>
|
||||
<FormItem label="friend_count"><Input v-model="form.friend_count" type="number" /></FormItem>
|
||||
<FormItem label="sns_count"><Input v-model="form.sns_count" type="number" /></FormItem>
|
||||
<FormItem label="active_user_count"><Input v-model="form.active_user_count" type="number" /></FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import usageServer from '@/api/subscription/usage_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'SubscriptionUsage',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'stat_month', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
modal: false,
|
||||
saving: false,
|
||||
form: {},
|
||||
rules: {
|
||||
user_id: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
plan_id: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
stat_month: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '用户', key: 'user_id', width: 90 },
|
||||
{ title: '套餐', key: 'plan_id', width: 90 },
|
||||
{ title: '月份', key: 'stat_month', width: 100 },
|
||||
{ title: 'msg', key: 'msg_count', width: 80 },
|
||||
{ title: 'mass', key: 'mass_count', width: 80 },
|
||||
{ title: 'friend', key: 'friend_count', width: 80 },
|
||||
{ title: 'sns', key: 'sns_count', width: 80 },
|
||||
{ title: 'active_user', key: 'active_user_count', width: 110 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 140,
|
||||
render: (h, p) =>
|
||||
h('div', [
|
||||
h('Button', { props: { type: 'info', size: 'small' }, on: { click: () => this.openEdit(p.row) } }, '编辑'),
|
||||
h('Button', { props: { type: 'error', size: 'small' }, class: { ml8: true }, on: { click: () => this.doDel(p.row) } }, '删除'),
|
||||
]),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load(1)
|
||||
},
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await usageServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '加载失败')
|
||||
}
|
||||
},
|
||||
onPage(p) {
|
||||
this.param.pageOption.page = p
|
||||
this.load()
|
||||
},
|
||||
onSize(s) {
|
||||
this.param.pageOption.pageSize = s
|
||||
this.load(1)
|
||||
},
|
||||
openEdit(row) {
|
||||
if (row) {
|
||||
this.form = { ...row }
|
||||
} else {
|
||||
this.form = {
|
||||
user_id: '',
|
||||
plan_id: '',
|
||||
stat_month: '',
|
||||
msg_count: 0,
|
||||
mass_count: 0,
|
||||
friend_count: 0,
|
||||
sns_count: 0,
|
||||
active_user_count: 0,
|
||||
}
|
||||
}
|
||||
this.modal = true
|
||||
},
|
||||
save() {
|
||||
this.saving = true
|
||||
this.$refs.formRef.validate(async (ok) => {
|
||||
if (!ok) {
|
||||
this.saving = false
|
||||
return
|
||||
}
|
||||
const payload = { ...this.form }
|
||||
payload.user_id = Number(payload.user_id)
|
||||
payload.plan_id = Number(payload.plan_id)
|
||||
try {
|
||||
const res = this.form.id ? await usageServer.edit(payload) : await usageServer.add(payload)
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('保存成功')
|
||||
this.modal = false
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
})
|
||||
},
|
||||
doDel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '删除',
|
||||
content: '确认删除该用量记录?',
|
||||
onOk: async () => {
|
||||
const res = await usageServer.del({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已删除')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
async doExport() {
|
||||
const res = await usageServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
downloadCsvFromRows(res.data.rows, 'usage_monthly.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.sub-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">业务用户</h2>
|
||||
<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="biz-search">
|
||||
<div class="sub-search">
|
||||
<Form inline :label-width="70">
|
||||
<FormItem label="条件">
|
||||
<Select v-model="param.seachOption.key" style="width: 140px">
|
||||
@@ -21,7 +22,7 @@
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-page-bar">
|
||||
<div class="sub-page-bar">
|
||||
<Page
|
||||
:total="total"
|
||||
:current="param.pageOption.page"
|
||||
@@ -63,10 +64,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizUserServer from '@/api/biz/biz_user_server.js'
|
||||
import userServer from '@/api/subscription/user_server.js'
|
||||
import { downloadCsvFromRows } from '@/utils/csvExport.js'
|
||||
|
||||
export default {
|
||||
name: 'BizUsers',
|
||||
name: 'SubscriptionUsers',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
@@ -104,7 +106,7 @@ export default {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 220,
|
||||
width: 340,
|
||||
render: (h, p) => {
|
||||
return h('div', [
|
||||
h(
|
||||
@@ -133,6 +135,15 @@ export default {
|
||||
},
|
||||
'禁用'
|
||||
),
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
props: { type: 'default', size: 'small' },
|
||||
class: { ml8: true },
|
||||
on: { click: () => this.revokeAllTokens(p.row) },
|
||||
},
|
||||
'吊销全部Token'
|
||||
),
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
@@ -154,7 +165,7 @@ export default {
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizUserServer.page({ param: this.param })
|
||||
const res = await userServer.page({ param: this.param })
|
||||
if (res && res.code === 0) {
|
||||
this.rows = res.data.rows || []
|
||||
this.total = res.data.count || 0
|
||||
@@ -187,8 +198,8 @@ export default {
|
||||
}
|
||||
try {
|
||||
const res = this.form.id
|
||||
? await bizUserServer.edit(this.form)
|
||||
: await bizUserServer.add(this.form)
|
||||
? await userServer.edit(this.form)
|
||||
: await userServer.add(this.form)
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('保存成功')
|
||||
this.modal = false
|
||||
@@ -202,7 +213,7 @@ export default {
|
||||
})
|
||||
},
|
||||
async showDetail(row) {
|
||||
const res = await bizUserServer.detail(row.id)
|
||||
const res = await userServer.detail(row.id)
|
||||
if (res && res.code === 0) {
|
||||
this.detail = res.data
|
||||
this.detailVisible = true
|
||||
@@ -215,7 +226,7 @@ export default {
|
||||
title: '禁用用户',
|
||||
content: '确认禁用该用户?',
|
||||
onOk: async () => {
|
||||
const res = await bizUserServer.disable({ id: row.id })
|
||||
const res = await userServer.disable({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已禁用')
|
||||
this.load(1)
|
||||
@@ -230,7 +241,7 @@ export default {
|
||||
title: '删除用户',
|
||||
content: '确认删除?若存在订阅/Token 可能受外键限制。',
|
||||
onOk: async () => {
|
||||
const res = await bizUserServer.del({ id: row.id })
|
||||
const res = await userServer.del({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已删除')
|
||||
this.load(1)
|
||||
@@ -240,18 +251,43 @@ export default {
|
||||
},
|
||||
})
|
||||
},
|
||||
async doExport() {
|
||||
const res = await userServer.exportRows({ param: this.param })
|
||||
if (res && res.code === 0 && res.data && res.data.rows) {
|
||||
downloadCsvFromRows(res.data.rows, 'users.csv')
|
||||
this.$Message.success('已导出')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '导出失败')
|
||||
}
|
||||
},
|
||||
revokeAllTokens(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '吊销全部 Token',
|
||||
content: '将吊销该用户下所有有效 Token,是否继续?',
|
||||
onOk: async () => {
|
||||
const res = await userServer.revokeAllTokens({ user_id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
const n = (res.data && res.data.revoked) != null ? res.data.revoked : 0
|
||||
this.$Message.success('已吊销 ' + n + ' 条')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '操作失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
.sub-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
.sub-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
.sub-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
@@ -260,10 +296,10 @@ export default {
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-search {
|
||||
.sub-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
.sub-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
Reference in New Issue
Block a user