feat: 添加推广管理功能,包含推广配置页面及相关表单

This commit is contained in:
2026-01-20 03:14:11 +07:00
parent 0fc54e0f7a
commit 299ea15a88
8 changed files with 198 additions and 2 deletions

View File

@@ -235,7 +235,8 @@ const local: App.I18n.Schema = {
withdraw: 'Withdraw', withdraw: 'Withdraw',
wallet: 'Wallet', wallet: 'Wallet',
kyc: 'KYC', kyc: 'KYC',
check: 'CheckIn' check: 'CheckIn',
referral: 'Referral'
}, },
page: { page: {
login: { login: {

View File

@@ -231,7 +231,8 @@ const local: App.I18n.Schema = {
withdraw: '提现管理', withdraw: '提现管理',
wallet: '钱包管理', wallet: '钱包管理',
kyc: '实名管理', kyc: '实名管理',
check: '签到管理' check: '签到管理',
referral: '推广管理'
}, },
page: { page: {
login: { login: {

View File

@@ -25,6 +25,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
kyc: () => import("@/views/kyc/index.vue"), kyc: () => import("@/views/kyc/index.vue"),
news: () => import("@/views/news/index.vue"), news: () => import("@/views/news/index.vue"),
product: () => import("@/views/product/index.vue"), product: () => import("@/views/product/index.vue"),
referral: () => import("@/views/referral/index.vue"),
user: () => import("@/views/user/index.vue"), user: () => import("@/views/user/index.vue"),
wallet: () => import("@/views/wallet/index.vue"), wallet: () => import("@/views/wallet/index.vue"),
withdraw: () => import("@/views/withdraw/index.vue"), withdraw: () => import("@/views/withdraw/index.vue"),

View File

@@ -109,6 +109,15 @@ export const generatedRoutes: GeneratedRoute[] = [
i18nKey: 'route.product' i18nKey: 'route.product'
} }
}, },
{
name: 'referral',
path: '/referral',
component: 'layout.base$view.referral',
meta: {
title: 'referral',
i18nKey: 'route.referral'
}
},
{ {
name: 'user', name: 'user',
path: '/user', path: '/user',

View File

@@ -173,6 +173,7 @@ const routeMap: RouteMap = {
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"news": "/news", "news": "/news",
"product": "/product", "product": "/product",
"referral": "/referral",
"user": "/user", "user": "/user",
"wallet": "/wallet", "wallet": "/wallet",
"withdraw": "/withdraw" "withdraw": "/withdraw"

View File

@@ -27,6 +27,7 @@ declare module "@elegant-router/types" {
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"news": "/news"; "news": "/news";
"product": "/product"; "product": "/product";
"referral": "/referral";
"user": "/user"; "user": "/user";
"wallet": "/wallet"; "wallet": "/wallet";
"withdraw": "/withdraw"; "withdraw": "/withdraw";
@@ -71,6 +72,7 @@ declare module "@elegant-router/types" {
| "login" | "login"
| "news" | "news"
| "product" | "product"
| "referral"
| "user" | "user"
| "wallet" | "wallet"
| "withdraw" | "withdraw"
@@ -100,6 +102,7 @@ declare module "@elegant-router/types" {
| "kyc" | "kyc"
| "news" | "news"
| "product" | "product"
| "referral"
| "user" | "user"
| "wallet" | "wallet"
| "withdraw" | "withdraw"

View File

@@ -0,0 +1,95 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { FormInst, FormRules } from 'naive-ui';
import { client, safeClient } from '@/service/api';
defineOptions({
name: 'ReferralEdit'
});
interface Props {
data: any;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'close'): void;
}>();
interface ReferralForm {
depth: number;
commissionRate: number;
}
const formRef = ref<FormInst | null>(null);
const loading = ref(false);
const formModel = ref<ReferralForm>({
depth: 1,
commissionRate: 0
});
const rules: FormRules = {
depth: [{ required: true, type: 'number', message: '请输入团队层级', trigger: 'blur' }],
commissionRate: [{ required: true, type: 'number', message: '请输入佣金比例', trigger: 'blur' }]
};
async function handleSubmit() {
await formRef.value?.validate();
loading.value = true;
try {
const result = await safeClient(() =>
client.api.admin.referrals['depth-configs']({ id: props.data.id }).patch({
depth: formModel.value.depth,
commissionRate: String(formModel.value.commissionRate / 100)
})
);
if (result.data) {
window.$message?.success('更新成功');
emit('close');
}
} finally {
loading.value = false;
}
}
onMounted(() => {
formModel.value = {
depth: props.data.depth || 1,
commissionRate: (Number(props.data.commissionRate) || 0) * 100
};
});
</script>
<template>
<div>
<NForm ref="formRef" :model="formModel" :rules="rules" label-placement="left" :label-width="100">
<NFormItem label="团队层级" path="depth">
<NInputNumber v-model:value="formModel.depth" placeholder="请输入团队层级" class="w-full" :min="1" disabled />
</NFormItem>
<NFormItem label="佣金比例" path="commissionRate">
<NInputNumber
v-model:value="formModel.commissionRate"
placeholder="请输入佣金比例"
class="w-full"
:min="0"
:max="100"
:step="0.01"
>
<template #suffix>%</template>
</NInputNumber>
</NFormItem>
</NForm>
<div class="mt-16px flex justify-end gap-12px">
<NButton @click="emit('close')">取消</NButton>
<NButton type="primary" :loading="loading" @click="handleSubmit">确定</NButton>
</div>
</div>
</template>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,85 @@
<script lang="ts" setup>
import { h, useTemplateRef } from 'vue';
import { useDialog } from 'naive-ui';
import dayjs from 'dayjs';
import { client, safeClient } from '@/service/api';
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
import Edit from './components/edit.vue';
defineOptions({
name: 'ReferralPage'
});
const dialog = useDialog();
const tableInst = useTemplateRef<TableInst>('tableInst');
const fetchData: TableFetchData = ({ pagination, filter }) => {
return safeClient(() => client.api.admin.referrals['depth-configs'].get({ query: { ...pagination, ...filter } }));
};
const columns: TableBaseColumns = [
{
key: 'depth',
title: '团队层级'
},
{
key: 'commissionRate',
title: '佣金比例(%)',
render: (row: any) => {
// 1 => 100%
return `${Number(row.commissionRate) * 100} %`;
}
},
{
key: 'createdAt',
title: '创建时间',
render: (row: any) => {
return dayjs(row.createdAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'operations',
title: '操作',
width: 150,
fixed: 'right',
operations: (row: any) => [
{
contentText: '编辑',
size: 'small',
onClick: () => {
handleEdit(row);
}
}
]
}
];
function handleEdit(row: any) {
const d = dialog.create({
title: '编辑推广配置',
showIcon: false,
style: { width: '600px' },
content: () =>
h(Edit, {
data: row,
onClose: () => {
d.destroy();
tableInst.value?.reload();
}
})
});
}
</script>
<template>
<TableBase
ref="tableInst"
:fetch-data="fetchData"
:columns="columns"
:scroll-x="800"
:show-header-operation="false"
/>
</template>
<style lang="css" scoped></style>