mirror of
https://github.com/certd/certd.git
synced 2026-07-05 19:37:34 +08:00
feat: 新增推广等级激励功能
This commit is contained in:
@@ -47,3 +47,11 @@ export async function ApproveWithdraw(id: number, remark?: string) {
|
||||
export async function RejectWithdraw(id: number, remark: string) {
|
||||
return await request({ url: "/sys/wallet/withdraw/reject", method: "post", data: { id, remark } });
|
||||
}
|
||||
|
||||
export async function GetSimpleUserByIds(ids: number[]) {
|
||||
return await request({
|
||||
url: "/sys/authority/user/getSimpleUserByIds",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notification } from "ant-design-vue";
|
||||
import * as api from "./api";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
@@ -11,6 +12,13 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
const userDict = dict({
|
||||
async getNodesByValues(ids: number[]) {
|
||||
return await api.GetSimpleUserByIds(ids);
|
||||
},
|
||||
value: "id",
|
||||
label: "nickName",
|
||||
});
|
||||
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetUserLevels(query);
|
||||
@@ -65,15 +73,20 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
userId: { title: "用户ID", type: "number", search: { show: true }, column: { width: 100 } },
|
||||
username: {
|
||||
title: "用户名",
|
||||
type: "text",
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
search: { show: true },
|
||||
column: {
|
||||
width: 180,
|
||||
cellRender({ row }) {
|
||||
return row.simpleUser?.displayName || row.userDisplay || row.username || row.userId;
|
||||
dict: userDict,
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: "点击选择用户",
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as api from "./api";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
|
||||
|
||||
function buildPrivateFileUrl(key: string) {
|
||||
const userStore = useUserStore();
|
||||
@@ -12,6 +13,13 @@ function buildPrivateFileUrl(key: string) {
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
const userDict = dict({
|
||||
async getNodesByValues(ids: number[]) {
|
||||
return await api.GetSimpleUserByIds(ids);
|
||||
},
|
||||
value: "id",
|
||||
label: "nickName",
|
||||
});
|
||||
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetWithdraws(query);
|
||||
@@ -25,7 +33,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
<span class={"text-red-500"}>{row.amount / 100} 元</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="渠道类型">{row.channel === "bank" ? "银行卡" : "支付宝"}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户名">{row.userDisplay || row.userId}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户ID">{row.userId}</a-descriptions-item>
|
||||
<a-descriptions-item label="账号">{row.account || "-"}</a-descriptions-item>
|
||||
{isBank ? <a-descriptions-item label="开户行名称">{row.bankName || "-"}</a-descriptions-item> : null}
|
||||
{!isBank ? <a-descriptions-item label="收款二维码">{row.qrCode ? <a-image src={buildPrivateFileUrl(row.qrCode)} width={160} /> : <span>-</span>}</a-descriptions-item> : null}
|
||||
@@ -122,7 +130,24 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
userId: { title: "用户ID", type: "number", search: { show: true }, column: { width: 100 } },
|
||||
createTime: { title: "申请时间", type: "datetime", column: { width: 180 } },
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
search: { show: true },
|
||||
dict: userDict,
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: "点击选择用户",
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
amount: {
|
||||
title: "金额",
|
||||
type: "number",
|
||||
@@ -131,7 +156,6 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||
},
|
||||
},
|
||||
userDisplay: { title: "用户名", type: "text", search: { show: true }, column: { width: 140 } },
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
@@ -173,7 +197,6 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
},
|
||||
},
|
||||
auditRemark: { title: "审核备注", type: "text", column: { minWidth: 180 } },
|
||||
createTime: { title: "申请时间", type: "datetime", column: { width: 180 } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<a-empty v-if="levelList.length === 0" class="level-empty" />
|
||||
<div v-else class="level-card-grid">
|
||||
<div v-for="(item, index) of levelList" :key="item.id" class="level-card" :class="{ disabled: item.disabled }">
|
||||
<div v-for="(item, index) of levelList" :key="item.id" class="level-card" :class="{ disabled: item.disabled, exclusive: item.levelType === 'exclusive' }">
|
||||
<div class="level-card-actions">
|
||||
<a-tooltip title="编辑">
|
||||
<a-button type="text" size="small" @click="openEdit({ index, row: item })">
|
||||
@@ -153,6 +153,17 @@ onActivated(() => {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.level-card.exclusive {
|
||||
border-color: rgba(147, 51, 234, 0.34);
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(250, 245, 255, 0.86)), linear-gradient(135deg, rgba(147, 51, 234, 0.24), rgba(245, 158, 11, 0.2));
|
||||
box-shadow: 0 12px 32px rgba(88, 28, 135, 0.14);
|
||||
}
|
||||
|
||||
.level-card.exclusive:hover {
|
||||
border-color: rgba(147, 51, 234, 0.52);
|
||||
box-shadow: 0 18px 42px rgba(88, 28, 135, 0.2);
|
||||
}
|
||||
|
||||
.level-card.disabled {
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
<a-form-item label="开启激励计划" name="enabled">
|
||||
<a-switch v-model:checked="settings.enabled" />
|
||||
</a-form-item>
|
||||
<a-form-item label="启用推广等级" name="levelEnabled">
|
||||
<a-space>
|
||||
<a-switch v-model:checked="settings.levelEnabled" />
|
||||
<a-button v-if="levelEnabled" type="link" @click="gotoInviteLevel">推广等级设置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!settings.levelEnabled" label="佣金比例" name="fixedCommissionRate">
|
||||
<a-input-number v-model:value="settings.fixedCommissionRate" :min="0" :max="100" addon-after="%" />
|
||||
</a-form-item>
|
||||
<a-form-item label="最低提现金额" name="minWithdrawAmountYuan">
|
||||
<a-input-number v-model:value="settings.minWithdrawAmountYuan" :min="0" addon-after="元" />
|
||||
</a-form-item>
|
||||
@@ -38,13 +47,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import * as api from "./api";
|
||||
import { util } from "/@/utils";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useAccessStore } from "/@/vben/stores";
|
||||
import { frameworkRoutes } from "/@/router/resolve";
|
||||
import { generateMenus } from "/@/vben/utils";
|
||||
import utilPermission from "/@/plugin/permission/util.permission";
|
||||
|
||||
defineOptions({ name: "SysInviteCommissionSetting" });
|
||||
|
||||
const router = useRouter();
|
||||
const defaultAgreement = "<p>请遵守平台推广规则,不得通过虚假注册、刷单、恶意诱导等方式获取收益。平台有权对异常推广行为进行核查,并根据实际情况暂停结算或关闭激励计划资格。</p>";
|
||||
const defaultWithdrawBanks = [
|
||||
"中国工商银行",
|
||||
@@ -63,13 +78,22 @@ const defaultWithdrawBanks = [
|
||||
"兴业银行",
|
||||
"浦发银行",
|
||||
];
|
||||
const settings = reactive<any>({ enabled: false, agreementContent: "", minWithdrawAmountYuan: 0, withdrawChannels: ["alipay", "bank"], withdrawBanks: defaultWithdrawBanks });
|
||||
const settings = reactive<any>({
|
||||
enabled: false,
|
||||
levelEnabled: false,
|
||||
fixedCommissionRate: 10,
|
||||
agreementContent: "",
|
||||
minWithdrawAmountYuan: 0,
|
||||
withdrawChannels: ["alipay", "bank"],
|
||||
withdrawBanks: defaultWithdrawBanks,
|
||||
});
|
||||
const withdrawChannelOptions = [
|
||||
{ label: "支付宝", value: "alipay" },
|
||||
{ label: "银行卡", value: "bank" },
|
||||
];
|
||||
const bankOptions = computed(() => defaultWithdrawBanks.map(item => ({ label: item, value: item })));
|
||||
const bankChannelEnabled = computed(() => settings.withdrawChannels?.includes("bank"));
|
||||
const levelEnabled = computed(() => settings.levelEnabled === true);
|
||||
const userStore = useUserStore();
|
||||
const editorUploader = {
|
||||
type: "form",
|
||||
@@ -89,6 +113,8 @@ const editorUploader = {
|
||||
async function loadSettings() {
|
||||
const data: any = await api.GetSettings();
|
||||
settings.enabled = !!data?.enabled;
|
||||
settings.levelEnabled = data?.levelEnabled === true;
|
||||
settings.fixedCommissionRate = Number(data?.fixedCommissionRate) || 10;
|
||||
settings.agreementContent = data?.agreementContent || defaultAgreement;
|
||||
settings.minWithdrawAmountYuan = util.amount.toYuan(data?.minWithdrawAmount || 0);
|
||||
settings.withdrawChannels = data?.withdrawChannels?.length ? data.withdrawChannels : ["alipay", "bank"];
|
||||
@@ -101,17 +127,57 @@ async function saveSettings() {
|
||||
notification.warning({ message: "请填写推广协议内容" });
|
||||
return;
|
||||
}
|
||||
if (!levelEnabled.value && (!settings.fixedCommissionRate || settings.fixedCommissionRate <= 0)) {
|
||||
notification.warning({ message: "关闭推广等级时,请设置佣金比例" });
|
||||
return;
|
||||
}
|
||||
await api.SaveSettings({
|
||||
enabled: settings.enabled,
|
||||
levelEnabled: levelEnabled.value,
|
||||
fixedCommissionRate: settings.fixedCommissionRate || 0,
|
||||
agreementContent: settings.agreementContent || "",
|
||||
minWithdrawAmount: util.amount.toCent(settings.minWithdrawAmountYuan || 0),
|
||||
withdrawChannels: settings.withdrawChannels || [],
|
||||
withdrawBanks,
|
||||
});
|
||||
await useSettingStore().loadSysSettings();
|
||||
await refreshMenus();
|
||||
notification.success({ message: "保存成功" });
|
||||
}
|
||||
|
||||
function gotoInviteLevel() {
|
||||
router.push({ path: "/sys/suite/invite/level" });
|
||||
}
|
||||
|
||||
async function refreshMenus() {
|
||||
const accessStore = useAccessStore();
|
||||
const settingStore = useSettingStore();
|
||||
let allMenus = await generateMenus(frameworkRoutes[0].children, router);
|
||||
allMenus = allMenus.concat(settingStore.getHeaderMenus);
|
||||
allMenus = buildAccessedMenus(allMenus);
|
||||
accessStore.setAccessMenus(allMenus);
|
||||
}
|
||||
|
||||
function buildAccessedMenus(menus: any) {
|
||||
if (menus == null) {
|
||||
return [];
|
||||
}
|
||||
const list: any = [];
|
||||
for (const sub of menus) {
|
||||
if (sub.meta?.permission != null && !utilPermission.hasPermissions(sub.meta.permission)) {
|
||||
continue;
|
||||
}
|
||||
const item: any = {
|
||||
...sub,
|
||||
};
|
||||
list.push(item);
|
||||
if (sub.children && sub.children.length > 0) {
|
||||
item.children = buildAccessedMenus(sub.children);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function isBlankAgreement(content: string) {
|
||||
const text = `${content || ""}`
|
||||
.replace(/<[^>]*>/g, "")
|
||||
|
||||
Reference in New Issue
Block a user