Files
opsx-licence/frontend/src/views/Products.vue
T

163 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<el-card shadow="never">
<div class="toolbar">
<el-input v-model="keyword" placeholder="搜索产品名称/编码" clearable style="width: 240px" @keyup.enter="loadData" @clear="loadData" />
<div>
<el-button type="danger" plain :disabled="!selectedRows.length" @click="batchDelete">
批量删除{{ selectedRows.length ? `(${selectedRows.length})` : '' }}
</el-button>
<el-button type="primary" @click="dialogVisible = true">新增产品</el-button>
</div>
</div>
<el-table :data="list" v-loading="loading" stripe @selection-change="onSelectionChange">
<el-table-column type="selection" width="48" />
<el-table-column prop="name" label="产品名称" min-width="140" />
<el-table-column prop="code" label="编码" width="120" />
<el-table-column prop="version" label="版本" width="90" />
<el-table-column prop="price" label="价格" width="90">
<template #default="{ row }">{{ row.price }} {{ row.currency || '' }}</template>
</el-table-column>
<el-table-column prop="price_model" label="计费模式" width="110" />
<el-table-column label="功能" min-width="180">
<template #default="{ row }">
<el-tag v-for="f in row.features" :key="f" size="small" style="margin: 2px">{{ f }}</el-tag>
</template>
</el-table-column>
<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 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="560px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="名称" prop="name"><el-input v-model="form.name" /></el-form-item>
<el-form-item label="编码" prop="code"><el-input v-model="form.code" :disabled="!!form.id" /></el-form-item>
<el-form-item label="版本"><el-input v-model="form.version" /></el-form-item>
<el-form-item label="价格"><el-input-number v-model="form.price" :min="0" :precision="2" /></el-form-item>
<el-form-item label="计费模式">
<el-select v-model="form.price_model" style="width: 100%">
<el-option value="subscription" label="订阅" /><el-option value="perpetual" label="买断" /><el-option value="trial" label="试用" />
</el-select>
</el-form-item>
<el-form-item label="功能特性">
<el-select v-model="form.features" multiple filterable allow-create default-first-option style="width: 100%" placeholder="输入后回车添加" />
</el-form-item>
<el-form-item label="描述"><el-input v-model="form.description" 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 { ElMessage, ElMessageBox } from 'element-plus'
const selectedRows = ref([])
function onSelectionChange(rows) {
selectedRows.value = rows || []
}
// 批量删除:被 License 引用的产品由后端自动跳过 // Logan.Li
async function batchDelete() {
const ids = selectedRows.value.map((r) => r.id).filter(Boolean)
if (!ids.length) return
await ElMessageBox.confirm(
`已选 ${ids.length} 个产品;被 License 引用的将自动跳过,未引用的连同其定价计划一并删除。确认继续?`,
'批量删除',
{ type: 'warning' },
)
const res = await batchDeleteProducts(ids)
const data = res.data || {}
ElMessage.success(`删除 ${data.deleted || 0} 条,跳过 ${data.skipped || 0} 条${data.failed ? `,失败 ${data.failed}` : ''}`)
selectedRows.value = []
loadData()
}
import { getProducts, createProduct, updateProduct, deleteProduct, updateProductStatus, batchDeleteProducts } from '@/api/product'
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: '', code: '', version: '', price: 0, price_model: 'subscription', features: [], description: '' })
const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入编码', trigger: 'blur' }],
}
async function loadData() {
loading.value = true
try {
const res = await getProducts({ 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 edit(row) {
Object.assign(form, row, { features: row.features || [] })
dialogVisible.value = true
}
async function submit() {
await formRef.value.validate(async (valid) => {
if (!valid) return
try {
if (form.id) {
await updateProduct(form.id, form)
} else {
await createProduct(form)
}
dialogVisible.value = false
loadData()
} catch { /* 拦截器处理 */ }
})
}
async function remove(row) {
await ElMessageBox.confirm(`确定删除产品「${row.name}」?`, '提示', { type: 'warning' })
await deleteProduct(row.id)
loadData()
}
async function toggleStatus(row, val) {
await updateProductStatus(row.id, val ? 1 : 0)
loadData()
}
loadData()
</script>
<style scoped>
.toolbar { display: flex; justify-content: space-between; margin-bottom: 16px; }
</style>