mirror of
https://github.com/certd/certd.git
synced 2026-10-13 15:49:25 +08:00
perf: 新增余额管理,管理员充值功能
1. 新增余额管理页面,支持用户余额查询与充值 2. 新增余额变更记录页面,查看用户余额明细 3. 补充多语言国际化文案 4. 修复免费订单显示文案为0元而非免费 5. 隐藏审核页面的操作按钮 6. 注释掉无用的堆日志启动代码
This commit is contained in:
@@ -53,6 +53,8 @@ export default {
|
||||
suiteSetting: "Suite Settings",
|
||||
orderManager: "Order Management",
|
||||
userSuites: "User Suites",
|
||||
balanceManager: "Balance Management",
|
||||
balanceLog: "Balance Change Records",
|
||||
inviteCommissionSetting: "Incentive Plan Settings",
|
||||
inviteLevel: "Promotion Levels",
|
||||
inviteUserLevel: "User Promotion Levels",
|
||||
|
||||
@@ -54,6 +54,8 @@ export default {
|
||||
suiteSetting: "套餐设置",
|
||||
orderManager: "订单管理",
|
||||
userSuites: "用户套餐",
|
||||
balanceManager: "余额管理",
|
||||
balanceLog: "余额变更记录",
|
||||
inviteCommissionSetting: "激励计划设置",
|
||||
inviteLevel: "推广等级",
|
||||
inviteUserLevel: "用户推广等级",
|
||||
|
||||
@@ -317,6 +317,34 @@ export const sysResources = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.balanceManager",
|
||||
name: "SysWallet",
|
||||
path: "/sys/suite/wallet",
|
||||
component: "/sys/suite/wallet/index.vue",
|
||||
meta: {
|
||||
show: () => {
|
||||
const settingStore = useSettingStore();
|
||||
return settingStore.isComm;
|
||||
},
|
||||
icon: "ion:wallet-outline",
|
||||
permission: "sys:settings:edit",
|
||||
auth: true,
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.balanceLog",
|
||||
name: "SysWalletLog",
|
||||
path: "/sys/suite/wallet-log",
|
||||
component: "/sys/suite/wallet/log/index.vue",
|
||||
meta: {
|
||||
isMenu: false,
|
||||
permission: "sys:settings:view",
|
||||
auth: true,
|
||||
keepAlive: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.sysResources.inviteCommissionSetting",
|
||||
name: "SysInviteCommissionSetting",
|
||||
|
||||
@@ -51,10 +51,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
rowHandle: {
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
show: false,
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
edit: { show: false },
|
||||
remove: { show: true },
|
||||
remove: { show: false },
|
||||
copy: { show: false },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{ $t("certd.order.paymentMethod") }}:</span>
|
||||
<div v-if="durationSelected.price === 0 || expectedThirdPartyAmount === 0">{{ $t("certd.order.free") }}</div>
|
||||
<div v-if="durationSelected.price === 0 || expectedThirdPartyAmount === 0">0元</div>
|
||||
<fs-dict-select v-else v-model:value="formRef.payType" :dict="paymentsDictRef" style="width: 200px"> </fs-dict-select>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/sys/wallet";
|
||||
|
||||
export async function GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
export async function Recharge(data: { userId: number; amount: number; remark?: string }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/recharge",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetWalletLogs(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/log/page",
|
||||
method: "post",
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetSimpleUserByIds(ids: number[]) {
|
||||
return await request({
|
||||
url: "/sys/authority/user/getSimpleUserByIds",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { CreateCrudOptionsProps, CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { util } from "/@/utils";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
import * as api from "./api";
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
const router = useRouter();
|
||||
|
||||
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.GetList(query);
|
||||
};
|
||||
|
||||
function moneyColumn(title: string) {
|
||||
return {
|
||||
title,
|
||||
type: "number",
|
||||
column: {
|
||||
width: 120,
|
||||
component: {
|
||||
name: PriceInput,
|
||||
vModel: "modelValue",
|
||||
edit: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function openRecharge(userId?: number) {
|
||||
await openFormDialog({
|
||||
title: "用户余额充值",
|
||||
wrapper: { width: 520 },
|
||||
initialForm: {
|
||||
userId: userId ?? null,
|
||||
amountYuan: null,
|
||||
remark: "",
|
||||
},
|
||||
columns: {
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
dict: userDict,
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
rules: [{ required: true, message: "请选择用户" }],
|
||||
component: {
|
||||
disabled: userId != null,
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: "请选择用户",
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
amountYuan: {
|
||||
title: "充值金额",
|
||||
type: "number",
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
rules: [{ required: true, message: "请输入充值金额" }],
|
||||
component: {
|
||||
name: "a-input-number",
|
||||
vModel: "value",
|
||||
min: 0,
|
||||
precision: 2,
|
||||
addonAfter: "元",
|
||||
style: { width: "100%" },
|
||||
},
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
type: "text",
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
helper: "选填",
|
||||
},
|
||||
},
|
||||
},
|
||||
async onSubmit(form: any) {
|
||||
await api.Recharge({
|
||||
userId: form.userId,
|
||||
amount: util.amount.toCent(form.amountYuan || 0),
|
||||
remark: form.remark,
|
||||
});
|
||||
await crudExpose.doRefresh();
|
||||
notification.success({ message: "充值成功" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
},
|
||||
toolbar: { show: false },
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: { show: false },
|
||||
recharge: {
|
||||
text: "余额充值",
|
||||
type: "primary",
|
||||
click: () => openRecharge(),
|
||||
},
|
||||
log: {
|
||||
text: "余额变更记录",
|
||||
type: "primary",
|
||||
click: () => router.push("/sys/suite/wallet-log"),
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
width: 220,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
edit: { show: false },
|
||||
copy: { show: false },
|
||||
remove: { show: false },
|
||||
walletLog: {
|
||||
text: "余额变更记录",
|
||||
type: "link",
|
||||
click: ({ row }) => router.push({ path: "/sys/suite/wallet-log", query: { userId: row.userId } }),
|
||||
},
|
||||
recharge: {
|
||||
text: "充值",
|
||||
type: "link",
|
||||
click: ({ row }) => openRecharge(row.userId),
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
type: "number",
|
||||
column: { width: 80 },
|
||||
form: { show: false },
|
||||
},
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
dict: userDict,
|
||||
search: { show: true, col: { span: 3 } },
|
||||
column: { width: 160 },
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
placeholder: "点击选择用户",
|
||||
},
|
||||
createCrudOptions: createCrudOptionsUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
availableAmount: moneyColumn("可用余额"),
|
||||
totalAmount: moneyColumn("总余额"),
|
||||
frozenAmount: moneyColumn("冻结余额"),
|
||||
totalIncomeAmount: moneyColumn("累计收入"),
|
||||
totalConsumedAmount: moneyColumn("累计消费"),
|
||||
totalWithdrawAmount: moneyColumn("累计提现"),
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: { show: false },
|
||||
column: { sorter: true, width: 160 },
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: { show: false },
|
||||
column: { width: 160 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
余额管理
|
||||
<span class="sub">管理用户余额,可给用户充值</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { useMounted } from "/@/use/use-mounted";
|
||||
|
||||
defineOptions({
|
||||
name: "SysWallet",
|
||||
});
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
useMounted(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,100 @@
|
||||
import { CreateCrudOptionsRet, dict, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useRoute } from "vue-router";
|
||||
import { util } from "/@/utils";
|
||||
import createCrudOptionsUser from "/@/views/sys/authority/user/crud";
|
||||
import * as api from "../api";
|
||||
|
||||
function moneyText(amount: number) {
|
||||
const yuan = util.amount.toYuan(Math.abs(amount || 0));
|
||||
if (amount < 0) {
|
||||
return `-¥${yuan}`;
|
||||
}
|
||||
return `¥${yuan}`;
|
||||
}
|
||||
|
||||
export default function (): CreateCrudOptionsRet {
|
||||
const route = useRoute();
|
||||
const userIdFromQuery = route.query.userId ? Number(route.query.userId) : undefined;
|
||||
|
||||
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.GetWalletLogs(query);
|
||||
};
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: { pageRequest },
|
||||
search: {
|
||||
initialForm: { userId: userIdFromQuery },
|
||||
},
|
||||
actionbar: { show: false },
|
||||
toolbar: { show: false },
|
||||
rowHandle: { show: false },
|
||||
columns: {
|
||||
createTime: { title: "时间", type: "datetime", column: { width: 180 } },
|
||||
userId: {
|
||||
title: "用户",
|
||||
type: "table-select",
|
||||
dict: userDict,
|
||||
search: { show: true, col: { span: 3 } },
|
||||
column: { width: 160 },
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: { placeholder: "点击选择用户" },
|
||||
createCrudOptions: createCrudOptionsUser,
|
||||
},
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "收益入账", value: "income", color: "success" },
|
||||
{ label: "余额抵扣", value: "consume", color: "default" },
|
||||
{ label: "提现扣减", value: "withdraw", color: "warning" },
|
||||
],
|
||||
}),
|
||||
search: { show: true, col: { span: 3 } },
|
||||
column: { width: 120 },
|
||||
},
|
||||
amount: {
|
||||
title: "变动金额",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 120,
|
||||
cellRender({ value }) {
|
||||
const amount = Number(value || 0);
|
||||
return <span class={amount < 0 ? "text-green-500" : "text-red-500"}>{moneyText(amount)}</span>;
|
||||
},
|
||||
},
|
||||
},
|
||||
balanceAfter: {
|
||||
title: "变动后余额",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 130,
|
||||
cellRender({ value }) {
|
||||
return <span class="text-red-500">{moneyText(Number(value || 0))}</span>;
|
||||
},
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
type: "text",
|
||||
column: { minWidth: 220 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title">
|
||||
{{ pageTitle }}
|
||||
<span class="sub">{{ pageSub }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { useMounted } from "/@/use/use-mounted";
|
||||
|
||||
defineOptions({
|
||||
name: "SysWalletLog",
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const isUserLog = computed(() => route.query.userId != null);
|
||||
const pageTitle = computed(() => {
|
||||
if (isUserLog.value) {
|
||||
return "用户余额变更记录";
|
||||
}
|
||||
return "余额变更记录";
|
||||
});
|
||||
const pageSub = computed(() => {
|
||||
if (isUserLog.value) {
|
||||
return "查看该用户的余额变更明细";
|
||||
}
|
||||
return "查看所有用户的余额变更明细";
|
||||
});
|
||||
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
useMounted(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -45,6 +45,7 @@ process.on("uncaughtException", error => {
|
||||
// setInterval(log, 200);
|
||||
// log()
|
||||
// }
|
||||
|
||||
// startHeapLog();
|
||||
|
||||
@Configuration({
|
||||
|
||||
Reference in New Issue
Block a user