mirror of
https://github.com/certd/certd.git
synced 2026-06-24 03:07:31 +08:00
perf(trade): 优化商品购买页面的规格展示和折扣计算,支持订单取消
This commit is contained in:
@@ -370,21 +370,6 @@ export const certdResources = [
|
||||
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",
|
||||
@@ -400,6 +385,21 @@ export const certdResources = [
|
||||
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.paymentReturn",
|
||||
name: "PaymentReturn",
|
||||
|
||||
@@ -14,21 +14,30 @@ export default function (): CreateCrudOptionsRet {
|
||||
toolbar: { show: false },
|
||||
rowHandle: { show: false },
|
||||
columns: {
|
||||
inviteeUserId: {
|
||||
title: "被推广用户ID",
|
||||
type: "number",
|
||||
column: { width: 140 },
|
||||
},
|
||||
inviteCode: {
|
||||
title: "推广码",
|
||||
type: "text",
|
||||
column: { width: 160 },
|
||||
},
|
||||
createTime: {
|
||||
title: "推广时间",
|
||||
title: "邀请时间",
|
||||
type: "datetime",
|
||||
column: { width: 180 },
|
||||
},
|
||||
simpleUser: {
|
||||
title: "被邀请用户名",
|
||||
type: "text",
|
||||
column: {
|
||||
minWidth: 180,
|
||||
cellRender({ row }) {
|
||||
const simpleUser = row.simpleUser;
|
||||
if (!simpleUser) {
|
||||
return row.inviteeUserId ? `用户${row.inviteeUserId} (${row.inviteeUserId})` : "-";
|
||||
}
|
||||
return simpleUser.displayName || `${simpleUser.username || "-"} (${simpleUser.id})`;
|
||||
},
|
||||
},
|
||||
},
|
||||
inviteCode: {
|
||||
title: "邀请码",
|
||||
type: "text",
|
||||
column: { width: 160 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,6 +15,11 @@ export default function (): CreateCrudOptionsRet {
|
||||
toolbar: { show: false },
|
||||
rowHandle: { show: false },
|
||||
columns: {
|
||||
createTime: {
|
||||
title: "时间",
|
||||
type: "datetime",
|
||||
column: { width: 180 },
|
||||
},
|
||||
amount: {
|
||||
title: "收益金额",
|
||||
type: "number",
|
||||
@@ -56,11 +61,6 @@ export default function (): CreateCrudOptionsRet {
|
||||
type: "text",
|
||||
column: { minWidth: 220 },
|
||||
},
|
||||
createTime: {
|
||||
title: "时间",
|
||||
type: "datetime",
|
||||
column: { width: 180 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
<div v-if="loaded && enabled && inviteInfo.enabled" class="invite-body">
|
||||
<div class="invite-summary-grid">
|
||||
<div v-for="item in summaryCards" :key="item.key" class="summary-card">
|
||||
<div class="summary-title">{{ item.title }}</div>
|
||||
<div class="summary-value" :class="item.className">{{ item.value }}</div>
|
||||
<div class="summary-card-main">
|
||||
<div class="summary-title">{{ item.title }}</div>
|
||||
<div class="summary-value" :class="item.className">{{ item.value }}</div>
|
||||
</div>
|
||||
<div v-if="item.key === 'totalIncome'" class="withdraw-action">
|
||||
<div class="withdraw-available">可提现 {{ moneyText(inviteInfo.wallet?.availableAmount) }}</div>
|
||||
<a-button class="summary-action-button" type="primary" @click="gotoWallet">提现</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,14 +53,18 @@
|
||||
<span class="info-label">我的等级</span>
|
||||
<div class="info-content level-info-content">
|
||||
<span class="level-name-text">{{ inviteInfo.currentLevel?.name || "未设置" }}</span>
|
||||
<span v-if="inviteInfo.currentLevel" class="current-level-rate">{{ inviteInfo.currentLevel.commissionRate }}%</span>
|
||||
<span v-if="inviteInfo.currentLevel" class="current-level-rate">
|
||||
<span class="current-level-rate-label">返佣比例</span>
|
||||
<span class="current-level-rate-value">{{ inviteInfo.currentLevel.commissionRate }}%</span>
|
||||
</span>
|
||||
<span v-if="inviteInfo.currentLevel" class="level-rate-desc">好友付费后按此比例计算佣金</span>
|
||||
</div>
|
||||
<fs-icon class="level-open-icon" icon="ion:chevron-forward-outline" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-tabs v-model:active-key="activeTab" class="invite-tabs" @change="handleTabChange">
|
||||
<a-tab-pane key="invitees" tab="推广成功用户">
|
||||
<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="收益记录">
|
||||
@@ -72,6 +82,17 @@
|
||||
|
||||
<a-modal v-model:open="levelDialogOpen" title="推广等级" width="820px" wrap-class-name="invite-level-modal" :footer="null">
|
||||
<div class="level-modal-subtitle">推广越多,等级越高,返佣比例越高</div>
|
||||
<div class="level-progress-box">
|
||||
<div>
|
||||
<div class="level-progress-label">当前累计推广金额</div>
|
||||
<div class="level-progress-value">¥ {{ amountToYuan(inviteInfo.summary.promotionAmount) }}</div>
|
||||
</div>
|
||||
<div class="level-progress-desc">
|
||||
<template v-if="inviteInfo.nextLevel">距离下一等级「{{ inviteInfo.nextLevel.name }}」还差 {{ amountToYuan(inviteInfo.nextLevel.gapAmount) }} 元</template>
|
||||
<template v-else-if="inviteInfo.currentLevel?.levelType === 'exclusive'">当前为专属等级,不参与自动升级</template>
|
||||
<template v-else>已达到当前可自动升级的最高等级</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-card-grid modal-level-grid">
|
||||
<div v-for="level in visibleLevels" :key="level.id" class="level-card" :class="{ active: level.id === inviteInfo.currentLevel?.id }">
|
||||
<div class="level-name">
|
||||
@@ -83,7 +104,8 @@
|
||||
</div>
|
||||
<div class="level-rate-label">佣金比例</div>
|
||||
<div class="level-rate">{{ level.commissionRate }}%</div>
|
||||
<div class="level-threshold">累计推广 ≥ {{ amountToYuan(level.minAmount) }} 元</div>
|
||||
<div v-if="level.levelType === 'exclusive'" class="level-threshold exclusive-threshold">平台指定专属等级</div>
|
||||
<div v-else class="level-threshold">累计推广 ≥ {{ amountToYuan(level.minAmount) }} 元</div>
|
||||
<a-tag v-if="level.id === inviteInfo.currentLevel?.id" class="current-tag" color="blue">当前等级</a-tag>
|
||||
<div v-else-if="level.id === inviteInfo.nextLevel?.id" class="next-gap">还差 {{ amountToYuan(inviteInfo.nextLevel.gapAmount) }}</div>
|
||||
</div>
|
||||
@@ -119,6 +141,7 @@
|
||||
import { computed, nextTick, onActivated, onMounted, reactive, ref } from "vue";
|
||||
import { FsIcon, useFs } from "@fast-crud/fast-crud";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import * as api from "./api";
|
||||
import createInviteesCrudOptions from "./crud-invitees";
|
||||
import createLogsCrudOptions from "./crud-logs";
|
||||
@@ -127,6 +150,7 @@ import { util } from "/@/utils";
|
||||
|
||||
defineOptions({ name: "InviteCommission" });
|
||||
|
||||
const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const enabled = ref(false);
|
||||
const activeTab = ref("invitees");
|
||||
@@ -144,6 +168,7 @@ const inviteInfo = reactive<any>({
|
||||
inviteLink: "",
|
||||
agreementContent: "",
|
||||
summary: { totalIncomeAmount: 0, monthIncomeAmount: 0, promotionAmount: 0, inviteeCount: 0 },
|
||||
wallet: { availableAmount: 0 },
|
||||
currentLevel: null,
|
||||
nextLevel: null,
|
||||
levelList: [],
|
||||
@@ -173,6 +198,12 @@ const summaryCards = computed(() => [
|
||||
value: moneyText(inviteInfo.summary.monthIncomeAmount),
|
||||
className: "income",
|
||||
},
|
||||
{
|
||||
key: "promotionAmount",
|
||||
title: "累计推广金额",
|
||||
value: moneyText(inviteInfo.summary.promotionAmount),
|
||||
className: "promotion",
|
||||
},
|
||||
{
|
||||
key: "inviteeCount",
|
||||
title: "已推广人数",
|
||||
@@ -191,6 +222,10 @@ function levelIcon(level: any) {
|
||||
return level?.icon || "ion:ribbon-outline";
|
||||
}
|
||||
|
||||
function gotoWallet() {
|
||||
router.push({ path: "/certd/wallet" });
|
||||
}
|
||||
|
||||
function openAgreementDialog(needOpenPlan: boolean) {
|
||||
agreementDialogNeedOpen.value = needOpenPlan;
|
||||
agreementAgree.value = false;
|
||||
@@ -326,7 +361,7 @@ onActivated(async () => {
|
||||
|
||||
.invite-summary-grid {
|
||||
flex: none;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
@@ -351,11 +386,19 @@ onActivated(async () => {
|
||||
|
||||
.summary-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 112px;
|
||||
overflow: hidden;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.summary-card-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
margin-bottom: 10px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
@@ -376,6 +419,30 @@ onActivated(async () => {
|
||||
color: #3478f6;
|
||||
}
|
||||
|
||||
.summary-value.promotion {
|
||||
color: #16a085;
|
||||
}
|
||||
|
||||
.summary-action-button {
|
||||
flex: none;
|
||||
min-width: 72px;
|
||||
}
|
||||
|
||||
.withdraw-action {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.withdraw-available {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.invite-link-panel {
|
||||
flex: none;
|
||||
padding: 16px 18px;
|
||||
@@ -450,22 +517,56 @@ onActivated(async () => {
|
||||
}
|
||||
|
||||
.current-level-rate {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: none;
|
||||
height: 26px;
|
||||
margin-left: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(197, 138, 53, 0.22);
|
||||
border-radius: 6px;
|
||||
background: rgba(197, 138, 53, 0.08);
|
||||
color: #c58a35;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.current-level-rate-label {
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
border-right: 1px solid rgba(197, 138, 53, 0.18);
|
||||
background: rgba(197, 138, 53, 0.1);
|
||||
color: #8a5a16;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.current-level-rate-value {
|
||||
padding: 0 8px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.level-info-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.level-name-text {
|
||||
flex: none;
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.level-rate-desc {
|
||||
min-width: 180px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.level-open-icon {
|
||||
flex: none;
|
||||
color: hsl(var(--muted-foreground));
|
||||
@@ -608,6 +709,10 @@ onActivated(async () => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exclusive-threshold {
|
||||
color: #8a5a16;
|
||||
}
|
||||
|
||||
.current-tag {
|
||||
display: table;
|
||||
margin: 10px auto 0;
|
||||
@@ -622,6 +727,36 @@ onActivated(async () => {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.level-progress-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid rgba(52, 120, 246, 0.16);
|
||||
border-radius: 8px;
|
||||
background: rgba(248, 250, 252, 0.86);
|
||||
}
|
||||
|
||||
.level-progress-label {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.level-progress-value {
|
||||
margin-top: 2px;
|
||||
color: #16a085;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.level-progress-desc {
|
||||
color: #3478f6;
|
||||
font-size: 13px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.invite-agreement-content {
|
||||
max-height: 360px;
|
||||
padding: 12px;
|
||||
@@ -680,6 +815,15 @@ onActivated(async () => {
|
||||
.level-card-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.level-progress-box {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.level-progress-desc {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,20 +4,33 @@
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{ $t("certd.order.package") }}:</span>{{ product.title }}
|
||||
</div>
|
||||
<div class="flex-o mt-5">
|
||||
<div v-if="product.intro" class="flex-o mt-5">
|
||||
<span class="label">{{ $t("certd.order.description") }}:</span>{{ product.intro }}
|
||||
</div>
|
||||
<div class="flex-o mt-5">
|
||||
<div class="order-spec-row mt-5">
|
||||
<span class="label">{{ $t("certd.order.specifications") }}:</span>
|
||||
<span class="flex-o flex-wrap">
|
||||
<span class="flex-o"> {{ $t("certd.order.pipeline") }}<suite-value class="ml-5" :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />; </span>
|
||||
<span class="flex-o"> {{ $t("certd.order.totalDomain") }}<suite-value class="ml-5" :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />; </span>
|
||||
<span class="flex-o" style="padding-left: 2em">
|
||||
- {{ $t("certd.order.includedWildcardDomain") }}<suite-value class="ml-5" :model-value="product.content.maxWildcardDomainCount" :unit="$t('certd.order.unit.count')" />;
|
||||
</span>
|
||||
<span class="flex-o"> {{ $t("certd.order.deployTimes") }}<suite-value class="ml-5" :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||
<span class="flex-o"> {{ $t("certd.order.monitorCount") }}<suite-value class="ml-5" :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />; </span>
|
||||
</span>
|
||||
<div class="spec-grid">
|
||||
<div class="spec-item">
|
||||
<div class="spec-name">{{ $t("certd.order.totalDomain") }}</div>
|
||||
<suite-value :model-value="product.content.maxDomainCount" :unit="$t('certd.order.unit.count')" />
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<div class="spec-name">{{ $t("certd.order.includedWildcardDomain") }}</div>
|
||||
<suite-value :model-value="product.content.maxWildcardDomainCount" :unit="$t('certd.order.unit.count')" />
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<div class="spec-name">{{ $t("certd.order.pipeline") }}</div>
|
||||
<suite-value :model-value="product.content.maxPipelineCount" :unit="$t('certd.order.unit.pieces')" />
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<div class="spec-name">{{ $t("certd.order.deployTimes") }}</div>
|
||||
<suite-value :model-value="product.content.maxDeployCount" :unit="$t('certd.order.unit.times')" />
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<div class="spec-name">{{ $t("certd.order.monitorCount") }}</div>
|
||||
<suite-value :model-value="product.content.maxMonitorCount" :unit="$t('certd.order.unit.times')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-o mt-5">
|
||||
@@ -26,10 +39,10 @@
|
||||
</div>
|
||||
<div class="flex-o mt-5">
|
||||
<span class="label">{{ $t("certd.order.price") }}:</span>
|
||||
<price-input :edit="false" :model-value="durationSelected.price" zero-text="免费"></price-input>
|
||||
<price-input :edit="false" :model-value="durationSelected.price" zero-text="0元"></price-input>
|
||||
</div>
|
||||
<div v-if="durationSelected.price > 0 && wallet.availableAmount > 0" class="flex-o mt-5">
|
||||
<span class="label">返利抵扣:</span>
|
||||
<span class="label">余额抵扣:</span>
|
||||
<a-switch v-model:checked="formRef.useRebateBalance" />
|
||||
<span class="ml-10">可用 {{ amountToYuan(wallet.availableAmount) }} 元,预计抵扣 {{ amountToYuan(expectedRebateAmount) }} 元</span>
|
||||
</div>
|
||||
@@ -58,7 +71,7 @@ import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import qrcode from "qrcode";
|
||||
import * as api from "/@/views/certd/suite/api";
|
||||
import { GetMyInvite } from "/@/views/certd/invite/api";
|
||||
import { GetWalletSummary } from "/@/views/certd/wallet/api";
|
||||
import { util } from "/@/utils";
|
||||
const openRef = ref(false);
|
||||
|
||||
@@ -77,8 +90,7 @@ async function open(opts: OrderModalOpenReq) {
|
||||
formRef.value.num = opts.num ?? 1;
|
||||
formRef.value.useRebateBalance = false;
|
||||
try {
|
||||
const inviteInfo: any = await GetMyInvite();
|
||||
wallet.value = inviteInfo.wallet || { availableAmount: 0 };
|
||||
wallet.value = await GetWalletSummary();
|
||||
} catch (e) {
|
||||
wallet.value = { availableAmount: 0 };
|
||||
}
|
||||
@@ -261,6 +273,10 @@ defineExpose({
|
||||
</script>
|
||||
<style lang="less">
|
||||
.order-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.label {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
@@ -268,6 +284,37 @@ defineExpose({
|
||||
color: #686868;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.order-spec-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.spec-grid {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.spec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 42px;
|
||||
padding: 8px 10px;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(52, 120, 246, 0.12);
|
||||
border-radius: 8px;
|
||||
background: rgba(248, 250, 252, 0.86);
|
||||
}
|
||||
|
||||
.spec-name {
|
||||
color: hsl(var(--foreground));
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-confirm-center {
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
<div class="flex-o duration-label">时长</div>
|
||||
<div class="duration-list">
|
||||
<div v-for="dp of product.durationPrices" :key="dp.duration" class="duration-item" :class="{ active: selected.duration === dp.duration }" @click="selected = dp">
|
||||
{{ durationDict.dataMap[dp.duration]?.label }}
|
||||
<span class="duration-text">{{ durationDict.dataMap[dp.duration]?.label }}</span>
|
||||
<span v-if="discountText(dp)" class="duration-discount">{{ discountText(dp) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,7 +49,7 @@
|
||||
<div class="flex-o">价格</div>
|
||||
<div class="flex-o price-text">
|
||||
<price-input style="color: red" :font-size="20" :model-value="selected?.price" :edit="false" zero-text="免费" />
|
||||
<span class="ml-5" style="font-size: 12px"> / {{ durationDict.dataMap[selected.duration]?.label }}</span>
|
||||
<span class="price-unit">/ {{ durationDict.dataMap[selected.duration]?.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +62,7 @@
|
||||
import { durationDict } from "/@/views/certd/suite/api";
|
||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { dict, FsIcon } from "@fast-crud/fast-crud";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -69,6 +70,20 @@ const props = defineProps<{
|
||||
}>();
|
||||
const selected = ref(props.product.durationPrices[0]);
|
||||
|
||||
const originalUnitPrice = computed(() => {
|
||||
const unitPrices = (props.product.durationPrices || [])
|
||||
.map((item: any) => {
|
||||
const duration = Number(item.duration);
|
||||
const price = Number(item.price);
|
||||
if (!duration || duration <= 0 || price <= 0) {
|
||||
return null;
|
||||
}
|
||||
return price / duration;
|
||||
})
|
||||
.filter((item: number | null): item is number => item != null);
|
||||
return Math.max(...unitPrices, 0);
|
||||
});
|
||||
|
||||
const productTypeDictRef = dict({
|
||||
data: [
|
||||
{ value: "suite", label: "套餐", color: "green" },
|
||||
@@ -77,6 +92,20 @@ const productTypeDictRef = dict({
|
||||
});
|
||||
|
||||
const emit = defineEmits(["order"]);
|
||||
function discountText(durationPrice: any) {
|
||||
const duration = Number(durationPrice.duration);
|
||||
const price = Number(durationPrice.price);
|
||||
if (!duration || duration <= 0 || price <= 0 || originalUnitPrice.value <= 0) {
|
||||
return "";
|
||||
}
|
||||
const currentUnitPrice = price / duration;
|
||||
const discount = Math.round((currentUnitPrice / originalUnitPrice.value) * 100) / 10;
|
||||
if (discount >= 10) {
|
||||
return "";
|
||||
}
|
||||
return `${discount}折`;
|
||||
}
|
||||
|
||||
async function doOrder() {
|
||||
emit("order", { product: props.product, productId: props.product.id, duration: selected.value.duration, price: selected.value.price });
|
||||
}
|
||||
@@ -108,13 +137,21 @@ async function doOrder() {
|
||||
.duration-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
.duration-item {
|
||||
width: 45px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
min-width: 56px;
|
||||
height: 32px;
|
||||
border: 1px solid #cdcdcd;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
padding: 3px 6px;
|
||||
cursor: pointer;
|
||||
line-height: 16px;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
@@ -124,6 +161,44 @@ async function doOrder() {
|
||||
background-color: #c1eafb;
|
||||
}
|
||||
}
|
||||
|
||||
.duration-text {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.duration-discount {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
right: -7px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 8px 8px 8px 2px;
|
||||
background: #ff4d4f;
|
||||
color: #f5222d;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 6px rgba(245, 34, 45, 0.24);
|
||||
}
|
||||
}
|
||||
|
||||
.price-text {
|
||||
flex: none;
|
||||
align-items: baseline;
|
||||
justify-content: flex-end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
flex: none;
|
||||
margin-left: 5px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,11 +26,11 @@ export async function UpdateObj(obj: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function DelObj(id: any) {
|
||||
export async function CancelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
url: apiPrefix + "/cancel",
|
||||
method: "post",
|
||||
params: { id },
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,14 +50,6 @@ export async function GetDetail(id: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function DeleteBatch(ids: any[]) {
|
||||
return await request({
|
||||
url: apiPrefix + "/deleteByIds",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
export async function SyncStatus(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/syncStatus",
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
import SuiteValue from "/@/views/sys/suite/product/suite-value.vue";
|
||||
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
@@ -21,56 +13,34 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const res = await api.UpdateObj(form);
|
||||
return res;
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
plugins: {
|
||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
||||
rowSelection: {
|
||||
enabled: true,
|
||||
order: -2,
|
||||
before: true,
|
||||
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
||||
props: {
|
||||
multiple: true,
|
||||
crossPage: true,
|
||||
selectedRowKeys,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 240,
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
edit: { show: false },
|
||||
copy: { show: false },
|
||||
remove: { show: false },
|
||||
syncStatus: {
|
||||
show: compute(({ row }) => {
|
||||
return row.status === "wait_pay";
|
||||
}),
|
||||
text: "同步订单状态",
|
||||
title: "同步订单状态",
|
||||
text: null,
|
||||
tooltip: { title: "同步订单状态" },
|
||||
icon: "ant-design:sync-outlined",
|
||||
type: "link",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
@@ -83,6 +53,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
});
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
show: compute(({ row }) => {
|
||||
return row.status === "wait_pay";
|
||||
}),
|
||||
title: "取消订单",
|
||||
text: null,
|
||||
tooltip: { title: "取消订单" },
|
||||
icon: "ion:close-circle-outline",
|
||||
type: "link",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
title: "确认取消订单?",
|
||||
content: "取消后订单会关闭,已冻结的余额抵扣金额将自动退回。",
|
||||
okText: "确认取消",
|
||||
cancelText: "再想想",
|
||||
onOk: async () => {
|
||||
await api.CancelObj(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actionbar: {
|
||||
@@ -152,7 +144,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
},
|
||||
rebateAmount: {
|
||||
title: "返利抵扣",
|
||||
title: "余额抵扣",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 110,
|
||||
@@ -201,7 +193,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
{ label: "支付宝", value: "alipay" },
|
||||
{ label: "微信", value: "wxpay" },
|
||||
{ label: "免费", value: "free" },
|
||||
{ label: "返利余额", value: "rebate" },
|
||||
{ label: "余额抵扣", value: "rebate" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
<template #header>
|
||||
<div class="title">我的订单</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
@@ -17,31 +11,11 @@
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "MyTrade",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
||||
|
||||
const selectedRowKeys = context.selectedRowKeys;
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info("删除成功");
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error("请先勾选记录");
|
||||
}
|
||||
};
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
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";
|
||||
import { util } from "/@/utils";
|
||||
|
||||
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 pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
@@ -24,6 +32,7 @@ export default function (): CreateCrudOptionsRet {
|
||||
{ label: "收益入账", value: "income", color: "success" },
|
||||
{ label: "余额抵扣", value: "consume", color: "default" },
|
||||
{ label: "提现冻结", value: "withdraw_freeze", color: "warning" },
|
||||
{ label: "提现成功", value: "withdraw", color: "success" },
|
||||
{ label: "提现成功", value: "withdraw_success", color: "success" },
|
||||
{ label: "提现退回", value: "withdraw_reject", color: "processing" },
|
||||
],
|
||||
@@ -35,7 +44,10 @@ export default function (): CreateCrudOptionsRet {
|
||||
type: "number",
|
||||
column: {
|
||||
width: 120,
|
||||
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||
cellRender({ value }) {
|
||||
const amount = Number(value || 0);
|
||||
return <span class={amount < 0 ? "text-green-500" : "text-red-500"}>{moneyText(amount)}</span>;
|
||||
},
|
||||
},
|
||||
},
|
||||
balanceAfter: {
|
||||
@@ -43,7 +55,9 @@ export default function (): CreateCrudOptionsRet {
|
||||
type: "number",
|
||||
column: {
|
||||
width: 130,
|
||||
component: { name: PriceInput, vModel: "modelValue", edit: false },
|
||||
cellRender({ value }) {
|
||||
return <span class="text-red-500">{moneyText(Number(value || 0))}</span>;
|
||||
},
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function (): CreateCrudOptionsRet {
|
||||
title: "升级金额",
|
||||
type: "number",
|
||||
form: {
|
||||
show: compute(({ form }) => form.levelType !== "exclusive"),
|
||||
component: { name: PriceInput, vModel: "modelValue", edit: true },
|
||||
rules: [{ required: true, message: "请输入升级金额" }],
|
||||
},
|
||||
@@ -100,7 +101,7 @@ export default function (): CreateCrudOptionsRet {
|
||||
}),
|
||||
form: {
|
||||
value: "normal",
|
||||
helper: "专属等级可由管理员手动指定,不参与普通用户自动升级。",
|
||||
helper: "专属等级可由管理员手动指定,不参与普通用户自动升级。专属等级不会在普通用户端展示。",
|
||||
},
|
||||
column: { width: 120, align: "center" },
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
initialForm: {
|
||||
userId: row.userId,
|
||||
levelId: row.levelId,
|
||||
levelLocked: row.levelLocked === true,
|
||||
},
|
||||
columns: {
|
||||
levelId: {
|
||||
@@ -33,20 +32,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
rules: [{ required: true, message: "请选择推广等级" }],
|
||||
},
|
||||
},
|
||||
levelLocked: {
|
||||
title: "锁定等级",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "自动升级", value: false, color: "success" },
|
||||
{ label: "锁定", value: true, color: "warning" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
col: { span: 24 },
|
||||
helper: "专属等级会自动锁定,不参与自动升级。",
|
||||
helper: "专属等级将锁定为当前等级,普通等级将按累计推广金额自动升级。",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -80,8 +66,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
},
|
||||
columns: {
|
||||
userId: { title: "用户ID", type: "number", search: { show: true }, column: { width: 100 } },
|
||||
username: { title: "用户名", type: "text", search: { show: true }, column: { width: 160 } },
|
||||
userDisplay: { title: "显示名称", type: "text", column: { width: 160 } },
|
||||
username: {
|
||||
title: "用户名",
|
||||
type: "text",
|
||||
search: { show: true },
|
||||
column: {
|
||||
width: 180,
|
||||
cellRender({ row }) {
|
||||
return row.simpleUser?.displayName || row.userDisplay || row.username || row.userId;
|
||||
},
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
title: "开通状态",
|
||||
type: "dict-switch",
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
</div>
|
||||
<div class="level-rate-label">佣金比例</div>
|
||||
<div class="level-rate">{{ item.commissionRate || 0 }}%</div>
|
||||
<div class="level-threshold">累计推广 ≥ {{ amountToYuan(item.minAmount) }} 元</div>
|
||||
<div v-if="item.levelType === 'exclusive'" class="level-threshold exclusive-threshold">平台指定专属等级</div>
|
||||
<div v-else class="level-threshold">累计推广 ≥ {{ amountToYuan(item.minAmount) }} 元</div>
|
||||
<div class="level-meta">
|
||||
<a-tag :color="item.disabled ? 'default' : 'success'">{{ item.disabled ? "已禁用" : "已启用" }}</a-tag>
|
||||
<span>排序 {{ item.sort || 0 }}</span>
|
||||
@@ -232,6 +233,10 @@ onActivated(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exclusive-threshold {
|
||||
color: #8a5a16;
|
||||
}
|
||||
|
||||
.level-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -26,11 +26,11 @@ export async function UpdateObj(obj: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function DelObj(id: any) {
|
||||
export async function CancelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
url: apiPrefix + "/cancel",
|
||||
method: "post",
|
||||
params: { id },
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,14 +50,6 @@ export async function GetDetail(id: any) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function DeleteBatch(ids: any[]) {
|
||||
return await request({
|
||||
url: apiPrefix + "/deleteByIds",
|
||||
method: "post",
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
export async function UpdatePaid(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/updatePaid",
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import DurationValue from "/@/views/sys/suite/product/duration-value.vue";
|
||||
import PriceInput from "/@/views/sys/suite/product/price-input.vue";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
@@ -20,43 +13,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const res = await api.UpdateObj(form);
|
||||
return res;
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
return await api.DelObj(row.id);
|
||||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
plugins: {
|
||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
||||
rowSelection: {
|
||||
enabled: true,
|
||||
order: -99,
|
||||
before: true,
|
||||
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
||||
props: {
|
||||
multiple: true,
|
||||
crossPage: true,
|
||||
selectedRowKeys,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
@@ -67,7 +34,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
toolbar: { show: false },
|
||||
rowHandle: {
|
||||
width: 320,
|
||||
width: 150,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: {
|
||||
@@ -79,11 +46,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
edit: {
|
||||
show: false,
|
||||
},
|
||||
remove: {
|
||||
show: false,
|
||||
},
|
||||
syncStatus: {
|
||||
show: compute(({ row }) => {
|
||||
return row.status === "wait_pay";
|
||||
}),
|
||||
text: "同步订单状态",
|
||||
title: "同步订单状态",
|
||||
text: null,
|
||||
tooltip: { title: "同步订单状态" },
|
||||
icon: "ant-design:sync-outlined",
|
||||
type: "link",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
@@ -96,11 +69,36 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
});
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
show: compute(({ row }) => {
|
||||
return row.status === "wait_pay";
|
||||
}),
|
||||
title: "取消订单",
|
||||
text: null,
|
||||
tooltip: { title: "取消订单" },
|
||||
icon: "ion:close-circle-outline",
|
||||
type: "link",
|
||||
click({ row }) {
|
||||
Modal.confirm({
|
||||
title: "确认取消订单?",
|
||||
content: "取消后订单会关闭,已冻结的余额抵扣金额将自动退回。",
|
||||
okText: "确认取消",
|
||||
cancelText: "再想想",
|
||||
onOk: async () => {
|
||||
await api.CancelObj(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
updatePaid: {
|
||||
show: compute(({ row }) => {
|
||||
return row.status === "wait_pay";
|
||||
}),
|
||||
text: "确认已支付",
|
||||
title: "确认已支付",
|
||||
text: null,
|
||||
tooltip: { title: "确认已支付" },
|
||||
icon: "ant-design:check-circle-outlined",
|
||||
type: "link",
|
||||
click({ row }) {
|
||||
Modal.confirm({
|
||||
@@ -175,7 +173,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
},
|
||||
rebateAmount: {
|
||||
title: "返利抵扣",
|
||||
title: "余额抵扣",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 110,
|
||||
@@ -224,7 +222,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
{ label: "支付宝", value: "alipay" },
|
||||
{ label: "微信", value: "wxpay" },
|
||||
{ label: "免费", value: "free" },
|
||||
{ label: "返利余额", value: "rebate" },
|
||||
{ label: "余额抵扣", value: "rebate" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
|
||||
@@ -6,13 +6,7 @@
|
||||
<span class="sub"> </span>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
@@ -20,31 +14,11 @@
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "./api";
|
||||
|
||||
defineOptions({
|
||||
name: "TradeManager",
|
||||
});
|
||||
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
|
||||
|
||||
const selectedRowKeys = context.selectedRowKeys;
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedRowKeys.value?.length > 0) {
|
||||
Modal.confirm({
|
||||
title: "确认",
|
||||
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
|
||||
async onOk() {
|
||||
await DeleteBatch(selectedRowKeys.value);
|
||||
message.info("删除成功");
|
||||
crudExpose.doRefresh();
|
||||
selectedRowKeys.value = [];
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error("请先勾选记录");
|
||||
}
|
||||
};
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user