This commit is contained in:
张成
2025-12-27 17:39:14 +08:00
parent b17d08ffa8
commit 6e38ba6b38
11 changed files with 1450 additions and 77 deletions

View File

@@ -0,0 +1,54 @@
/**
* 价格套餐 API 服务
*/
class PricingPlansServer {
/**
* 分页查询价格套餐
* @param {Object} param - 查询参数
* @param {Object} param.seachOption - 搜索条件
* @param {Object} param.pageOption - 分页选项
* @returns {Promise}
*/
page(param) {
return window.framework.http.post('/pricing_plans/list', param)
}
/**
* 获取价格套餐详情
* @param {Number|String} id - 价格套餐ID
* @returns {Promise}
*/
getById(id) {
return window.framework.http.get('/pricing_plans/detail', { id })
}
/**
* 新增价格套餐
* @param {Object} row - 价格套餐数据
* @returns {Promise}
*/
add(row) {
return window.framework.http.post('/pricing_plans/create', row)
}
/**
* 更新价格套餐信息
* @param {Object} row - 价格套餐数据
* @returns {Promise}
*/
update(row) {
return window.framework.http.post('/pricing_plans/update', row)
}
/**
* 删除价格套餐
* @param {Object} row - 价格套餐数据包含id
* @returns {Promise}
*/
del(row) {
return window.framework.http.post('/pricing_plans/delete', { id: row.id })
}
}
export default new PricingPlansServer()

View File

@@ -21,6 +21,7 @@ import TaskStatus from '@/views/task/task_status.vue'
import SystemConfig from '@/views/system/system_config.vue'
import Version from '@/views/system/version.vue'
import JobTypes from '@/views/work/job_types.vue'
import PricingPlans from '@/views/system/pricing_plans.vue'
// 首页模块
import HomeIndex from '@/views/home/index.vue'
@@ -53,6 +54,8 @@ const componentMap = {
'system/system_config': SystemConfig,
'system/version': Version,
'work/job_types': JobTypes,
'system/pricing_plans': PricingPlans,
'system/pricing_plans.vue': PricingPlans,
'home/index': HomeIndex,

View File

@@ -0,0 +1,385 @@
<template>
<div class="content-view">
<div class="table-head-tool">
<Button type="primary" @click="showAddWarp">新增价格套餐</Button>
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
<FormItem :label-width="20" class="flex">
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
:placeholder="seachTypePlaceholder">
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
</Select>
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
placeholder="请输入关键字" @on-search="query(1)" />
</FormItem>
<FormItem label="状态">
<Select v-model="gridOption.param.seachOption.is_active" style="width: 120px" clearable
@on-change="query(1)">
<Option :value="1">启用</Option>
<Option :value="0">禁用</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="query(1)">查询</Button>
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
</FormItem>
</Form>
</div>
<div class="table-body">
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
@changePage="query"></tables>
</div>
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
</editModal>
</div>
</template>
<script>
import pricingPlansServer from '@/api/system/pricing_plans_server.js'
export default {
data() {
let rules = {}
rules["name"] = [{ required: true, message: '请填写套餐名称', trigger: 'blur' }]
rules["duration"] = [{ required: true, message: '请填写时长描述', trigger: 'blur' }]
rules["days"] = [{ required: true, message: '请填写天数', trigger: 'blur' }]
rules["price"] = [{ required: true, message: '请填写价格', trigger: 'blur' }]
return {
seachTypes: [
{ key: 'name', value: '套餐名称' },
{ key: 'duration', value: '时长' }
],
gridOption: {
param: {
seachOption: {
key: 'name',
value: '',
is_active: null
},
pageOption: {
page: 1,
pageSize: 20
}
},
data: [],
rules: rules
},
listColumns: [
{ title: 'ID', key: 'id', minWidth: 60 },
{ title: '套餐名称', key: 'name', minWidth: 120 },
{ title: '时长', key: 'duration', minWidth: 100 },
{ title: '天数', key: 'days', minWidth: 80 },
{
title: '价格',
key: 'price',
minWidth: 100,
render: (h, params) => {
return h('span', `¥${params.row.price}`)
}
},
{
title: '原价',
key: 'original_price',
minWidth: 100,
render: (h, params) => {
return h('span', params.row.original_price ? `¥${params.row.original_price}` : '-')
}
},
{ title: '折扣', key: 'discount', minWidth: 100 },
{
title: '推荐',
key: 'featured',
minWidth: 80,
render: (h, params) => {
return h('Tag', {
props: { color: params.row.featured === 1 ? 'warning' : 'default' }
}, params.row.featured === 1 ? '推荐' : '普通')
}
},
{
title: '状态',
key: 'is_active',
minWidth: 80,
render: (h, params) => {
const status = params.row.is_active === 1
return h('Tag', {
props: { color: status ? 'success' : 'default' }
}, status ? '启用' : '禁用')
}
},
{ title: '排序', key: 'sort_order', minWidth: 80 },
{
title: '操作',
key: 'action',
width: 200,
align: 'center',
render: (h, params) => {
return h('div', [
h('Button', {
props: {
type: 'primary',
size: 'small'
},
style: {
marginRight: '5px'
},
on: {
click: () => {
this.edit(params.row)
}
}
}, '编辑'),
h('Button', {
props: {
type: 'error',
size: 'small'
},
on: {
click: () => {
this.del(params.row)
}
}
}, '删除')
])
}
}
],
editColumns: [
{
title: '套餐名称',
key: 'name',
type: 'input',
required: true
},
{
title: '时长描述',
key: 'duration',
type: 'input',
required: true,
placeholder: '如7天、30天、永久'
},
{
title: '天数',
key: 'days',
type: 'number',
required: true,
tooltip: '-1表示永久0表示无限制'
},
{
title: '价格',
key: 'price',
type: 'number',
required: true
},
{
title: '原价',
key: 'original_price',
type: 'number',
required: false,
placeholder: '可留空,表示无原价'
},
{
title: '单位',
key: 'unit',
type: 'input',
required: false,
defaultValue: '元'
},
{
title: '折扣描述',
key: 'discount',
type: 'input',
required: false,
placeholder: '如8.3折、超值'
},
{
title: '功能特性',
key: 'features',
com: 'TextArea',
required: false,
placeholder: '请输入JSON数组格式例如["功能1", "功能2", "功能3"]',
tooltip: '功能特性列表JSON数组格式'
},
{
title: '是否推荐',
key: 'featured',
type: 'select',
required: true,
options: [
{ value: 1, label: '推荐' },
{ value: 0, label: '普通' }
]
},
{
title: '是否启用',
key: 'is_active',
type: 'select',
required: true,
options: [
{ value: 1, label: '启用' },
{ value: 0, label: '禁用' }
]
},
{
title: '排序顺序',
key: 'sort_order',
type: 'number',
required: false,
defaultValue: 0,
tooltip: '数字越小越靠前'
}
]
}
},
computed: {
seachTypePlaceholder() {
const item = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
return item ? `请输入${item.value}` : '请选择'
}
},
mounted() {
this.query(1)
},
methods: {
query(page) {
if (page) {
this.gridOption.param.pageOption.page = page
}
const param = {
pageOption: this.gridOption.param.pageOption,
seachOption: {}
}
if (this.gridOption.param.seachOption.key && this.gridOption.param.seachOption.value) {
param.seachOption[this.gridOption.param.seachOption.key] = this.gridOption.param.seachOption.value
}
if (this.gridOption.param.seachOption.is_active !== null) {
param.seachOption.is_active = this.gridOption.param.seachOption.is_active
}
pricingPlansServer.page(param).then(res => {
if (res.code === 0) {
const data = res.data
this.gridOption.data = data.rows
this.gridOption.param.pageOption.total = data.count || data.total || 0
} else {
this.$Message.error(res.message || '查询失败')
}
}).catch(err => {
this.$Message.error('查询失败:' + (err.message || '未知错误'))
})
},
resetQuery() {
this.gridOption.param.seachOption = {
key: 'name',
value: '',
is_active: null
}
this.query(1)
},
showAddWarp() {
this.$refs.editModal.show({
name: '',
duration: '',
days: 0,
price: 0,
original_price: null,
unit: '元',
discount: '',
features: '[]',
featured: 0,
is_active: 1,
sort_order: 0
})
},
edit(row) {
// 解析 JSON 字段
let features = row.features || '[]'
// 如果是字符串,尝试解析并格式化
if (typeof features === 'string') {
try {
const parsed = JSON.parse(features)
features = JSON.stringify(parsed, null, 2)
} catch (e) {
// 保持原样
}
} else {
features = JSON.stringify(features, null, 2)
}
this.$refs.editModal.editShow({
id: row.id,
name: row.name,
duration: row.duration || '',
days: row.days,
price: row.price,
original_price: row.original_price,
unit: row.unit || '元',
discount: row.discount || '',
features: features,
featured: row.featured,
is_active: row.is_active,
sort_order: row.sort_order || 0
})
},
del(row) {
this.$Modal.confirm({
title: '确认删除',
content: `确定要删除价格套餐"${row.name}"吗?`,
onOk: () => {
pricingPlansServer.del(row).then(res => {
if (res.code === 0) {
this.$Message.success('删除成功')
this.query(this.gridOption.param.pageOption.page)
} else {
this.$Message.error(res.message || '删除失败')
}
}).catch(err => {
this.$Message.error('删除失败:' + (err.message || '未知错误'))
})
}
})
},
handleSaveSuccess(data) {
// 处理 JSON 字段
const formData = { ...data }
// 处理 features
if (formData.features) {
try {
const parsed = typeof formData.features === 'string'
? JSON.parse(formData.features)
: formData.features
if (!Array.isArray(parsed)) {
this.$Message.warning('功能特性必须是数组格式,将使用空数组')
formData.features = []
} else {
formData.features = parsed
}
} catch (e) {
this.$Message.warning('功能特性格式错误,将使用空数组')
formData.features = []
}
}
const apiMethod = formData.id ? pricingPlansServer.update : pricingPlansServer.add
apiMethod(formData).then(res => {
if (res.code === 0) {
this.$Message.success(formData.id ? '更新成功' : '添加成功')
this.$refs.editModal.hide()
this.query(this.gridOption.param.pageOption.page)
} else {
this.$Message.error(res.message || (formData.id ? '更新失败' : '添加失败'))
}
}).catch(err => {
this.$Message.error((formData.id ? '更新失败' : '添加失败') + '' + (err.message || '未知错误'))
})
}
}
}
</script>
<style scoped>
.content-view {
padding: 16px;
}
</style>