This commit is contained in:
张成
2026-03-24 17:03:54 +08:00
parent 268520a0f2
commit 5b654824b4
25 changed files with 799 additions and 111 deletions

View File

@@ -0,0 +1,306 @@
<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">
<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="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="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 userServer from '@/api/subscription/user_server.js'
import { downloadCsvFromRows } from '@/utils/csvExport.js'
export default {
name: 'SubscriptionUsers',
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: 340,
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: 'default', size: 'small' },
class: { ml8: true },
on: { click: () => this.revokeAllTokens(p.row) },
},
'吊销全部Token'
),
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 userServer.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 userServer.edit(this.form)
: await userServer.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 userServer.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 userServer.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 userServer.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 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>
.sub-page {
padding: 16px;
}
.sub-toolbar {
margin-bottom: 12px;
}
.sub-title {
display: inline-block;
margin: 0 16px 0 0;
font-size: 18px;
vertical-align: middle;
}
.ml8 {
margin-left: 8px;
}
.sub-search {
margin-bottom: 12px;
}
.sub-page-bar {
margin-top: 12px;
text-align: right;
}
</style>