init
This commit is contained in:
96
admin/src/views/biz/biz_payment.vue
Normal file
96
admin/src/views/biz/biz_payment.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<h2 class="biz-title">支付确认(轻量)</h2>
|
||||
<p class="biz-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" />
|
||||
</FormItem>
|
||||
<FormItem label="支付单号">
|
||||
<Input v-model="offline.payment_ref" placeholder="流水号/凭证号" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" :loading="loading1" @click="doOffline">确认线下收款</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
<Card dis-hover title="链接支付确认" style="max-width: 520px">
|
||||
<Form :label-width="110">
|
||||
<FormItem label="订阅ID">
|
||||
<Input v-model="link.subscription_id" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="第三方单号">
|
||||
<Input v-model="link.payment_ref" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" :loading="loading2" @click="doLink">确认链接支付</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizPaymentServer from '@/api/biz/biz_payment_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizPayment',
|
||||
data() {
|
||||
return {
|
||||
offline: { subscription_id: '', payment_ref: '' },
|
||||
link: { subscription_id: '', payment_ref: '' },
|
||||
loading1: false,
|
||||
loading2: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async doOffline() {
|
||||
this.loading1 = true
|
||||
try {
|
||||
const res = await bizPaymentServer.confirmOffline({
|
||||
subscription_id: Number(this.offline.subscription_id),
|
||||
payment_ref: this.offline.payment_ref,
|
||||
})
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已确认,订阅已激活')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.loading1 = false
|
||||
}
|
||||
},
|
||||
async doLink() {
|
||||
this.loading2 = true
|
||||
try {
|
||||
const res = await bizPaymentServer.confirmLink({
|
||||
subscription_id: Number(this.link.subscription_id),
|
||||
payment_ref: this.link.payment_ref,
|
||||
})
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已确认,订阅已激活')
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.loading2 = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.biz-desc {
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
258
admin/src/views/biz/biz_plans.vue
Normal file
258
admin/src/views/biz/biz_plans.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">套餐</h2>
|
||||
<Button type="primary" @click="openEdit(null)">新增套餐</Button>
|
||||
<Button class="ml8" @click="load(1)">刷新</Button>
|
||||
</div>
|
||||
<div class="biz-search">
|
||||
<Form inline :label-width="70">
|
||||
<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)" />
|
||||
</FormItem>
|
||||
<Button type="primary" @click="load(1)">查询</Button>
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-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="720" :loading="saving" @on-ok="save">
|
||||
<Form ref="formRef" :model="form" :rules="rules" :label-width="120">
|
||||
<FormItem label="套餐编码" prop="plan_code">
|
||||
<Input v-model="form.plan_code" :disabled="!!form.id" />
|
||||
</FormItem>
|
||||
<FormItem label="套餐名称" prop="plan_name">
|
||||
<Input v-model="form.plan_name" />
|
||||
</FormItem>
|
||||
<FormItem label="月费">
|
||||
<Input v-model="form.monthly_price" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="授权费">
|
||||
<Input v-model="form.auth_fee" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="账号上限">
|
||||
<Input v-model="form.account_limit" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="活跃上限">
|
||||
<Input v-model="form.active_user_limit" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="消息额度">
|
||||
<Input v-model="form.msg_quota" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="群发额度">
|
||||
<Input v-model="form.mass_quota" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="好友额度">
|
||||
<Input v-model="form.friend_quota" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="朋友圈额度">
|
||||
<Input v-model="form.sns_quota" type="number" />
|
||||
</FormItem>
|
||||
<FormItem label="功能点 JSON">
|
||||
<Input v-model="featuresText" type="textarea" :rows="4" placeholder='如 {"msg":true} 或 ["msg","mass"]' />
|
||||
</FormItem>
|
||||
<FormItem label="状态" prop="status">
|
||||
<Select v-model="form.status" style="width: 100%">
|
||||
<Option value="active">上线</Option>
|
||||
<Option value="inactive">下线</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizPlanServer from '@/api/biz/biz_plan_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizPlans',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'plan_code', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
modal: false,
|
||||
saving: false,
|
||||
form: {},
|
||||
featuresText: '{}',
|
||||
rules: {
|
||||
plan_code: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
plan_name: [{ required: true, message: '必填', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '必选', trigger: 'change' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '编码', key: 'plan_code', width: 120 },
|
||||
{ title: '名称', key: 'plan_name', minWidth: 140 },
|
||||
{ title: '月费', key: 'monthly_price', width: 90 },
|
||||
{ title: '状态', key: 'status', width: 90 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 260,
|
||||
render: (h, p) =>
|
||||
h('div', [
|
||||
h('Button', { props: { type: 'info', size: 'small' }, on: { click: () => this.openEdit(p.row) } }, '编辑'),
|
||||
h('Button', { props: { type: 'warning', size: 'small' }, class: { ml8: true }, on: { click: () => this.toggle(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 bizPlanServer.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 }
|
||||
this.featuresText =
|
||||
row.enabled_features == null
|
||||
? ''
|
||||
: typeof row.enabled_features === 'string'
|
||||
? row.enabled_features
|
||||
: JSON.stringify(row.enabled_features, null, 2)
|
||||
} else {
|
||||
this.form = {
|
||||
plan_code: '',
|
||||
plan_name: '',
|
||||
monthly_price: 0,
|
||||
auth_fee: 0,
|
||||
account_limit: 0,
|
||||
active_user_limit: 0,
|
||||
msg_quota: 0,
|
||||
mass_quota: 0,
|
||||
friend_quota: 0,
|
||||
sns_quota: 0,
|
||||
status: 'active',
|
||||
}
|
||||
this.featuresText = '{}'
|
||||
}
|
||||
this.modal = true
|
||||
},
|
||||
save() {
|
||||
this.saving = true
|
||||
this.$refs.formRef.validate(async (ok) => {
|
||||
if (!ok) {
|
||||
this.saving = false
|
||||
return
|
||||
}
|
||||
let enabled_features = null
|
||||
const t = (this.featuresText || '').trim()
|
||||
if (t) {
|
||||
try {
|
||||
enabled_features = JSON.parse(t)
|
||||
} catch (e) {
|
||||
this.$Message.error('功能点 JSON 格式错误')
|
||||
this.saving = false
|
||||
return
|
||||
}
|
||||
}
|
||||
const payload = { ...this.form, enabled_features }
|
||||
try {
|
||||
const res = this.form.id ? await bizPlanServer.edit(payload) : await bizPlanServer.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
|
||||
}
|
||||
})
|
||||
},
|
||||
async toggle(row) {
|
||||
const res = await bizPlanServer.toggle({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('状态已更新为 ' + (res.data && res.data.status))
|
||||
this.load()
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
},
|
||||
doDel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '删除套餐',
|
||||
content: '确认删除?若已被订阅引用可能失败。',
|
||||
onOk: async () => {
|
||||
const res = await bizPlanServer.del({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已删除')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
280
admin/src/views/biz/biz_subscriptions.vue
Normal file
280
admin/src/views/biz/biz_subscriptions.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">订阅</h2>
|
||||
<Button type="primary" @click="openOpen">开通订阅</Button>
|
||||
<Button class="ml8" @click="load(1)">刷新</Button>
|
||||
</div>
|
||||
<div class="biz-search">
|
||||
<Form inline>
|
||||
<FormItem label="用户ID">
|
||||
<Input v-model="param.seachOption.value" style="width: 140px" placeholder="筛选 user_id" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Select v-model="param.seachOption.key" style="width: 120px">
|
||||
<Option value="user_id">用户ID</Option>
|
||||
<Option value="status">状态</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<Button type="primary" @click="load(1)">查询</Button>
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-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="openModal" title="开通订阅" width="640" :loading="saving" @on-ok="submitOpen">
|
||||
<Form :label-width="110">
|
||||
<FormItem label="用户ID"><Input v-model="openForm.user_id" type="number" /></FormItem>
|
||||
<FormItem label="套餐ID"><Input v-model="openForm.plan_id" type="number" /></FormItem>
|
||||
<FormItem label="开始时间"><Input v-model="openForm.start_time" placeholder="2025-01-01 00:00:00" /></FormItem>
|
||||
<FormItem label="结束时间"><Input v-model="openForm.end_time" placeholder="2025-12-31 23:59:59" /></FormItem>
|
||||
<FormItem label="状态">
|
||||
<Select v-model="openForm.status" style="width: 100%">
|
||||
<Option value="pending">pending</Option>
|
||||
<Option value="active">active</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="续费方式">
|
||||
<Select v-model="openForm.renew_mode" style="width: 100%">
|
||||
<Option value="manual">manual</Option>
|
||||
<Option value="auto">auto</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="支付渠道">
|
||||
<Select v-model="openForm.payment_channel" clearable style="width: 100%">
|
||||
<Option value="offline">offline</Option>
|
||||
<Option value="pay_link">pay_link</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="支付单号"><Input v-model="openForm.payment_ref" /></FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="renewModal" title="续费" :loading="saving" @on-ok="submitRenew">
|
||||
<Form :label-width="100">
|
||||
<FormItem label="新结束时间"><Input v-model="renewForm.end_time" /></FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="upgradeModal" title="升级套餐" :loading="saving" @on-ok="submitUpgrade">
|
||||
<Form :label-width="100">
|
||||
<FormItem label="新套餐ID"><Input v-model="upgradeForm.new_plan_id" type="number" /></FormItem>
|
||||
<FormItem label="开始"><Input v-model="upgradeForm.start_time" /></FormItem>
|
||||
<FormItem label="结束"><Input v-model="upgradeForm.end_time" /></FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizSubscriptionServer from '@/api/biz/biz_subscription_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizSubscriptions',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'user_id', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
openModal: false,
|
||||
renewModal: false,
|
||||
upgradeModal: false,
|
||||
saving: false,
|
||||
currentRow: null,
|
||||
openForm: {},
|
||||
renewForm: { end_time: '' },
|
||||
upgradeForm: { new_plan_id: '', start_time: '', end_time: '' },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '用户', key: 'user_id', width: 90 },
|
||||
{ title: '套餐', key: 'plan_id', width: 90 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '开始', key: 'start_time', minWidth: 150 },
|
||||
{ title: '结束', key: 'end_time', minWidth: 150 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 200,
|
||||
render: (h, p) =>
|
||||
h('div', [
|
||||
h('Button', { props: { size: 'small' }, on: { click: () => this.openRenew(p.row) } }, '续费'),
|
||||
h('Button', { props: { size: 'small' }, class: { ml8: true }, on: { click: () => this.openUpgrade(p.row) } }, '升级'),
|
||||
h('Button', { props: { type: 'error', size: 'small' }, class: { ml8: true }, on: { click: () => this.doCancel(p.row) } }, '取消'),
|
||||
]),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load(1)
|
||||
},
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizSubscriptionServer.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)
|
||||
},
|
||||
openOpen() {
|
||||
const now = new Date()
|
||||
const end = new Date(now)
|
||||
end.setFullYear(end.getFullYear() + 1)
|
||||
const fmt = (d) =>
|
||||
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(
|
||||
d.getHours()
|
||||
).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:00`
|
||||
this.openForm = {
|
||||
user_id: '',
|
||||
plan_id: '',
|
||||
start_time: fmt(now),
|
||||
end_time: fmt(end),
|
||||
status: 'pending',
|
||||
renew_mode: 'manual',
|
||||
payment_channel: '',
|
||||
payment_ref: '',
|
||||
}
|
||||
this.openModal = true
|
||||
},
|
||||
async submitOpen() {
|
||||
this.saving = true
|
||||
try {
|
||||
const body = {
|
||||
user_id: Number(this.openForm.user_id),
|
||||
plan_id: Number(this.openForm.plan_id),
|
||||
start_time: this.openForm.start_time,
|
||||
end_time: this.openForm.end_time,
|
||||
status: this.openForm.status,
|
||||
renew_mode: this.openForm.renew_mode,
|
||||
payment_channel: this.openForm.payment_channel || null,
|
||||
payment_ref: this.openForm.payment_ref || null,
|
||||
}
|
||||
const res = await bizSubscriptionServer.open(body)
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已创建订阅')
|
||||
this.openModal = false
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
openRenew(row) {
|
||||
this.currentRow = row
|
||||
this.renewForm = { end_time: row.end_time || '' }
|
||||
this.renewModal = true
|
||||
},
|
||||
async submitRenew() {
|
||||
if (!this.currentRow) return
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizSubscriptionServer.renew({
|
||||
subscription_id: this.currentRow.id,
|
||||
end_time: this.renewForm.end_time,
|
||||
})
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已续费')
|
||||
this.renewModal = false
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
openUpgrade(row) {
|
||||
this.currentRow = row
|
||||
this.upgradeForm = { new_plan_id: row.plan_id, start_time: '', end_time: '' }
|
||||
this.upgradeModal = true
|
||||
},
|
||||
async submitUpgrade() {
|
||||
if (!this.currentRow) return
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizSubscriptionServer.upgrade({
|
||||
subscription_id: this.currentRow.id,
|
||||
new_plan_id: Number(this.upgradeForm.new_plan_id),
|
||||
start_time: this.upgradeForm.start_time || undefined,
|
||||
end_time: this.upgradeForm.end_time || undefined,
|
||||
})
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已升级')
|
||||
this.upgradeModal = false
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
doCancel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '取消订阅',
|
||||
content: '确认取消?',
|
||||
onOk: async () => {
|
||||
const res = await bizSubscriptionServer.cancel({ subscription_id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已取消')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
193
admin/src/views/biz/biz_tokens.vue
Normal file
193
admin/src/views/biz/biz_tokens.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">API Token</h2>
|
||||
<Button type="primary" @click="openCreate">创建 Token</Button>
|
||||
<Button class="ml8" @click="load(1)">刷新</Button>
|
||||
</div>
|
||||
<div class="biz-search">
|
||||
<Form inline>
|
||||
<FormItem label="用户ID">
|
||||
<Input v-model="param.seachOption.value" style="width: 140px" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Select v-model="param.seachOption.key" style="width: 120px">
|
||||
<Option value="user_id">用户ID</Option>
|
||||
<Option value="status">状态</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<Button type="primary" @click="load(1)">查询</Button>
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-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="createModal" title="创建 Token" width="560" :loading="saving" @on-ok="submitCreate">
|
||||
<Form :label-width="100">
|
||||
<FormItem label="用户ID"><Input v-model="createForm.user_id" type="number" /></FormItem>
|
||||
<FormItem label="名称"><Input v-model="createForm.token_name" placeholder="default" /></FormItem>
|
||||
<FormItem label="过期时间"><Input v-model="createForm.expire_at" placeholder="2026-12-31 23:59:59" /></FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="plainModal" title="请立即保存 Token 明文" width="560" :closable="false">
|
||||
<Alert type="error">仅此一次展示,关闭后无法再次查看明文。</Alert>
|
||||
<Input type="textarea" :rows="4" v-model="plainToken" readonly />
|
||||
<div slot="footer">
|
||||
<Button type="primary" @click="plainModal = false">已保存</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizTokenServer from '@/api/biz/biz_token_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizTokens',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'user_id', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
createModal: false,
|
||||
plainModal: false,
|
||||
plainToken: '',
|
||||
saving: false,
|
||||
createForm: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: '用户', key: 'user_id', width: 90 },
|
||||
{ title: '套餐', key: 'plan_id', width: 90 },
|
||||
{ title: '名称', key: 'token_name', width: 120 },
|
||||
{ title: '状态', key: 'status', width: 90 },
|
||||
{ title: '过期', key: 'expire_at', minWidth: 150 },
|
||||
{ title: '最后使用', key: 'last_used_at', minWidth: 150 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 100,
|
||||
render: (h, p) =>
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
props: { type: 'error', size: 'small' },
|
||||
on: {
|
||||
click: () => this.doRevoke(p.row),
|
||||
},
|
||||
},
|
||||
'吊销'
|
||||
),
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load(1)
|
||||
},
|
||||
methods: {
|
||||
async load(page) {
|
||||
if (page) this.param.pageOption.page = page
|
||||
const res = await bizTokenServer.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)
|
||||
},
|
||||
openCreate() {
|
||||
const d = new Date()
|
||||
d.setFullYear(d.getFullYear() + 1)
|
||||
const fmt = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(
|
||||
2,
|
||||
'0'
|
||||
)} 23:59:59`
|
||||
this.createForm = { user_id: '', token_name: 'default', expire_at: fmt }
|
||||
this.createModal = true
|
||||
},
|
||||
async submitCreate() {
|
||||
this.saving = true
|
||||
try {
|
||||
const res = await bizTokenServer.create({
|
||||
user_id: Number(this.createForm.user_id),
|
||||
token_name: this.createForm.token_name || 'default',
|
||||
expire_at: this.createForm.expire_at,
|
||||
})
|
||||
if (res && res.code === 0) {
|
||||
if (res.data.warn) this.$Message.warning(res.data.warn)
|
||||
this.createModal = false
|
||||
this.plainToken = res.data.plain_token
|
||||
this.plainModal = true
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '创建失败')
|
||||
}
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
doRevoke(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '吊销 Token',
|
||||
content: '确认吊销?',
|
||||
onOk: async () => {
|
||||
const res = await bizTokenServer.revoke({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已吊销')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
270
admin/src/views/biz/biz_users.vue
Normal file
270
admin/src/views/biz/biz_users.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<div class="biz-page">
|
||||
<div class="biz-toolbar">
|
||||
<h2 class="biz-title">业务用户</h2>
|
||||
<Button type="primary" @click="openEdit(null)">新增</Button>
|
||||
<Button class="ml8" @click="load(1)">刷新</Button>
|
||||
</div>
|
||||
<div class="biz-search">
|
||||
<Form inline :label-width="70">
|
||||
<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)" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" @click="load(1)">查询</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" border stripe />
|
||||
<div class="biz-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="100">
|
||||
<FormItem label="名称" prop="name">
|
||||
<Input v-model="form.name" />
|
||||
</FormItem>
|
||||
<FormItem label="手机" prop="mobile">
|
||||
<Input v-model="form.mobile" />
|
||||
</FormItem>
|
||||
<FormItem label="邮箱">
|
||||
<Input v-model="form.email" />
|
||||
</FormItem>
|
||||
<FormItem label="公司">
|
||||
<Input v-model="form.company_name" />
|
||||
</FormItem>
|
||||
<FormItem label="状态" prop="status">
|
||||
<Select v-model="form.status" style="width: 100%">
|
||||
<Option value="active">正常</Option>
|
||||
<Option value="disabled">禁用</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal v-model="detailVisible" title="用户详情" width="720" footer-hide>
|
||||
<p v-if="detail">Token 数量:{{ detail.tokenCount }}</p>
|
||||
<Table v-if="detail && detail.subscriptions" :columns="subCols" :data="detail.subscriptions" size="small" border />
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bizUserServer from '@/api/biz/biz_user_server.js'
|
||||
|
||||
export default {
|
||||
name: 'BizUsers',
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
total: 0,
|
||||
param: {
|
||||
seachOption: { key: 'mobile', value: '' },
|
||||
pageOption: { page: 1, pageSize: 20, total: 0 },
|
||||
},
|
||||
modal: false,
|
||||
saving: false,
|
||||
form: {},
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
},
|
||||
detailVisible: false,
|
||||
detail: null,
|
||||
subCols: [
|
||||
{ title: 'ID', key: 'id', width: 80 },
|
||||
{ title: '套餐ID', key: 'plan_id', width: 90 },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '开始', key: 'start_time', minWidth: 160 },
|
||||
{ title: '结束', key: 'end_time', minWidth: 160 },
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return [
|
||||
{ title: 'ID', key: 'id', width: 80 },
|
||||
{ title: '名称', key: 'name', minWidth: 120 },
|
||||
{ title: '手机', key: 'mobile', width: 130 },
|
||||
{ title: '公司', key: 'company_name', minWidth: 140 },
|
||||
{ title: '状态', key: 'status', width: 90 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'a',
|
||||
width: 220,
|
||||
render: (h, p) => {
|
||||
return h('div', [
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
props: { type: 'info', size: 'small' },
|
||||
on: { click: () => this.openEdit(p.row) },
|
||||
},
|
||||
'编辑'
|
||||
),
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
props: { type: 'default', size: 'small' },
|
||||
class: { ml8: true },
|
||||
on: { click: () => this.showDetail(p.row) },
|
||||
},
|
||||
'详情'
|
||||
),
|
||||
h(
|
||||
'Button',
|
||||
{
|
||||
props: { type: 'warning', size: 'small' },
|
||||
class: { ml8: true },
|
||||
on: { click: () => this.doDisable(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 bizUserServer.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 = { name: '', mobile: '', email: '', company_name: '', status: 'active' }
|
||||
}
|
||||
this.modal = true
|
||||
},
|
||||
save() {
|
||||
this.saving = true
|
||||
this.$refs.formRef.validate(async (ok) => {
|
||||
if (!ok) {
|
||||
this.saving = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = this.form.id
|
||||
? await bizUserServer.edit(this.form)
|
||||
: await bizUserServer.add(this.form)
|
||||
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
|
||||
}
|
||||
})
|
||||
},
|
||||
async showDetail(row) {
|
||||
const res = await bizUserServer.detail(row.id)
|
||||
if (res && res.code === 0) {
|
||||
this.detail = res.data
|
||||
this.detailVisible = true
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '加载详情失败')
|
||||
}
|
||||
},
|
||||
doDisable(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '禁用用户',
|
||||
content: '确认禁用该用户?',
|
||||
onOk: async () => {
|
||||
const res = await bizUserServer.disable({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已禁用')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '操作失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
doDel(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '删除用户',
|
||||
content: '确认删除?若存在订阅/Token 可能受外键限制。',
|
||||
onOk: async () => {
|
||||
const res = await bizUserServer.del({ id: row.id })
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success('已删除')
|
||||
this.load(1)
|
||||
} else {
|
||||
this.$Message.error((res && res.message) || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.biz-page {
|
||||
padding: 16px;
|
||||
}
|
||||
.biz-toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-title {
|
||||
display: inline-block;
|
||||
margin: 0 16px 0 0;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ml8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.biz-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-page-bar {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
35
admin/src/views/test/test.vue
Normal file
35
admin/src/views/test/test.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="test-page">
|
||||
<h2 class="test-page__title">Test</h2>
|
||||
<p class="test-page__desc">基础测试页面(动态菜单的 component 请填 <code>test/test</code>)</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TestPage'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.test-page {
|
||||
padding: 24px;
|
||||
}
|
||||
.test-page__title {
|
||||
margin: 0 0 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.test-page__desc {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.test-page__desc code {
|
||||
padding: 2px 6px;
|
||||
font-size: 13px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user