基于 opsx-web-lite 框架(Go+Gin+Vue3+Element Plus)构建许可证管理服务器,
业务域参考 Licence 服务器 Web 版。
【后端 - 4层架构】
- 认证:单 Admin 表 + JWT(HS256)
- 核心授权域:Product / Customer / License / Activation
· License 生成/验证/激活/机器绑定/心跳/续期/吊销
· 客户端契约接口:/api/v1/license/{verify,activate,deactivate,heartbeat}
- 定价订单:Plan / Order / 支付网关配置
- 防重复购买:CustomerEntitlement + 幂等支付 webhook + 手动发放
- 分析审计:Dashboard 统计 / Analytics / AuditLog / Setting 管理
【前端】
- Dashboard(ECharts 图表)、产品、客户、License、计划、订单、权益、日志、设置
- 9 个完整 CRUD 业务页面 + 登录/布局
【部署】
- SQLite + 单二进制(//go:embed 嵌入前端)
- 开发:./run.sh start(前端5181/后端9001)
- 生产:./run.sh build && ./run.sh binary start
- 默认账号:admin/admin
Author: Logan.Li
120 lines
4.3 KiB
Vue
120 lines
4.3 KiB
Vue
<template>
|
|
<div>
|
|
<el-card shadow="never">
|
|
<div class="toolbar">
|
|
<el-input v-model="keyword" placeholder="搜索客户/邮箱/公司" clearable style="width: 260px" @keyup.enter="loadData" @clear="loadData" />
|
|
<el-button type="primary" @click="openCreate">新增客户</el-button>
|
|
</div>
|
|
|
|
<el-table :data="list" v-loading="loading" stripe>
|
|
<el-table-column prop="name" label="客户名称" min-width="120" />
|
|
<el-table-column prop="company" label="公司" min-width="140" />
|
|
<el-table-column prop="email" label="邮箱" min-width="180" />
|
|
<el-table-column prop="phone" label="电话" width="130" />
|
|
<el-table-column label="状态" width="80">
|
|
<template #default="{ row }">
|
|
<el-switch :model-value="row.status === 1" @change="(v) => toggleStatus(row, v)" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="created_at" label="创建时间" width="170" />
|
|
<el-table-column label="操作" width="140" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button size="small" @click="edit(row)">编辑</el-button>
|
|
<el-button size="small" type="danger" @click="remove(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-pagination
|
|
v-model:current-page="page" v-model:page-size="pageSize"
|
|
:total="total" layout="total, prev, pager, next" style="margin-top: 16px"
|
|
@current-change="loadData"
|
|
/>
|
|
</el-card>
|
|
|
|
<el-dialog v-model="dialogVisible" :title="form.id ? '编辑客户' : '新增客户'" width="520px">
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
|
<el-form-item label="名称" prop="name"><el-input v-model="form.name" /></el-form-item>
|
|
<el-form-item label="公司"><el-input v-model="form.company" /></el-form-item>
|
|
<el-form-item label="邮箱" prop="email"><el-input v-model="form.email" /></el-form-item>
|
|
<el-form-item label="电话"><el-input v-model="form.phone" /></el-form-item>
|
|
<el-form-item label="地址"><el-input v-model="form.address" /></el-form-item>
|
|
<el-form-item label="备注"><el-input v-model="form.notes" type="textarea" :rows="2" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="submit">确认</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive } from 'vue'
|
|
import { ElMessageBox } from 'element-plus'
|
|
import { getCustomers, createCustomer, updateCustomer, deleteCustomer, updateCustomerStatus } from '@/api/customer'
|
|
|
|
const list = ref([])
|
|
const loading = ref(false)
|
|
const keyword = ref('')
|
|
const page = ref(1)
|
|
const pageSize = ref(10)
|
|
const total = ref(0)
|
|
|
|
const dialogVisible = ref(false)
|
|
const formRef = ref(null)
|
|
const form = reactive({ id: null, name: '', company: '', email: '', phone: '', address: '', notes: '' })
|
|
const rules = {
|
|
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
|
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
|
|
}
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const res = await getCustomers({ page: page.value, page_size: pageSize.value, keyword: keyword.value })
|
|
list.value = res.data.list || []
|
|
total.value = res.data.total
|
|
} finally { loading.value = false }
|
|
}
|
|
|
|
function openCreate() {
|
|
Object.assign(form, { id: null, name: '', company: '', email: '', phone: '', address: '', notes: '' })
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
function edit(row) {
|
|
Object.assign(form, row)
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
async function submit() {
|
|
await formRef.value.validate(async (valid) => {
|
|
if (!valid) return
|
|
try {
|
|
if (form.id) await updateCustomer(form.id, form)
|
|
else await createCustomer(form)
|
|
dialogVisible.value = false
|
|
loadData()
|
|
} catch { /* */ }
|
|
})
|
|
}
|
|
|
|
async function remove(row) {
|
|
await ElMessageBox.confirm(`确定删除客户「${row.name}」?`, '提示', { type: 'warning' })
|
|
await deleteCustomer(row.id)
|
|
loadData()
|
|
}
|
|
|
|
async function toggleStatus(row, val) {
|
|
await updateCustomerStatus(row.id, val ? 1 : 0)
|
|
loadData()
|
|
}
|
|
|
|
loadData()
|
|
</script>
|
|
|
|
<style scoped>
|
|
.toolbar { display: flex; justify-content: space-between; margin-bottom: 16px; }
|
|
</style>
|