feat: 添加产品管理功能,包括产品列表和添加产品表单

This commit is contained in:
2026-01-19 17:25:48 +07:00
parent 724f0a47e9
commit 9b36a114b3
11 changed files with 310 additions and 17 deletions

View File

@@ -0,0 +1,146 @@
<script lang="ts" setup>
import { ref, useTemplateRef } from 'vue';
import type { FormInst, FormRules } from 'naive-ui';
import { client, safeClient } from '@/service/api';
type Body = CommonType.TreatyBody<typeof client.api.admin.subscription.products.post>;
defineOptions({
name: 'ProductAdd'
});
const emit = defineEmits<{
(e: 'close'): void;
}>();
const formInst = useTemplateRef<FormInst>('formInst');
const form = ref<Body>({
name: '',
price: '',
cycleDays: 1,
maturityYield: '',
subscribeStartAt: new Date(),
subscribeEndAt: new Date(),
description: '',
isActive: true,
reformPioneerAllowance: '',
sortOrder: 0
});
const rules: FormRules = {
name: [{ required: true, message: '请输入产品名称', trigger: ['blur', 'input'] }],
price: [{ required: true, message: '请输入产品价格', trigger: ['blur', 'change'] }],
maturityYield: [{ required: true, message: '请输入到期收益率', trigger: ['blur', 'change'] }],
subscribeStartAt: [{ required: true, message: '请选择订阅时间范围', trigger: ['blur', 'change'] }]
};
function handleSubmit() {
formInst.value?.validate(async errors => {
if (!errors) {
await safeClient(() => client.api.admin.subscription.products.post(form.value));
emit('close');
}
});
}
</script>
<template>
<div class="my-10">
<NForm
ref="formInst"
:model="form"
label-width="140px"
label-placement="left"
:rules="rules"
require-mark-placement="left"
>
<NFormItem path="name" label="产品名称">
<NInput v-model:value="form.name" placeholder="请输入产品名称" />
</NFormItem>
<NGrid :cols="2" :x-gap="12">
<NFormItemGi path="price" label="产品价格">
<NInputNumber
:value="Number(form.price)"
:min="0"
:precision="2"
class="w-full"
@update:value="val => (form.price = String(val))"
>
<template #suffix></template>
</NInputNumber>
</NFormItemGi>
<NFormItemGi path="cycleDays" label="周期天数">
<NInputNumber v-model:value="form.cycleDays" :min="1" :precision="0" class="w-full">
<template #suffix></template>
</NInputNumber>
</NFormItemGi>
</NGrid>
<NGrid :cols="2" :x-gap="12">
<NFormItemGi path="maturityYield" label="到期收益率">
<NInputNumber
:value="Number(form.maturityYield)"
:min="0"
:max="100"
:step="0.01"
class="w-full"
@update:value="val => (form.maturityYield = String(val))"
>
<template #suffix>%</template>
</NInputNumber>
</NFormItemGi>
<NFormItemGi path="reformPioneerAllowance" label="改革先锋津贴">
<NInputNumber
:value="Number(form.reformPioneerAllowance)"
:min="0"
:precision="2"
class="w-full"
@update:value="val => (form.reformPioneerAllowance = String(val))"
>
<template #suffix></template>
</NInputNumber>
</NFormItemGi>
</NGrid>
<NFormItem label="订阅时间范围">
<NDatePicker
:value="[form.subscribeStartAt.valueOf(), form.subscribeEndAt.valueOf()]"
type="datetimerange"
clearable
format="yyyy-MM-dd HH:mm:ss"
class="w-full"
@update:value="
val => {
form.subscribeStartAt = new Date(val[0]);
form.subscribeEndAt = new Date(val[1]);
}
"
/>
</NFormItem>
<NFormItem path="description" label="产品描述">
<NInput v-model:value="form.description" type="textarea" placeholder="请输入产品描述" :rows="3" />
</NFormItem>
<NGrid :cols="2" :x-gap="12">
<NFormItemGi path="isActive" label="是否启用">
<NSwitch v-model:value="form.isActive" />
</NFormItemGi>
<NFormItemGi path="sortOrder" label="排序顺序">
<NInputNumber v-model:value="form.sortOrder" :min="0" :precision="0" class="w-full" />
</NFormItemGi>
</NGrid>
<NSpace justify="end">
<NButton type="primary" ghost @click="$emit('close')"> </NButton>
<NButton type="primary" @click="handleSubmit"> </NButton>
</NSpace>
</NForm>
</div>
</template>
<style lang="css" scoped></style>

131
src/views/product/index.vue Normal file
View File

@@ -0,0 +1,131 @@
<script lang="ts" setup>
import { h, useTemplateRef } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import dayjs from 'dayjs';
import { client, safeClient } from '@/service/api';
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
import Add from './components/add.vue';
const message = useMessage();
const dialog = useDialog();
const tableInst = useTemplateRef<TableInst>('tableInst');
const fetchData: TableFetchData = ({ pagination, filter }) => {
return safeClient(() =>
client.api.admin.subscription.products.get({
query: {
...pagination,
...filter
}
})
);
};
const columns: TableBaseColumns = [
{
key: 'id',
title: 'ID',
width: 120
},
{
key: 'name',
title: '名称'
},
{
key: 'price',
title: '价格(元)',
render(row) {
return Number(row.price);
}
},
{
key: 'cycleDays',
title: '周期(天数)'
},
{
key: 'maturityYield',
title: '到期收益率',
render(row) {
return `${Number(row.maturityYield)}%`;
}
},
{
key: 'reformPioneerAllowance',
title: '改革先锋津贴(元)',
render(row) {
return Number(row.reformPioneerAllowance);
}
},
{
key: 'subscribeStartAt',
title: '订阅开始时间',
render(row: any) {
return dayjs(row.subscribeStartAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'subscribeEndAt',
title: '订阅结束时间',
render(row: any) {
return dayjs(row.subscribeEndAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'description',
title: '描述'
},
{
key: 'isActive',
title: '是否激活',
render(row) {
return row.isActive ? '是' : '否';
}
},
{
key: 'sortOrder',
title: '排序'
},
{
key: 'createdAt',
title: '创建时间',
render(row: any) {
return dayjs(row.createdAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'operations',
title: '操作',
width: 120,
fixed: 'right',
operations: row => [
{
contentText: '编辑',
size: 'small',
onClick() {}
}
]
}
];
function handleAdd() {
const d = dialog.create({
title: '添加产品',
showIcon: false,
content: () =>
h(Add, {
onClose: () => {
d.destroy();
tableInst.value?.reload();
}
}),
style: { width: '800px' }
});
}
</script>
<template>
<TableBase ref="tableInst" :fetch-data="fetchData" :columns="columns" @add="handleAdd" />
</template>
<style lang="css" scoped></style>