mirror of
https://github.com/certd/certd.git
synced 2026-05-18 22:57:31 +08:00
feat: 商业版支持邀请返佣功能
This commit is contained in:
@@ -35,6 +35,8 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
- `packages/ui/certd-server/`:后端服务
|
- `packages/ui/certd-server/`:后端服务
|
||||||
- `packages/ui/certd-client/`:前端 Web 管理台
|
- `packages/ui/certd-client/`:前端 Web 管理台
|
||||||
|
|
||||||
|
`packages/pro/` 是独立 Git 工作区,使用 `packages/pro/.git` 管理。根仓库的 `git status` / `git diff` 默认看不到这里的实际改动;修改商业版代码后,要在 `packages/pro` 目录内单独执行 `git status` / `git diff` 检查。
|
||||||
|
|
||||||
## 后端
|
## 后端
|
||||||
|
|
||||||
主要后端包:`packages/ui/certd-server`。
|
主要后端包:`packages/ui/certd-server`。
|
||||||
@@ -108,6 +110,14 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
||||||
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
||||||
|
|
||||||
|
前端列表管理页面约定:
|
||||||
|
|
||||||
|
- 列表管理、后台管理、记录查询、CRUD 表格类页面,默认优先使用 Fast Crud(`@fast-crud/fast-crud`、`fs-crud`、`useFs`、`createCrudOptions`)实现。
|
||||||
|
- 只有轻量只读展示、强交互自定义界面或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
|
||||||
|
- 开发或重构这类页面前,先读取 `.trae/skills/fast-crud-page-dev/SKILL.md`,按仓库内 Fast Crud 页面拆分与验证方式实现。
|
||||||
|
- 前端对话框里只做纯确认时可以使用 `Modal.confirm`;只要对话框里有字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm` 的 `content` 里手写输入框。
|
||||||
|
- 页面内嵌 Fast Crud 表格时,要显式给外层容器稳定高度或 `flex: 1; min-height: 0` 的撑满链路;Fast Crud 依赖外部元素高度,不能只依赖表格默认高度。
|
||||||
|
|
||||||
## 流水线与插件模型
|
## 流水线与插件模型
|
||||||
|
|
||||||
项目最关键的架构概念是证书流水线。
|
项目最关键的架构概念是证书流水线。
|
||||||
@@ -168,6 +178,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
||||||
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
||||||
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
||||||
|
- 代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
|
||||||
|
|
||||||
## 插件开发技能
|
## 插件开发技能
|
||||||
|
|
||||||
@@ -182,6 +193,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
|
|
||||||
- `access-plugin-dev`:开发 Access 授权插件
|
- `access-plugin-dev`:开发 Access 授权插件
|
||||||
- `dns-provider-dev`:开发 DNS Provider 插件
|
- `dns-provider-dev`:开发 DNS Provider 插件
|
||||||
|
- `fast-crud-page-dev`:开发或重构前端 Fast Crud 列表管理页面
|
||||||
- `task-plugin-dev`:开发 Task 部署任务插件
|
- `task-plugin-dev`:开发 Task 部署任务插件
|
||||||
- `plugin-converter`:将插件转换为 YAML 配置
|
- `plugin-converter`:将插件转换为 YAML 配置
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export default {
|
|||||||
mySuite: "我的套餐",
|
mySuite: "我的套餐",
|
||||||
suiteBuy: "套餐购买",
|
suiteBuy: "套餐购买",
|
||||||
myTrade: "我的订单",
|
myTrade: "我的订单",
|
||||||
|
myWallet: "我的钱包",
|
||||||
|
inviteCommission: "邀请返佣",
|
||||||
paymentReturn: "支付返回",
|
paymentReturn: "支付返回",
|
||||||
source: "源码",
|
source: "源码",
|
||||||
github: "github",
|
github: "github",
|
||||||
@@ -46,6 +48,8 @@ export default {
|
|||||||
suiteSetting: "套餐设置",
|
suiteSetting: "套餐设置",
|
||||||
orderManager: "订单管理",
|
orderManager: "订单管理",
|
||||||
userSuites: "用户套餐",
|
userSuites: "用户套餐",
|
||||||
|
inviteCommissionSetting: "邀请返佣设置",
|
||||||
|
inviteWithdraw: "提现申请记录",
|
||||||
netTest: "网络测试",
|
netTest: "网络测试",
|
||||||
enterpriseManager: "企业管理设置",
|
enterpriseManager: "企业管理设置",
|
||||||
projectManager: "项目管理",
|
projectManager: "项目管理",
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import plugin from "./plugin/";
|
|||||||
import { setupVben } from "./vben";
|
import { setupVben } from "./vben";
|
||||||
import { util } from "/@/utils";
|
import { util } from "/@/utils";
|
||||||
import { initPreferences } from "/@/vben/preferences";
|
import { initPreferences } from "/@/vben/preferences";
|
||||||
|
import { inviteUtils } from "/@/utils/util.invite";
|
||||||
// import "./components/code-editor/import-works";
|
// import "./components/code-editor/import-works";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
inviteUtils.captureFromLocation();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
// app.use(Antd);
|
// app.use(Antd);
|
||||||
app.use(Antd);
|
app.use(Antd);
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export const certdResources = [
|
|||||||
meta: {
|
meta: {
|
||||||
show: () => {
|
show: () => {
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
return settingStore.isComm;
|
return settingStore.isInviteCommissionEnabled;
|
||||||
},
|
},
|
||||||
icon: "ion:gift-outline",
|
icon: "ion:gift-outline",
|
||||||
auth: true,
|
auth: true,
|
||||||
@@ -359,6 +359,36 @@ export const certdResources = [
|
|||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "certd.myWallet",
|
||||||
|
name: "MyWallet",
|
||||||
|
path: "/certd/wallet",
|
||||||
|
component: "/certd/wallet/index.vue",
|
||||||
|
meta: {
|
||||||
|
show: () => {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
return settingStore.isComm;
|
||||||
|
},
|
||||||
|
icon: "ion:wallet-outline",
|
||||||
|
auth: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "certd.inviteCommission",
|
||||||
|
name: "InviteCommission",
|
||||||
|
path: "/certd/invite",
|
||||||
|
component: "/certd/invite/index.vue",
|
||||||
|
meta: {
|
||||||
|
show: () => {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
return settingStore.isComm;
|
||||||
|
},
|
||||||
|
icon: "ion:gift-outline",
|
||||||
|
auth: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "certd.paymentReturn",
|
title: "certd.paymentReturn",
|
||||||
name: "PaymentReturn",
|
name: "PaymentReturn",
|
||||||
|
|||||||
@@ -286,6 +286,38 @@ export const sysResources = [
|
|||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "certd.sysResources.inviteCommissionSetting",
|
||||||
|
name: "SysInviteCommissionSetting",
|
||||||
|
path: "/sys/suite/invite/setting",
|
||||||
|
component: "/sys/suite/invite/setting.vue",
|
||||||
|
meta: {
|
||||||
|
show: () => {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
return settingStore.isComm;
|
||||||
|
},
|
||||||
|
icon: "ion:gift-outline",
|
||||||
|
permission: "sys:settings:edit",
|
||||||
|
auth: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "certd.sysResources.inviteWithdraw",
|
||||||
|
name: "SysInviteWithdraw",
|
||||||
|
path: "/sys/suite/invite/withdraw",
|
||||||
|
component: "/sys/suite/invite/withdraw.vue",
|
||||||
|
meta: {
|
||||||
|
show: () => {
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
return settingStore.isComm;
|
||||||
|
},
|
||||||
|
icon: "ion:cash-outline",
|
||||||
|
permission: "sys:settings:edit",
|
||||||
|
auth: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ export type SysPublicSetting = {
|
|||||||
export type SuiteSetting = {
|
export type SuiteSetting = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
export type InviteSetting = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
export type SysPrivateSetting = {
|
export type SysPrivateSetting = {
|
||||||
httpProxy?: string;
|
httpProxy?: string;
|
||||||
httpsProxy?: string;
|
httpsProxy?: string;
|
||||||
@@ -142,6 +145,7 @@ export type AllSettings = {
|
|||||||
siteEnv: SiteEnv;
|
siteEnv: SiteEnv;
|
||||||
headerMenus: HeaderMenus;
|
headerMenus: HeaderMenus;
|
||||||
suiteSetting: SuiteSetting;
|
suiteSetting: SuiteSetting;
|
||||||
|
inviteSetting: InviteSetting;
|
||||||
app: AppInfo;
|
app: AppInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export interface SettingState {
|
|||||||
headerMenus?: HeaderMenus;
|
headerMenus?: HeaderMenus;
|
||||||
inited?: boolean;
|
inited?: boolean;
|
||||||
suiteSetting?: SuiteSetting;
|
suiteSetting?: SuiteSetting;
|
||||||
|
inviteSetting?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
app: {
|
app: {
|
||||||
version?: string;
|
version?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
@@ -102,6 +105,7 @@ export const useSettingStore = defineStore({
|
|||||||
menus: [],
|
menus: [],
|
||||||
},
|
},
|
||||||
suiteSetting: { enabled: false },
|
suiteSetting: { enabled: false },
|
||||||
|
inviteSetting: { enabled: false },
|
||||||
inited: false,
|
inited: false,
|
||||||
app: {
|
app: {
|
||||||
version: "",
|
version: "",
|
||||||
@@ -196,6 +200,9 @@ export const useSettingStore = defineStore({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return this.suiteSetting?.enabled === true;
|
return this.suiteSetting?.enabled === true;
|
||||||
},
|
},
|
||||||
|
isInviteCommissionEnabled(): boolean {
|
||||||
|
return this.isComm && this.inviteSetting?.enabled === true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
checkPlus() {
|
checkPlus() {
|
||||||
@@ -215,6 +222,7 @@ export const useSettingStore = defineStore({
|
|||||||
merge(this.plusInfo, allSettings.plusInfo || {});
|
merge(this.plusInfo, allSettings.plusInfo || {});
|
||||||
merge(this.headerMenus, allSettings.headerMenus || {});
|
merge(this.headerMenus, allSettings.headerMenus || {});
|
||||||
merge(this.suiteSetting, allSettings.suiteSetting || {});
|
merge(this.suiteSetting, allSettings.suiteSetting || {});
|
||||||
|
merge(this.inviteSetting, allSettings.inviteSetting || {});
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
this.initSiteInfo(allSettings.siteInfo || {});
|
this.initSiteInfo(allSettings.siteInfo || {});
|
||||||
this.initAppInfo(allSettings.app || {});
|
this.initAppInfo(allSettings.app || {});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface RegisterReq {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
confirmPassword: string;
|
confirmPassword: string;
|
||||||
|
inviteCode?: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description: Login interface parameters
|
* @description: Login interface parameters
|
||||||
@@ -18,6 +19,7 @@ export interface SmsLoginReq {
|
|||||||
phoneCode: string;
|
phoneCode: string;
|
||||||
smsCode: string;
|
smsCode: string;
|
||||||
randomStr: string;
|
randomStr: string;
|
||||||
|
inviteCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForgotPasswordReq {
|
export interface ForgotPasswordReq {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
const INVITE_STORAGE_KEY = "certd_invite_code";
|
||||||
|
const INVITE_TTL = 3 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
export type InviteCache = {
|
||||||
|
code: string;
|
||||||
|
expiresAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeInviteCode(code?: string | null) {
|
||||||
|
return code?.trim().toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const inviteUtils = {
|
||||||
|
save(code?: string | null) {
|
||||||
|
const normalized = normalizeInviteCode(code);
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cache: InviteCache = {
|
||||||
|
code: normalized,
|
||||||
|
expiresAt: Date.now() + INVITE_TTL,
|
||||||
|
};
|
||||||
|
localStorage.setItem(INVITE_STORAGE_KEY, JSON.stringify(cache));
|
||||||
|
},
|
||||||
|
|
||||||
|
get() {
|
||||||
|
const text = localStorage.getItem(INVITE_STORAGE_KEY);
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const cache = JSON.parse(text) as InviteCache;
|
||||||
|
if (!cache.code || !cache.expiresAt || cache.expiresAt < Date.now()) {
|
||||||
|
localStorage.removeItem(INVITE_STORAGE_KEY);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return cache.code;
|
||||||
|
} catch (e) {
|
||||||
|
localStorage.removeItem(INVITE_STORAGE_KEY);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
captureFromLocation() {
|
||||||
|
const hashQuery = window.location.hash?.split("?")[1] || "";
|
||||||
|
const search = window.location.search?.replace(/^\?/, "") || "";
|
||||||
|
const hashParams = new URLSearchParams(hashQuery);
|
||||||
|
const searchParams = new URLSearchParams(search);
|
||||||
|
const code = hashParams.get("inviteCode") || searchParams.get("inviteCode");
|
||||||
|
if (code) {
|
||||||
|
this.save(code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
export async function GetMyInvite() {
|
||||||
|
return await request({ url: "/invite/my", method: "post" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetInvitees(query: any) {
|
||||||
|
return await request({ url: "/invite/invitees/page", method: "post", data: query });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetCommissionLogs(query: any) {
|
||||||
|
return await request({ url: "/invite/commission/page", method: "post", data: query });
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { CreateCrudOptionsProps, CreateCrudOptionsRet, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import * as api from "./api";
|
||||||
|
|
||||||
|
export default function (): CreateCrudOptionsRet {
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetInvitees(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: { pageRequest },
|
||||||
|
actionbar: { show: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
rowHandle: { show: false },
|
||||||
|
columns: {
|
||||||
|
inviteeUserId: {
|
||||||
|
title: "被邀请人ID",
|
||||||
|
type: "number",
|
||||||
|
column: { width: 140 },
|
||||||
|
},
|
||||||
|
inviteCode: {
|
||||||
|
title: "邀请码",
|
||||||
|
type: "text",
|
||||||
|
column: { width: 160 },
|
||||||
|
},
|
||||||
|
createTime: {
|
||||||
|
title: "邀请时间",
|
||||||
|
type: "datetime",
|
||||||
|
column: { width: 180 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import * as api from "./api";
|
||||||
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
|
|
||||||
|
export default function (): CreateCrudOptionsRet {
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetCommissionLogs(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: { pageRequest },
|
||||||
|
actionbar: { show: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
rowHandle: { show: false },
|
||||||
|
columns: {
|
||||||
|
type: {
|
||||||
|
title: "类型",
|
||||||
|
type: "dict-select",
|
||||||
|
dict: dict({
|
||||||
|
data: [{ label: "佣金入账", value: "commission", color: "success" }],
|
||||||
|
}),
|
||||||
|
column: { width: 130 },
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
title: "金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inviteeUserDisplay: {
|
||||||
|
title: "被邀请用户",
|
||||||
|
type: "text",
|
||||||
|
column: { width: 150 },
|
||||||
|
},
|
||||||
|
consumeAmount: {
|
||||||
|
title: "消费金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
remark: {
|
||||||
|
title: "备注",
|
||||||
|
type: "text",
|
||||||
|
column: { minWidth: 220 },
|
||||||
|
},
|
||||||
|
createTime: {
|
||||||
|
title: "时间",
|
||||||
|
type: "datetime",
|
||||||
|
column: { width: 180 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<fs-page class="page-invite">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">邀请返佣</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="loaded && enabled" class="invite-body">
|
||||||
|
<div class="invite-link-row flex-o">
|
||||||
|
<span class="label">邀请码:</span>
|
||||||
|
<fs-copyable v-model="inviteInfo.inviteCode" />
|
||||||
|
</div>
|
||||||
|
<div class="invite-link-row flex-o mt-10">
|
||||||
|
<span class="label">邀请链接:</span>
|
||||||
|
<fs-copyable v-model="inviteInfo.inviteLink" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-tabs v-model:active-key="activeTab" class="invite-tabs mt-6">
|
||||||
|
<a-tab-pane key="invitees" tab="邀请成功">
|
||||||
|
<fs-crud v-if="activeTab === 'invitees'" ref="inviteesCrudRef" class="invite-crud" v-bind="inviteesCrudBinding" />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="logs" tab="佣金记录">
|
||||||
|
<fs-crud v-if="activeTab === 'logs'" ref="logsCrudRef" class="invite-crud" v-bind="logsCrudBinding" />
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
<a-empty v-else-if="loaded" description="邀请返佣未开启" />
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, onActivated, onMounted, reactive, ref } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import * as api from "./api";
|
||||||
|
import createInviteesCrudOptions from "./crud-invitees";
|
||||||
|
import createLogsCrudOptions from "./crud-logs";
|
||||||
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
|
||||||
|
defineOptions({ name: "InviteCommission" });
|
||||||
|
|
||||||
|
const settingStore = useSettingStore();
|
||||||
|
const enabled = computed(() => settingStore.isInviteCommissionEnabled);
|
||||||
|
const activeTab = ref("invitees");
|
||||||
|
const inviteInfo = reactive<any>({ inviteCode: "", inviteLink: "" });
|
||||||
|
const loaded = ref(false);
|
||||||
|
|
||||||
|
const { crudBinding: inviteesCrudBinding, crudExpose: inviteesCrudExpose, crudRef: inviteesCrudRef } = useFs({ createCrudOptions: createInviteesCrudOptions });
|
||||||
|
const { crudBinding: logsCrudBinding, crudExpose: logsCrudExpose, crudRef: logsCrudRef } = useFs({ createCrudOptions: createLogsCrudOptions });
|
||||||
|
|
||||||
|
async function loadMyInvite() {
|
||||||
|
const res: any = await api.GetMyInvite();
|
||||||
|
inviteInfo.inviteCode = res.inviteCode;
|
||||||
|
inviteInfo.inviteLink = res.inviteLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshActiveList() {
|
||||||
|
if (activeTab.value === "invitees") {
|
||||||
|
await inviteesCrudExpose.doRefresh();
|
||||||
|
} else if (activeTab.value === "logs") {
|
||||||
|
await logsCrudExpose.doRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshInvitePage(refreshAll = false) {
|
||||||
|
await settingStore.initOnce();
|
||||||
|
loaded.value = true;
|
||||||
|
if (!enabled.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await loadMyInvite();
|
||||||
|
await nextTick();
|
||||||
|
if (refreshAll) {
|
||||||
|
await Promise.all([inviteesCrudExpose.doRefresh(), logsCrudExpose.doRefresh()]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await refreshActiveList();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshInvitePage(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(async () => {
|
||||||
|
if (!loaded.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await refreshInvitePage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-invite {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.fs-page-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-content-holder,
|
||||||
|
.ant-tabs-content,
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-crud {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
width: 80px;
|
||||||
|
flex: none;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -46,6 +46,7 @@ export type TradeCreateReq = {
|
|||||||
duration: number;
|
duration: number;
|
||||||
num: number;
|
num: number;
|
||||||
payType: string;
|
payType: string;
|
||||||
|
useRebateBalance?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function TradeCreate(form: TradeCreateReq) {
|
export async function TradeCreate(form: TradeCreateReq) {
|
||||||
|
|||||||
@@ -28,10 +28,19 @@
|
|||||||
<span class="label">{{ $t("certd.order.price") }}:</span>
|
<span class="label">{{ $t("certd.order.price") }}:</span>
|
||||||
<price-input :edit="false" :model-value="durationSelected.price"></price-input>
|
<price-input :edit="false" :model-value="durationSelected.price"></price-input>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="durationSelected.price > 0 && wallet.availableAmount > 0" class="flex-o mt-5">
|
||||||
|
<span class="label">返利抵扣:</span>
|
||||||
|
<a-switch v-model:checked="formRef.useRebateBalance" />
|
||||||
|
<span class="ml-10">可用 {{ amountToYuan(wallet.availableAmount) }} 元,预计抵扣 {{ amountToYuan(expectedRebateAmount) }} 元</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="durationSelected.price > 0 && formRef.useRebateBalance" class="flex-o mt-5">
|
||||||
|
<span class="label">还需支付:</span>
|
||||||
|
<span class="color-red">{{ amountToYuan(expectedThirdPartyAmount) }} 元</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex-o mt-5">
|
<div class="flex-o mt-5">
|
||||||
<span class="label">{{ $t("certd.order.paymentMethod") }}:</span>
|
<span class="label">{{ $t("certd.order.paymentMethod") }}:</span>
|
||||||
<div v-if="durationSelected.price === 0">{{ $t("certd.order.free") }}</div>
|
<div v-if="durationSelected.price === 0 || expectedThirdPartyAmount === 0">{{ $t("certd.order.free") }}</div>
|
||||||
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
|
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
|
import { GetPaymentTypes, OrderModalOpenReq, TradeCreate } from "/@/views/certd/suite/api";
|
||||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
@@ -49,11 +58,14 @@ import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import qrcode from "qrcode";
|
import qrcode from "qrcode";
|
||||||
import * as api from "/@/views/certd/suite/api";
|
import * as api from "/@/views/certd/suite/api";
|
||||||
|
import { GetMyInvite } from "/@/views/certd/invite/api";
|
||||||
|
import { util } from "/@/utils";
|
||||||
const openRef = ref(false);
|
const openRef = ref(false);
|
||||||
|
|
||||||
const product = ref<any>(null);
|
const product = ref<any>(null);
|
||||||
const formRef = ref<any>({});
|
const formRef = ref<any>({});
|
||||||
const durationSelected = ref<any>(null);
|
const durationSelected = ref<any>(null);
|
||||||
|
const wallet = ref<any>({ availableAmount: 0 });
|
||||||
async function open(opts: OrderModalOpenReq) {
|
async function open(opts: OrderModalOpenReq) {
|
||||||
openRef.value = true;
|
openRef.value = true;
|
||||||
|
|
||||||
@@ -63,6 +75,13 @@ async function open(opts: OrderModalOpenReq) {
|
|||||||
formRef.value.productId = opts.product.id;
|
formRef.value.productId = opts.product.id;
|
||||||
formRef.value.duration = opts.duration;
|
formRef.value.duration = opts.duration;
|
||||||
formRef.value.num = opts.num ?? 1;
|
formRef.value.num = opts.num ?? 1;
|
||||||
|
formRef.value.useRebateBalance = false;
|
||||||
|
try {
|
||||||
|
const inviteInfo: any = await GetMyInvite();
|
||||||
|
wallet.value = inviteInfo.wallet || { availableAmount: 0 };
|
||||||
|
} catch (e) {
|
||||||
|
wallet.value = { availableAmount: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const paymentsDictRef = dict({
|
const paymentsDictRef = dict({
|
||||||
async getData() {
|
async getData() {
|
||||||
@@ -77,6 +96,21 @@ const paymentsDictRef = dict({
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const expectedRebateAmount = computed(() => {
|
||||||
|
if (!formRef.value.useRebateBalance) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(wallet.value.availableAmount || 0, durationSelected.value?.price || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedThirdPartyAmount = computed(() => {
|
||||||
|
return Math.max(0, (durationSelected.value?.price || 0) - expectedRebateAmount.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function amountToYuan(amount: number) {
|
||||||
|
return util.amount.toYuan(amount || 0);
|
||||||
|
}
|
||||||
|
|
||||||
async function orderCreate() {
|
async function orderCreate() {
|
||||||
if (durationSelected.value.price === 0) {
|
if (durationSelected.value.price === 0) {
|
||||||
//如果是0,直接请求创建订单
|
//如果是0,直接请求创建订单
|
||||||
@@ -93,7 +127,7 @@ async function orderCreate() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formRef.value.payType) {
|
if (expectedThirdPartyAmount.value > 0 && !formRef.value.payType) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "请选择支付方式",
|
message: "请选择支付方式",
|
||||||
});
|
});
|
||||||
@@ -104,8 +138,17 @@ async function orderCreate() {
|
|||||||
duration: formRef.value.duration,
|
duration: formRef.value.duration,
|
||||||
num: formRef.value.num ?? 1,
|
num: formRef.value.num ?? 1,
|
||||||
payType: formRef.value.payType,
|
payType: formRef.value.payType,
|
||||||
|
useRebateBalance: formRef.value.useRebateBalance,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (paymentReq.paid) {
|
||||||
|
notification.success({
|
||||||
|
message: "套餐购买成功",
|
||||||
|
});
|
||||||
|
openRef.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
async function onPaid() {
|
async function onPaid() {
|
||||||
openRef.value = false;
|
openRef.value = false;
|
||||||
router.push({
|
router.push({
|
||||||
|
|||||||
@@ -151,6 +151,30 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
rebateAmount: {
|
||||||
|
title: "返利抵扣",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 110,
|
||||||
|
component: {
|
||||||
|
name: PriceInput,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thirdPartyPayAmount: {
|
||||||
|
title: "实付金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 110,
|
||||||
|
component: {
|
||||||
|
name: PriceInput,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
title: "状态",
|
title: "状态",
|
||||||
search: { show: true },
|
search: { show: true },
|
||||||
@@ -177,6 +201,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
{ label: "支付宝", value: "alipay" },
|
{ label: "支付宝", value: "alipay" },
|
||||||
{ label: "微信", value: "wxpay" },
|
{ label: "微信", value: "wxpay" },
|
||||||
{ label: "免费", value: "free" },
|
{ label: "免费", value: "free" },
|
||||||
|
{ label: "返利余额", value: "rebate" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
export async function GetWalletSummary() {
|
||||||
|
return await request({ url: "/wallet/summary", method: "post" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetWithdrawSetting() {
|
||||||
|
return await request({ url: "/wallet/withdraw/setting/get", method: "post" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SaveWithdrawSetting(data: any) {
|
||||||
|
return await request({ url: "/wallet/withdraw/setting/save", method: "post", data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ApplyWithdraw(amount: number) {
|
||||||
|
return await request({ url: "/wallet/withdraw/apply", method: "post", data: { amount } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetWithdraws(query: any) {
|
||||||
|
return await request({ url: "/wallet/withdraw/page", method: "post", data: query });
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import * as api from "./api";
|
||||||
|
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||||
|
|
||||||
|
export default function (): CreateCrudOptionsRet {
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetWithdraws(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: { pageRequest },
|
||||||
|
actionbar: { show: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
rowHandle: { show: false },
|
||||||
|
columns: {
|
||||||
|
amount: {
|
||||||
|
title: "金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
title: "状态",
|
||||||
|
type: "dict-select",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "待审核", value: "pending", color: "warning" },
|
||||||
|
{ label: "已通过", value: "approved", color: "success" },
|
||||||
|
{ label: "已拒绝", value: "rejected", color: "error" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
column: { width: 110 },
|
||||||
|
},
|
||||||
|
channel: {
|
||||||
|
title: "提现渠道",
|
||||||
|
type: "dict-select",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "银行卡", value: "bank" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
column: { width: 110 },
|
||||||
|
},
|
||||||
|
realName: { title: "真实姓名", type: "text", column: { width: 120 } },
|
||||||
|
account: { title: "收款账号", type: "text", column: { width: 180 } },
|
||||||
|
bankName: { title: "开户银行", type: "text", column: { width: 160 } },
|
||||||
|
auditRemark: { title: "审核备注", type: "text", column: { minWidth: 180 } },
|
||||||
|
createTime: { title: "申请时间", type: "datetime", column: { width: 180 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<fs-page class="page-wallet">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">我的钱包</div>
|
||||||
|
</template>
|
||||||
|
<div class="wallet-body">
|
||||||
|
<a-row :gutter="16" class="wallet-summary">
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-statistic title="可用余额" :value="amountToYuan(summary.availableAmount)" suffix="元" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-statistic title="冻结余额" :value="amountToYuan(summary.frozenAmount)" suffix="元" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-statistic title="累计收入" :value="amountToYuan(summary.totalIncomeAmount)" suffix="元" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-statistic title="累计提现" :value="amountToYuan(summary.totalWithdrawAmount)" suffix="元" />
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<div class="wallet-actions">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="openWithdrawSetting">提现设置</a-button>
|
||||||
|
<a-input-number v-model:value="withdrawAmountYuan" :min="0" addon-before="提现金额" addon-after="元" />
|
||||||
|
<a-button @click="applyWithdraw">申请提现</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fs-crud ref="withdrawCrudRef" class="wallet-crud" v-bind="withdrawCrudBinding" />
|
||||||
|
</div>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onActivated, onMounted, reactive, ref } from "vue";
|
||||||
|
import { compute, dict, useFs } from "@fast-crud/fast-crud";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import * as api from "./api";
|
||||||
|
import createWithdrawCrudOptions from "./crud-withdraw";
|
||||||
|
import { util } from "/@/utils";
|
||||||
|
import { useFormDialog } from "/@/use/use-dialog";
|
||||||
|
|
||||||
|
defineOptions({ name: "MyWallet" });
|
||||||
|
|
||||||
|
const summary = reactive<any>({ availableAmount: 0, frozenAmount: 0, totalIncomeAmount: 0, totalWithdrawAmount: 0 });
|
||||||
|
const withdrawAmountYuan = ref(0);
|
||||||
|
const loaded = ref(false);
|
||||||
|
const { openFormDialog } = useFormDialog();
|
||||||
|
const { crudBinding: withdrawCrudBinding, crudExpose: withdrawCrudExpose, crudRef: withdrawCrudRef } = useFs({ createCrudOptions: createWithdrawCrudOptions });
|
||||||
|
|
||||||
|
function amountToYuan(amount: number) {
|
||||||
|
return util.amount.toYuan(amount || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWalletSummary() {
|
||||||
|
const res: any = await api.GetWalletSummary();
|
||||||
|
Object.assign(summary, res || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openWithdrawSetting() {
|
||||||
|
const setting: any = await api.GetWithdrawSetting();
|
||||||
|
const initialForm = Object.assign({ channel: "alipay", realName: "", account: "", bankName: "" }, setting || {});
|
||||||
|
await openFormDialog({
|
||||||
|
title: "提现设置",
|
||||||
|
wrapper: {
|
||||||
|
width: 560,
|
||||||
|
},
|
||||||
|
initialForm,
|
||||||
|
columns: {
|
||||||
|
channel: {
|
||||||
|
title: "提现渠道",
|
||||||
|
type: "dict-radio",
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "银行卡", value: "bank" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
rules: [{ required: true, message: "请选择提现渠道" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
realName: {
|
||||||
|
title: "真实姓名",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
rules: [{ required: true, message: "请输入真实姓名" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
title: "收款账号",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
rules: [{ required: true, message: "请输入收款账号" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bankName: {
|
||||||
|
title: "开户银行",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
show: compute(({ form }) => form.channel === "bank"),
|
||||||
|
rules: [{ required: compute(({ form }) => form.channel === "bank"), message: "请输入开户银行" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async onSubmit(form: any) {
|
||||||
|
await api.SaveWithdrawSetting(form);
|
||||||
|
notification.success({ message: "保存成功" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyWithdraw() {
|
||||||
|
await api.ApplyWithdraw(util.amount.toCent(withdrawAmountYuan.value || 0));
|
||||||
|
withdrawAmountYuan.value = 0;
|
||||||
|
await loadWalletSummary();
|
||||||
|
await withdrawCrudExpose.doRefresh();
|
||||||
|
notification.success({ message: "提现申请已提交" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshWalletPage() {
|
||||||
|
await loadWalletSummary();
|
||||||
|
await withdrawCrudExpose.doRefresh();
|
||||||
|
loaded.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(refreshWalletPage);
|
||||||
|
|
||||||
|
onActivated(async () => {
|
||||||
|
if (!loaded.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await refreshWalletPage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-wallet {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.fs-page-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-summary,
|
||||||
|
.wallet-actions {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-actions {
|
||||||
|
margin: 16px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-crud {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/multi-word-component-names -->
|
||||||
<template>
|
<template>
|
||||||
<div class="main login-page">
|
<div class="main login-page">
|
||||||
<a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed">
|
<a-form v-if="!twoFactor.loginId" ref="formRef" class="user-layout-login" name="custom-validation" :model="formState" v-bind="layout" @finish="handleFinish" @finish-failed="handleFinishFailed">
|
||||||
@@ -108,6 +109,7 @@ import * as oauthApi from "../oauth/api";
|
|||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { request } from "/src/api/service";
|
import { request } from "/src/api/service";
|
||||||
import * as UserApi from "/src/store/user/api.user";
|
import * as UserApi from "/src/store/user/api.user";
|
||||||
|
import { inviteUtils } from "/@/utils/util.invite";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -136,6 +138,7 @@ const formState = reactive({
|
|||||||
smsCode: "",
|
smsCode: "",
|
||||||
captcha: null,
|
captcha: null,
|
||||||
smsCaptcha: null,
|
smsCaptcha: null,
|
||||||
|
inviteCode: inviteUtils.get(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
|
|||||||
@@ -78,6 +78,14 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
|
|
||||||
|
<a-form-item v-if="registerType !== 'mobile'" has-feedback name="inviteCode" label="邀请码">
|
||||||
|
<a-input v-model:value="formState.inviteCode" placeholder="邀请码(选填)" size="large" autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<fs-icon icon="ion:gift-outline"></fs-icon>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item v-if="registerType !== 'mobile'">
|
<a-form-item v-if="registerType !== 'mobile'">
|
||||||
<a-button type="primary" size="large" html-type="submit" class="login-button">注册</a-button>
|
<a-button type="primary" size="large" html-type="submit" class="login-button">注册</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -97,6 +105,7 @@ import { useSettingStore } from "/@/store/settings";
|
|||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import CaptchaInput from "/@/components/captcha/captcha-input.vue";
|
import CaptchaInput from "/@/components/captcha/captcha-input.vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { inviteUtils } from "/@/utils/util.invite";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "RegisterPage",
|
name: "RegisterPage",
|
||||||
components: { CaptchaInput, EmailCode },
|
components: { CaptchaInput, EmailCode },
|
||||||
@@ -125,6 +134,7 @@ export default defineComponent({
|
|||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
captcha: null,
|
captcha: null,
|
||||||
captchaForEmail: null,
|
captchaForEmail: null,
|
||||||
|
inviteCode: inviteUtils.get(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
@@ -213,6 +223,7 @@ export default defineComponent({
|
|||||||
email: formState.email,
|
email: formState.email,
|
||||||
captcha: registerType.value === "email" ? formState.captchaForEmail : formState.captcha,
|
captcha: registerType.value === "email" ? formState.captchaForEmail : formState.captcha,
|
||||||
validateCode: formState.validateCode,
|
validateCode: formState.validateCode,
|
||||||
|
inviteCode: formState.inviteCode,
|
||||||
}) as any
|
}) as any
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { request } from "/@/api/service";
|
||||||
|
|
||||||
|
export async function GetSettings() {
|
||||||
|
return await request({ url: "/sys/invite/settings/get", method: "post" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function SaveSettings(data: any) {
|
||||||
|
return await request({ url: "/sys/invite/settings/save", method: "post", data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetWithdraws(query: any) {
|
||||||
|
return await request({ url: "/sys/wallet/withdraw/page", method: "post", data: query });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ApproveWithdraw(id: number, remark?: string) {
|
||||||
|
return await request({ url: "/sys/wallet/withdraw/approve", method: "post", data: { id, remark } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function RejectWithdraw(id: number, remark: string) {
|
||||||
|
return await request({ url: "/sys/wallet/withdraw/reject", method: "post", data: { id, remark } });
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { compute, CreateCrudOptionsProps, CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
|
import { Modal, 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";
|
||||||
|
|
||||||
|
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
|
const { openFormDialog } = useFormDialog();
|
||||||
|
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
return await api.GetWithdraws(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function approve(row: any) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "确认提现已线下打款?",
|
||||||
|
async onOk() {
|
||||||
|
await api.ApproveWithdraw(row.id);
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
notification.success({ message: "已审核通过" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reject(row: any) {
|
||||||
|
await openFormDialog({
|
||||||
|
title: "拒绝提现申请",
|
||||||
|
wrapper: {
|
||||||
|
width: 520,
|
||||||
|
},
|
||||||
|
initialForm: {
|
||||||
|
remark: "",
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
remark: {
|
||||||
|
title: "拒绝理由",
|
||||||
|
type: "textarea",
|
||||||
|
form: {
|
||||||
|
col: {
|
||||||
|
span: 24,
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
name: "a-textarea",
|
||||||
|
vModel: "value",
|
||||||
|
rows: 4,
|
||||||
|
placeholder: "请填写拒绝理由",
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: "请填写拒绝理由" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async onSubmit(form: any) {
|
||||||
|
const remark = form.remark.trim();
|
||||||
|
if (!remark) {
|
||||||
|
notification.error({ message: "请填写拒绝理由" });
|
||||||
|
throw new Error("请填写拒绝理由");
|
||||||
|
}
|
||||||
|
await api.RejectWithdraw(row.id, remark);
|
||||||
|
await crudExpose.doRefresh();
|
||||||
|
notification.success({ message: "已拒绝并退回余额" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: { pageRequest },
|
||||||
|
actionbar: { show: false },
|
||||||
|
toolbar: { show: false },
|
||||||
|
rowHandle: {
|
||||||
|
width: 150,
|
||||||
|
fixed: "right",
|
||||||
|
buttons: {
|
||||||
|
view: { show: false },
|
||||||
|
edit: { show: false },
|
||||||
|
copy: { show: false },
|
||||||
|
remove: { show: false },
|
||||||
|
approve: {
|
||||||
|
text: "通过",
|
||||||
|
type: "link",
|
||||||
|
show: compute(({ row }) => row.status === "pending"),
|
||||||
|
click: ({ row }) => approve(row),
|
||||||
|
},
|
||||||
|
reject: {
|
||||||
|
text: "拒绝",
|
||||||
|
type: "link",
|
||||||
|
show: compute(({ row }) => row.status === "pending"),
|
||||||
|
click: ({ row }) => reject(row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
userId: { title: "用户ID", type: "number", search: { show: true }, column: { width: 100 } },
|
||||||
|
amount: {
|
||||||
|
title: "金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 120,
|
||||||
|
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
title: "状态",
|
||||||
|
type: "dict-select",
|
||||||
|
search: { show: true },
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "待审核", value: "pending", color: "warning" },
|
||||||
|
{ label: "已通过", value: "approved", color: "success" },
|
||||||
|
{ label: "已拒绝", value: "rejected", color: "error" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
column: { width: 110 },
|
||||||
|
},
|
||||||
|
channel: {
|
||||||
|
title: "提现渠道",
|
||||||
|
type: "dict-select",
|
||||||
|
search: { show: true },
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "银行卡", value: "bank" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
column: { width: 110 },
|
||||||
|
},
|
||||||
|
realName: { title: "真实姓名", type: "text", search: { show: true }, column: { width: 120 } },
|
||||||
|
account: { title: "收款账号", type: "text", column: { width: 180 } },
|
||||||
|
bankName: { title: "开户银行", type: "text", column: { width: 160 } },
|
||||||
|
auditRemark: { title: "审核备注", type: "text", column: { minWidth: 180 } },
|
||||||
|
createTime: { title: "申请时间", type: "datetime", column: { width: 180 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<fs-page class="page-sys-invite-setting">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">邀请返佣设置</div>
|
||||||
|
</template>
|
||||||
|
<div class="page-body">
|
||||||
|
<a-form ref="formRef" :model="settings" :label-col="{ style: { width: '140px' } }" class="settings-form">
|
||||||
|
<a-form-item label="开启返佣" name="enabled">
|
||||||
|
<a-switch v-model:checked="settings.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="返佣比例" name="commissionRate">
|
||||||
|
<a-input-number v-model:value="settings.commissionRate" :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>
|
||||||
|
<a-form-item label="提现渠道" name="withdrawChannels">
|
||||||
|
<a-checkbox-group v-model:value="settings.withdrawChannels" :options="withdrawChannelOptions" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label=" ">
|
||||||
|
<a-button type="primary" @click="saveSettings">保存设置</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import * as api from "./api";
|
||||||
|
import { util } from "/@/utils";
|
||||||
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
|
||||||
|
defineOptions({ name: "SysInviteCommissionSetting" });
|
||||||
|
|
||||||
|
const settings = reactive<any>({ enabled: false, commissionRate: 0, minWithdrawAmountYuan: 0, withdrawChannels: ["alipay", "bank"] });
|
||||||
|
const withdrawChannelOptions = [
|
||||||
|
{ label: "支付宝", value: "alipay" },
|
||||||
|
{ label: "银行卡", value: "bank" },
|
||||||
|
];
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const data: any = await api.GetSettings();
|
||||||
|
settings.enabled = !!data?.enabled;
|
||||||
|
settings.commissionRate = data?.commissionRate || 0;
|
||||||
|
settings.minWithdrawAmountYuan = util.amount.toYuan(data?.minWithdrawAmount || 0);
|
||||||
|
settings.withdrawChannels = data?.withdrawChannels?.length ? data.withdrawChannels : ["alipay", "bank"];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSettings() {
|
||||||
|
await api.SaveSettings({
|
||||||
|
enabled: settings.enabled,
|
||||||
|
commissionRate: settings.commissionRate || 0,
|
||||||
|
minWithdrawAmount: util.amount.toCent(settings.minWithdrawAmountYuan || 0),
|
||||||
|
withdrawChannels: settings.withdrawChannels || [],
|
||||||
|
});
|
||||||
|
await useSettingStore().loadSysSettings();
|
||||||
|
notification.success({ message: "保存成功" });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadSettings);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.page-sys-invite-setting {
|
||||||
|
.page-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.settings-form {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<fs-page class="page-sys-invite-withdraw">
|
||||||
|
<template #header>
|
||||||
|
<div class="title">提现申请记录</div>
|
||||||
|
</template>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onActivated, onMounted } from "vue";
|
||||||
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
|
import createCrudOptions from "./crud-withdraw";
|
||||||
|
|
||||||
|
defineOptions({ name: "SysInviteWithdraw" });
|
||||||
|
|
||||||
|
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -174,6 +174,30 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
rebateAmount: {
|
||||||
|
title: "返利抵扣",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 110,
|
||||||
|
component: {
|
||||||
|
name: PriceInput,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thirdPartyPayAmount: {
|
||||||
|
title: "实付金额",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
width: 110,
|
||||||
|
component: {
|
||||||
|
name: PriceInput,
|
||||||
|
vModel: "modelValue",
|
||||||
|
edit: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
title: "状态",
|
title: "状态",
|
||||||
search: { show: true },
|
search: { show: true },
|
||||||
@@ -200,6 +224,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
{ label: "支付宝", value: "alipay" },
|
{ label: "支付宝", value: "alipay" },
|
||||||
{ label: "微信", value: "wxpay" },
|
{ label: "微信", value: "wxpay" },
|
||||||
{ label: "免费", value: "free" },
|
{ label: "免费", value: "free" },
|
||||||
|
{ label: "返利余额", value: "rebate" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
column: {
|
column: {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
ALTER TABLE cd_trade ADD COLUMN rebate_amount bigint NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE cd_trade ADD COLUMN third_party_pay_amount bigint NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
CREATE TABLE `cd_invite_code`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`user_id` bigint,
|
||||||
|
`code` varchar(50),
|
||||||
|
`disabled` boolean NOT NULL DEFAULT false,
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX `index_invite_code_user_id` ON `cd_invite_code` (`user_id`);
|
||||||
|
CREATE UNIQUE INDEX `index_invite_code_code` ON `cd_invite_code` (`code`);
|
||||||
|
|
||||||
|
CREATE TABLE `cd_invite_relation`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`inviter_user_id` bigint,
|
||||||
|
`invitee_user_id` bigint,
|
||||||
|
`invite_code` varchar(50),
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX `index_invite_relation_inviter` ON `cd_invite_relation` (`inviter_user_id`);
|
||||||
|
CREATE UNIQUE INDEX `index_invite_relation_invitee` ON `cd_invite_relation` (`invitee_user_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `cd_user_wallet`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`user_id` bigint,
|
||||||
|
`available_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`frozen_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`total_income_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`total_consumed_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`total_withdraw_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX `index_user_wallet_user_id` ON `cd_user_wallet` (`user_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `cd_invite_commission_log`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`user_id` bigint,
|
||||||
|
`amount` bigint,
|
||||||
|
`trade_id` bigint,
|
||||||
|
`invitee_user_id` bigint,
|
||||||
|
`consume_amount` bigint NOT NULL DEFAULT 0,
|
||||||
|
`remark` varchar(2048),
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX `index_invite_log_user_id` ON `cd_invite_commission_log` (`user_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `cd_user_wallet_log`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`user_id` bigint,
|
||||||
|
`type` varchar(50),
|
||||||
|
`amount` bigint,
|
||||||
|
`balance_after` bigint,
|
||||||
|
`trade_id` bigint,
|
||||||
|
`withdraw_id` bigint,
|
||||||
|
`remark` varchar(2048),
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX `index_user_wallet_log_user_id` ON `cd_user_wallet_log` (`user_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `cd_user_wallet_withdraw`
|
||||||
|
(
|
||||||
|
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
|
||||||
|
`user_id` bigint,
|
||||||
|
`amount` bigint,
|
||||||
|
`status` varchar(50),
|
||||||
|
`channel` varchar(50),
|
||||||
|
`real_name` varchar(100),
|
||||||
|
`account` varchar(200),
|
||||||
|
`bank_name` varchar(200),
|
||||||
|
`audit_user_id` bigint,
|
||||||
|
`audit_remark` varchar(2048),
|
||||||
|
`audit_time` bigint,
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX `index_user_wallet_withdraw_user_id` ON `cd_user_wallet_withdraw` (`user_id`);
|
||||||
|
CREATE INDEX `index_user_wallet_withdraw_status` ON `cd_user_wallet_withdraw` (`status`);
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
ALTER TABLE cd_trade ADD COLUMN rebate_amount bigint NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE cd_trade ADD COLUMN third_party_pay_amount bigint NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_code"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"user_id" bigint,
|
||||||
|
"code" varchar(50),
|
||||||
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "index_invite_code_user_id" ON "cd_invite_code" ("user_id");
|
||||||
|
CREATE UNIQUE INDEX "index_invite_code_code" ON "cd_invite_code" ("code");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_relation"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"inviter_user_id" bigint,
|
||||||
|
"invitee_user_id" bigint,
|
||||||
|
"invite_code" varchar(50),
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_invite_relation_inviter" ON "cd_invite_relation" ("inviter_user_id");
|
||||||
|
CREATE UNIQUE INDEX "index_invite_relation_invitee" ON "cd_invite_relation" ("invitee_user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"user_id" bigint,
|
||||||
|
"available_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"frozen_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"total_income_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"total_consumed_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"total_withdraw_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "index_user_wallet_user_id" ON "cd_user_wallet" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_commission_log"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"user_id" bigint,
|
||||||
|
"amount" bigint,
|
||||||
|
"trade_id" bigint,
|
||||||
|
"invitee_user_id" bigint,
|
||||||
|
"consume_amount" bigint NOT NULL DEFAULT 0,
|
||||||
|
"remark" varchar(2048),
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_invite_log_user_id" ON "cd_invite_commission_log" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet_log"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"user_id" bigint,
|
||||||
|
"type" varchar(50),
|
||||||
|
"amount" bigint,
|
||||||
|
"balance_after" bigint,
|
||||||
|
"trade_id" bigint,
|
||||||
|
"withdraw_id" bigint,
|
||||||
|
"remark" varchar(2048),
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_user_wallet_log_user_id" ON "cd_user_wallet_log" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet_withdraw"
|
||||||
|
(
|
||||||
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||||
|
"user_id" bigint,
|
||||||
|
"amount" bigint,
|
||||||
|
"status" varchar(50),
|
||||||
|
"channel" varchar(50),
|
||||||
|
"real_name" varchar(100),
|
||||||
|
"account" varchar(200),
|
||||||
|
"bank_name" varchar(200),
|
||||||
|
"audit_user_id" bigint,
|
||||||
|
"audit_remark" varchar(2048),
|
||||||
|
"audit_time" bigint,
|
||||||
|
"create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_user_wallet_withdraw_user_id" ON "cd_user_wallet_withdraw" ("user_id");
|
||||||
|
CREATE INDEX "index_user_wallet_withdraw_status" ON "cd_user_wallet_withdraw" ("status");
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
ALTER TABLE cd_trade ADD COLUMN rebate_amount integer NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE cd_trade ADD COLUMN third_party_pay_amount integer NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_code"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"code" varchar(50),
|
||||||
|
"disabled" boolean NOT NULL DEFAULT (false),
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "index_invite_code_user_id" ON "cd_invite_code" ("user_id");
|
||||||
|
CREATE UNIQUE INDEX "index_invite_code_code" ON "cd_invite_code" ("code");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_relation"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"inviter_user_id" integer,
|
||||||
|
"invitee_user_id" integer,
|
||||||
|
"invite_code" varchar(50),
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_invite_relation_inviter" ON "cd_invite_relation" ("inviter_user_id");
|
||||||
|
CREATE UNIQUE INDEX "index_invite_relation_invitee" ON "cd_invite_relation" ("invitee_user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"available_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"frozen_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"total_income_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"total_consumed_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"total_withdraw_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "index_user_wallet_user_id" ON "cd_user_wallet" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_invite_commission_log"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"amount" integer,
|
||||||
|
"trade_id" integer,
|
||||||
|
"invitee_user_id" integer,
|
||||||
|
"consume_amount" integer NOT NULL DEFAULT 0,
|
||||||
|
"remark" varchar(2048),
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_invite_log_user_id" ON "cd_invite_commission_log" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet_log"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"type" varchar(50),
|
||||||
|
"amount" integer,
|
||||||
|
"balance_after" integer,
|
||||||
|
"trade_id" integer,
|
||||||
|
"withdraw_id" integer,
|
||||||
|
"remark" varchar(2048),
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_user_wallet_log_user_id" ON "cd_user_wallet_log" ("user_id");
|
||||||
|
|
||||||
|
CREATE TABLE "cd_user_wallet_withdraw"
|
||||||
|
(
|
||||||
|
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
"user_id" integer,
|
||||||
|
"amount" integer,
|
||||||
|
"status" varchar(50),
|
||||||
|
"channel" varchar(50),
|
||||||
|
"real_name" varchar(100),
|
||||||
|
"account" varchar(200),
|
||||||
|
"bank_name" varchar(200),
|
||||||
|
"audit_user_id" integer,
|
||||||
|
"audit_remark" varchar(2048),
|
||||||
|
"audit_time" integer,
|
||||||
|
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_user_wallet_withdraw_user_id" ON "cd_user_wallet_withdraw" ("user_id");
|
||||||
|
CREATE INDEX "index_user_wallet_withdraw_status" ON "cd_user_wallet_withdraw" ("status");
|
||||||
@@ -64,6 +64,7 @@ export class LoginController extends BaseController {
|
|||||||
mobile: body.mobile,
|
mobile: body.mobile,
|
||||||
smsCode: body.smsCode,
|
smsCode: body.smsCode,
|
||||||
randomStr: body.randomStr,
|
randomStr: body.randomStr,
|
||||||
|
inviteCode: body.inviteCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.writeTokenCookie(token);
|
this.writeTokenCookie(token);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { BaseController, Constants, SysSettingsService } from '@certd/lib-server
|
|||||||
import { RegisterType, UserService } from '../../../modules/sys/authority/service/user-service.js';
|
import { RegisterType, UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||||
import { checkComm, checkPlus } from '@certd/plus-core';
|
import { checkComm, checkPlus } from '@certd/plus-core';
|
||||||
|
import { InviteService } from '@certd/commercial-core';
|
||||||
|
|
||||||
export type RegisterReq = {
|
export type RegisterReq = {
|
||||||
type: RegisterType;
|
type: RegisterType;
|
||||||
@@ -14,6 +15,7 @@ export type RegisterReq = {
|
|||||||
|
|
||||||
validateCode: string;
|
validateCode: string;
|
||||||
captcha:any;
|
captcha:any;
|
||||||
|
inviteCode?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +31,9 @@ export class RegisterController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
sysSettingsService: SysSettingsService;
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inviteService: InviteService;
|
||||||
|
|
||||||
@Post('/register', { description: Constants.per.guest })
|
@Post('/register', { description: Constants.per.guest })
|
||||||
public async register(
|
public async register(
|
||||||
@Body(ALL)
|
@Body(ALL)
|
||||||
@@ -53,10 +58,13 @@ export class RegisterController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.codeService.checkCaptcha(body.captcha,{remoteIp});
|
await this.codeService.checkCaptcha(body.captcha,{remoteIp});
|
||||||
const newUser = await this.userService.register(body.type, {
|
const registerUser = {
|
||||||
username: body.username,
|
username: body.username,
|
||||||
password: body.password,
|
password: body.password,
|
||||||
} as any);
|
} as any;
|
||||||
|
const newUser = await this.userService.register(body.type, registerUser, async txManager => {
|
||||||
|
await this.inviteService.bindInvitee(registerUser.id, body.inviteCode, txManager);
|
||||||
|
});
|
||||||
return this.ok(newUser);
|
return this.ok(newUser);
|
||||||
} else if (body.type === 'mobile') {
|
} else if (body.type === 'mobile') {
|
||||||
if (sysPublicSettings.mobileRegisterEnabled === false) {
|
if (sysPublicSettings.mobileRegisterEnabled === false) {
|
||||||
@@ -70,12 +78,15 @@ export class RegisterController extends BaseController {
|
|||||||
smsCode: body.validateCode,
|
smsCode: body.validateCode,
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
const newUser = await this.userService.register(body.type, {
|
const registerUser = {
|
||||||
username: body.username,
|
username: body.username,
|
||||||
phoneCode: body.phoneCode,
|
phoneCode: body.phoneCode,
|
||||||
mobile: body.mobile,
|
mobile: body.mobile,
|
||||||
password: body.password,
|
password: body.password,
|
||||||
} as any);
|
} as any;
|
||||||
|
const newUser = await this.userService.register(body.type, registerUser, async txManager => {
|
||||||
|
await this.inviteService.bindInvitee(registerUser.id, body.inviteCode, txManager);
|
||||||
|
});
|
||||||
return this.ok(newUser);
|
return this.ok(newUser);
|
||||||
} else if (body.type === 'email') {
|
} else if (body.type === 'email') {
|
||||||
if (sysPublicSettings.emailRegisterEnabled === false) {
|
if (sysPublicSettings.emailRegisterEnabled === false) {
|
||||||
@@ -87,11 +98,14 @@ export class RegisterController extends BaseController {
|
|||||||
validateCode: body.validateCode,
|
validateCode: body.validateCode,
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
const newUser = await this.userService.register(body.type, {
|
const registerUser = {
|
||||||
username: body.username,
|
username: body.username,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
password: body.password,
|
password: body.password,
|
||||||
} as any);
|
} as any;
|
||||||
|
const newUser = await this.userService.register(body.type, registerUser, async txManager => {
|
||||||
|
await this.inviteService.bindInvitee(registerUser.id, body.inviteCode, txManager);
|
||||||
|
});
|
||||||
return this.ok(newUser);
|
return this.ok(newUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
SysSuiteSetting
|
SysSuiteSetting
|
||||||
} from "@certd/lib-server";
|
} from "@certd/lib-server";
|
||||||
import { AppKey, getPlusInfo, isComm } from "@certd/plus-core";
|
import { AppKey, getPlusInfo, isComm } from "@certd/plus-core";
|
||||||
|
import { SysInviteCommissionSetting } from "@certd/commercial-core";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
import { getVersion } from "../../utils/version.js";
|
import { getVersion } from "../../utils/version.js";
|
||||||
import { http } from "@certd/basic";
|
import { http } from "@certd/basic";
|
||||||
@@ -57,6 +58,16 @@ export class BasicSettingsController extends BaseController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getInviteSetting() {
|
||||||
|
if (!isComm()) {
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
const setting = await this.sysSettingsService.getSetting<SysInviteCommissionSetting>(SysInviteCommissionSetting);
|
||||||
|
return {
|
||||||
|
enabled: setting.enabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getSiteEnv() {
|
public async getSiteEnv() {
|
||||||
const env: SysSiteEnv = {
|
const env: SysSiteEnv = {
|
||||||
agent: this.agentConfig
|
agent: this.agentConfig
|
||||||
@@ -92,6 +103,7 @@ export class BasicSettingsController extends BaseController {
|
|||||||
const plusInfo = await this.plusInfo();
|
const plusInfo = await this.plusInfo();
|
||||||
const headerMenus = await this.getHeaderMenus();
|
const headerMenus = await this.getHeaderMenus();
|
||||||
const suiteSetting = await this.getSuiteSetting();
|
const suiteSetting = await this.getSuiteSetting();
|
||||||
|
const inviteSetting = await this.getInviteSetting();
|
||||||
const version = await getVersion();
|
const version = await getVersion();
|
||||||
return this.ok({
|
return this.ok({
|
||||||
sysPublic,
|
sysPublic,
|
||||||
@@ -101,6 +113,7 @@ export class BasicSettingsController extends BaseController {
|
|||||||
plusInfo,
|
plusInfo,
|
||||||
headerMenus,
|
headerMenus,
|
||||||
suiteSetting,
|
suiteSetting,
|
||||||
|
inviteSetting,
|
||||||
app: {
|
app: {
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
version
|
version
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { isPlus } from "@certd/plus-core";
|
|||||||
import { AddonService } from "@certd/lib-server";
|
import { AddonService } from "@certd/lib-server";
|
||||||
import { OauthBoundService } from "./oauth-bound-service.js";
|
import { OauthBoundService } from "./oauth-bound-service.js";
|
||||||
import { PasskeyService } from "./passkey-service.js";
|
import { PasskeyService } from "./passkey-service.js";
|
||||||
|
import { InviteService } from "@certd/commercial-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@@ -49,6 +50,9 @@ export class LoginService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
passkeyService: PasskeyService;
|
passkeyService: PasskeyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
inviteService: InviteService;
|
||||||
|
|
||||||
checkIsBlocked(username: string) {
|
checkIsBlocked(username: string) {
|
||||||
const blockDurationKey = `login_block_duration:${username}`;
|
const blockDurationKey = `login_block_duration:${username}`;
|
||||||
const value = cache.get(blockDurationKey);
|
const value = cache.get(blockDurationKey);
|
||||||
@@ -111,7 +115,7 @@ export class LoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string }) {
|
async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; inviteCode?: string }) {
|
||||||
|
|
||||||
this.checkIsBlocked(req.mobile)
|
this.checkIsBlocked(req.mobile)
|
||||||
|
|
||||||
@@ -129,11 +133,14 @@ export class LoginService {
|
|||||||
let info = await this.userService.findOne({phoneCode, mobile: mobile});
|
let info = await this.userService.findOne({phoneCode, mobile: mobile});
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
//用户不存在,注册
|
//用户不存在,注册
|
||||||
info = await this.userService.register('mobile', {
|
const registerUser = {
|
||||||
phoneCode,
|
phoneCode,
|
||||||
mobile,
|
mobile,
|
||||||
password: '',
|
password: '',
|
||||||
} as any);
|
} as any;
|
||||||
|
info = await this.userService.register('mobile', registerUser, async txManager => {
|
||||||
|
await this.inviteService.bindInvitee(registerUser.id, req.inviteCode, txManager);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.clearCacheOnSuccess(mobile);
|
this.clearCacheOnSuccess(mobile);
|
||||||
return this.onLoginSuccess(info);
|
return this.onLoginSuccess(info);
|
||||||
@@ -264,4 +271,3 @@ export class LoginService {
|
|||||||
const user = await this.passkeyService.loginByPasskey(credential, challenge, ctx);
|
const user = await this.passkeyService.loginByPasskey(credential, challenge, ctx);
|
||||||
return this.generateToken(user);
|
return this.generateToken(user);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user