feat(manager): 优化列表操作列规范、会员分组统计、漏斗图重构与订单交互

1. 订单交互:移除 orderList 缓存机制修复默认带入订单号问题,支持 query 联动;

2. 会员模块:会员分组新增人数统计与联动,全量规范会员提现、充值、日志等操作列样式;

3. 商品与销量:虚拟销量优化首列缩略图与对齐方式,规范品牌与商品分组操作;

4. 数据统计:重构交易转化漏斗图为自适应 SVG 渐变图与指标卡片;

5. 系统与营销:统一菜单/部门操作列样式,优化 OSS 模块、营销活动与首页待办对齐。
This commit is contained in:
pikachu1995@126.com
2026-10-30 06:51:48 +08:00
parent 2563f122fc
commit c0cc8d8598
39 changed files with 16121 additions and 6705 deletions

View File

@@ -1,20 +1,29 @@
<template>
<div class="search">
<el-card>
<div class="member-recharge-page search">
<!-- 顶部筛选表单 -->
<el-card shadow="never" class="filter-card">
<el-form
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
label-width="76px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="客户ID" prop="memberId">
<el-input
v-model="searchForm.memberId"
placeholder="请输入客户ID"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="客户名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入客户名称"
clearable
style="width: 240px"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="充值单号" prop="rechargeSn">
@@ -22,59 +31,106 @@
v-model="searchForm.rechargeSn"
placeholder="请输入充值单号"
clearable
style="width: 240px"
style="width: 190px"
/>
</el-form-item>
<el-form-item label="支付时间">
<el-form-item label="充值时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择起始时间"
style="width: 360px"
style="width: 320px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-form-item class="form-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">
搜索
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<!-- 充值数据表格 -->
<el-card shadow="never" class="table-card mt_10">
<template #header>
<div class="card-header-bar">
<div class="header-title">
<span>会员充值记录</span>
</div>
<div class="header-actions">
<el-button size="small" @click="getDataList">刷新</el-button>
</div>
</div>
</template>
<el-table
ref="table"
v-loading="loading"
border
stripe
:data="data"
ref="table"
class="mt_10"
style="width: 100%"
>
<el-table-column prop="memberName" label="客户名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="rechargeSn" label="订单号" min-width="180" show-overflow-tooltip />
<el-table-column label="充值金额" width="160" sortable>
<!-- 客户ID -->
<el-table-column prop="memberId" label="客户ID" min-width="170" show-overflow-tooltip />
<!-- 客户名称 -->
<el-table-column prop="memberName" label="客户名称" min-width="140" show-overflow-tooltip />
<!-- 充值单号 -->
<el-table-column prop="rechargeSn" label="充值单号" min-width="180" show-overflow-tooltip />
<!-- 充值金额 -->
<el-table-column label="充值金额" width="140" align="right" sortable prop="rechargeMoney">
<template #default="{ row }">
<priceColorScheme v-if="row" :value="row.rechargeMoney" :color="$mainColor" unit="+" />
<span v-if="row" class="recharge-money-text">
+¥ {{ formatMoney(row.rechargeMoney) }}
</span>
</template>
</el-table-column>
<el-table-column label="充值方式" width="120">
<!-- 充值方式 -->
<el-table-column label="充值方式" width="120" align="center">
<template #default="{ row }">
<span v-if="row">{{ rechargeWayText(row.rechargeWay) }}</span>
{{ row ? rechargeWayText(row.rechargeWay) : "-" }}
</template>
</el-table-column>
<el-table-column label="支付状态" width="120">
<!-- 支付状态 -->
<el-table-column label="支付状态" width="110" align="center">
<template #default="{ row }">
<el-tag v-if="row" :type="row.payStatus === 'PAID' ? 'success' : 'danger'">
{{ row.payStatus === "PAID" ? "已付款" : "未付款" }}
</el-tag>
{{ row ? (row.payStatus === "PAID" ? "已付款" : "未付款") : "-" }}
</template>
</el-table-column>
<!-- 第三方流水号 -->
<el-table-column prop="receivableNo" label="第三方流水号" min-width="180" show-overflow-tooltip />
<!-- 充值时间 -->
<el-table-column prop="createTime" label="充值时间" width="170" align="center" />
<!-- 支付时间 -->
<el-table-column prop="payTime" label="支付时间" width="170" align="center" />
<!-- 操作 -->
<el-table-column label="操作" width="110" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a v-if="row.memberId" class="link-text" @click="viewMemberDetail(row)">客户详情</a>
<span v-else>-</span>
</template>
</template>
</el-table-column>
<el-table-column prop="createTime" label="充值时间" width="190" />
<el-table-column prop="payTime" label="支付时间" width="190" />
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<!-- 底部分页 -->
<div class="pagination-wrap mt_10">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
@@ -91,49 +147,85 @@
</template>
<script setup>
import { ref, onMounted } from "vue";
import { onMounted, ref } from "vue";
import { useTable } from "@/utils/useTable";
import { getUserRecharge } from "@/api/member";
import { customRouterPush } from "@/utils/filters";
import { ElMessage } from "element-plus";
defineOptions({ name: "recharge" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const selectDate = ref(null);
const { loading, data, total, searchForm, getDataList } = useTable(
const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = useTable(
getUserRecharge,
{
pageSize: 20,
immediate: false,
form: { sort: "createTime", order: "desc", startDate: "", endDate: "", memberName: "" },
form: {
sort: "createTime",
order: "desc",
memberId: "",
memberName: "",
rechargeSn: "",
startDate: "",
endDate: "",
},
beforeRequest: (p) => {
const params = { ...p };
if (selectDate.value && selectDate.value.length === 2) {
params.startDate = selectDate.value[0];
params.endDate = selectDate.value[1];
} else {
params.startDate = "";
params.endDate = "";
}
return params;
},
}
);
function rechargeWayText(way) {
if (way === "ALIPAY") return "支付宝";
if (way === "WECHAT") return "微信";
if (way === "WECHAT") return "微信支付";
if (way === "BANK_TRANSFER") return "线下转账";
return "";
return way || "-";
}
function init() {
getDataList();
function formatMoney(val) {
const num = Number(val || 0);
return num.toFixed(2);
}
function viewMemberDetail(row) {
if (!row || !row.memberId) return;
customRouterPush({ name: "member-detail", query: { id: row.memberId } });
}
function handleReset() {
selectDate.value = null;
searchForm.memberId = "";
searchForm.memberName = "";
searchForm.rechargeSn = "";
searchForm.startDate = "";
searchForm.endDate = "";
searchForm.pageNumber = 1;
resetSearch();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function selectDateRange(v) {
if (v && v.length === 2) {
searchForm.startDate = v[0];
@@ -145,6 +237,84 @@ function selectDateRange(v) {
}
onMounted(() => {
init();
getDataList();
});
</script>
<style lang="scss" scoped>
.member-recharge-page {
padding: 16px 20px;
background-color: #f6f8fa;
min-height: calc(100vh - 110px);
}
.font-mono {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.font-semibold {
font-weight: 600;
}
.text-dark {
color: #303133;
}
.text-muted {
color: #909399;
}
/* 筛选卡片 */
.filter-card {
border-radius: 8px;
border: 1px solid #ebeef5;
:deep(.el-card__body) {
padding: 16px 16px 2px;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
.el-form-item {
margin-bottom: 14px;
margin-right: 16px;
}
.form-actions {
margin-left: auto;
}
}
}
/* 表格卡片 */
.table-card {
border-radius: 8px;
border: 1px solid #ebeef5;
.card-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
}
.recharge-money-text {
font-size: 14px;
color: #67c23a;
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
padding-top: 6px;
}
</style>

View File

@@ -1,75 +1,123 @@
<template>
<div class="search">
<el-card>
<div class="member-wallet-log-page search">
<!-- 顶部筛选表单 -->
<el-card shadow="never" class="filter-card">
<el-form
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
label-width="76px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="客户ID" prop="memberId">
<el-input
v-model="searchForm.memberId"
placeholder="请输入客户ID"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="客户名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入客户名称"
clearable
style="width: 240px"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="支付时间">
<el-form-item label="变动时间">
<el-date-picker
v-model="selectDate"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择起始时间"
style="width: 360px"
style="width: 320px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
<el-form-item class="form-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">
搜索
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<!-- 变动记录表格 -->
<el-card shadow="never" class="table-card mt_10">
<template #header>
<div class="card-header-bar">
<div class="header-title">
<span>预存款变动明细</span>
</div>
<div class="header-actions">
<el-button size="small" @click="getDataList">刷新</el-button>
</div>
</div>
</template>
<el-table
ref="table"
v-loading="loading"
border
stripe
:data="data"
ref="table"
class="mt_10"
style="width: 100%"
>
<el-table-column prop="memberName" label="客户名称" min-width="100" />
<el-table-column label="变动金额" width="150">
<!-- 客户ID -->
<el-table-column prop="memberId" label="客户ID" min-width="190" show-overflow-tooltip />
<!-- 客户名称 -->
<el-table-column prop="memberName" label="客户名称" min-width="140" show-overflow-tooltip />
<!-- 变动金额 -->
<el-table-column label="变动金额" width="140" align="right" sortable prop="money">
<template #default="{ row }">
<template v-if="row">
<priceColorScheme
v-if="row.money > 0"
:value="row.money"
color="green"
/>
<priceColorScheme
v-else-if="row.money < 0"
:value="row.money"
:color="$mainColor"
/>
<span v-if="row.money > 0" class="money-increase">
+¥ {{ formatMoney(row.money) }}
</span>
<span v-else-if="row.money < 0" class="money-decrease">
-¥ {{ formatMoney(Math.abs(row.money)) }}
</span>
<span v-else class="money-zero">
¥ 0.00
</span>
</template>
</template>
</el-table-column>
<el-table-column prop="createTime" label="变更时间" width="200" />
<el-table-column label="业务类型" width="200">
<!-- 业务类型 -->
<el-table-column label="业务类型" width="120" align="center">
<template #default="{ row }">
<span v-if="row">{{ serviceTypeText(row.serviceType) }}</span>
{{ row ? serviceTypeText(row.serviceType) : "-" }}
</template>
</el-table-column>
<!-- 日志详细说明 -->
<el-table-column prop="detail" label="变动说明" min-width="260" show-overflow-tooltip />
<!-- 变动时间 -->
<el-table-column prop="createTime" label="变动时间" width="170" align="center" />
<!-- 操作 -->
<el-table-column label="操作" width="110" align="center" fixed="right">
<template #default="{ row }">
<template v-if="row">
<a v-if="row.memberId" class="link-text" @click="viewMemberDetail(row)">客户详情</a>
<span v-else>-</span>
</template>
</template>
</el-table-column>
<el-table-column prop="detail" label="详细" min-width="300" show-overflow-tooltip />
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<!-- 底部分页 -->
<div class="pagination-wrap mt_10">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
@@ -86,23 +134,42 @@
</template>
<script setup>
import { ref, onMounted } from "vue";
import { onMounted, ref } from "vue";
import { useTable } from "@/utils/useTable";
import { getUserWallet } from "@/api/member";
import { customRouterPush } from "@/utils/filters";
import { ElMessage } from "element-plus";
defineOptions({ name: "walletLog" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const searchFormRef = ref(null);
const table = ref(null);
const selectDate = ref(null);
const { loading, data, total, searchForm, getDataList } = useTable(
const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = useTable(
getUserWallet,
{
pageSize: 20,
immediate: false,
form: { sort: "createTime", order: "desc", startDate: "", endDate: "", memberName: "" },
form: {
sort: "createTime",
order: "desc",
memberId: "",
memberName: "",
startDate: "",
endDate: "",
},
beforeRequest: (p) => {
const params = { ...p };
if (selectDate.value && selectDate.value.length === 2) {
params.startDate = selectDate.value[0];
params.endDate = selectDate.value[1];
} else {
params.startDate = "";
params.endDate = "";
}
return params;
},
}
);
@@ -113,23 +180,46 @@ function serviceTypeText(type) {
if (type === "WALLET_RECHARGE") return "余额充值";
return "佣金提成";
}
function init() {
getDataList();
function getServiceTagType(type) {
if (type === "WALLET_RECHARGE") return "success";
if (type === "WALLET_REFUND") return "primary";
if (type === "WALLET_PAY") return "warning";
if (type === "WALLET_WITHDRAWAL") return "danger";
return "info";
}
function formatMoney(val) {
const num = Number(val || 0);
return num.toFixed(2);
}
function viewMemberDetail(row) {
if (!row || !row.memberId) return;
customRouterPush({ name: "member-detail", query: { id: row.memberId } });
}
function handleReset() {
selectDate.value = null;
searchForm.memberId = "";
searchForm.memberName = "";
searchForm.startDate = "";
searchForm.endDate = "";
searchForm.pageNumber = 1;
resetSearch();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function selectDateRange(v) {
if (v && v.length === 2) {
searchForm.startDate = v[0];
@@ -141,6 +231,93 @@ function selectDateRange(v) {
}
onMounted(() => {
init();
getDataList();
});
</script>
<style lang="scss" scoped>
.member-wallet-log-page {
padding: 16px 20px;
background-color: #f6f8fa;
min-height: calc(100vh - 110px);
}
.font-mono {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.font-semibold {
font-weight: 600;
}
.text-dark {
color: #303133;
}
.text-muted {
color: #909399;
}
/* 筛选卡片 */
.filter-card {
border-radius: 8px;
border: 1px solid #ebeef5;
:deep(.el-card__body) {
padding: 16px 16px 2px;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
.el-form-item {
margin-bottom: 14px;
margin-right: 16px;
}
.form-actions {
margin-left: auto;
}
}
}
/* 表格卡片 */
.table-card {
border-radius: 8px;
border: 1px solid #ebeef5;
.card-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
}
.money-increase {
font-size: 14px;
color: #67c23a;
}
.money-decrease {
font-size: 14px;
color: #f56c6c;
}
.money-zero {
font-size: 14px;
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
padding-top: 6px;
}
</style>

View File

@@ -1,29 +1,52 @@
<template>
<div class="search">
<el-card>
<div class="member-withdraw-apply-page search">
<!-- 顶部筛选表单 -->
<el-card shadow="never" class="filter-card">
<el-form
ref="searchFormRef"
:model="searchForm"
inline
label-width="70px"
label-width="76px"
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="客户ID" prop="memberId">
<el-input
v-model="searchForm.memberId"
placeholder="请输入客户ID"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="客户名称" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入客户名称"
clearable
style="width: 240px"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="申请编号" prop="sn">
<el-input
v-model="searchForm.sn"
placeholder="请输入申请编号"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="提现状态" prop="applyStatus">
<el-select v-model="searchForm.applyStatus" clearable style="width: 240px">
<el-select
v-model="searchForm.applyStatus"
placeholder="全部状态"
clearable
style="width: 130px"
>
<el-option label="全部" value="" />
<el-option label="申请中" value="APPLY" />
<el-option label="审核通过" value="VIA_AUDITING" />
<el-option label="提现成功" value="SUCCESS" />
<el-option label="用户确认" value="WAIT_USER_CONFIRM" />
<el-option label="审核拒绝" value="FAIL_AUDITING" />
<el-option label="提现成功" value="SUCCESS" />
<el-option label="提现失败" value="ERROR" />
</el-select>
</el-form-item>
@@ -33,55 +56,90 @@
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择起始时间"
style="width: 360px"
style="width: 320px"
@change="selectDateRange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-form-item class="form-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">
搜索
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card>
<!-- 提现申请数据表格 -->
<el-card shadow="never" class="table-card mt_10">
<template #header>
<div class="card-header-bar">
<div class="header-title">
<span>会员提现申请</span>
</div>
<div class="header-actions">
<el-button size="small" @click="getDataList">刷新</el-button>
</div>
</div>
</template>
<el-table
ref="table"
v-loading="loading"
border
stripe
:data="data"
ref="table"
class="mt_10"
style="width: 100%"
@sort-change="changeSort"
@selection-change="changeSelect"
>
<el-table-column prop="sn" label="申请编号" min-width="160" show-overflow-tooltip />
<el-table-column prop="memberName" label="客户名称" min-width="120" show-overflow-tooltip />
<el-table-column label="申请金额" width="120">
<!-- 客户ID -->
<el-table-column prop="memberId" label="客户ID" min-width="170" show-overflow-tooltip />
<!-- 客户名称 -->
<el-table-column prop="memberName" label="客户名称" min-width="140" show-overflow-tooltip />
<!-- 申请编号 -->
<el-table-column prop="sn" label="申请编号" min-width="180" show-overflow-tooltip />
<!-- 申请提现金额 -->
<el-table-column label="申请金额" width="130" align="right" sortable="custom" prop="applyMoney">
<template #default="{ row }">
<priceColorScheme v-if="row" :value="row.applyMoney" :color="$mainColor" />
<span v-if="row">¥ {{ formatMoney(row.applyMoney) }}</span>
</template>
</el-table-column>
<el-table-column label="提现状态" width="120">
<!-- 提现状态 -->
<el-table-column label="提现状态" width="120" align="center">
<template #default="{ row }">
<el-tag v-if="row" :type="applyStatusTagType(row.applyStatus)">
{{ applyStatusText(row.applyStatus) }}
</el-tag>
{{ row ? applyStatusText(row.applyStatus) : "-" }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" width="170" sortable="custom" />
<el-table-column prop="inspectTime" label="审核时间" width="170" />
<el-table-column label="操作" width="120" align="center" fixed="right">
<!-- 申请时间 -->
<el-table-column prop="createTime" label="申请时间" width="170" align="center" sortable="custom" />
<!-- 审核时间 -->
<el-table-column prop="inspectTime" label="审核时间" width="170" align="center" />
<!-- 操作 -->
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="{ row }">
<div v-if="row" class="ops">
<a class="link-text" v-if="row.applyStatus === 'APPLY'" @click="openAudit(row)">审核</a>
<a class="link-text" v-else @click="openQuery(row)">查看</a>
</div>
<template v-if="row">
<a v-if="row.applyStatus === 'APPLY'" class="link-text" @click="openAudit(row)">审核</a>
<a v-else class="link-text" @click="openQuery(row)">查看</a>
<template v-if="row.memberId">
<span class="op-split">|</span>
<a class="link-text" @click="viewMemberDetail(row)">客户详情</a>
</template>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt_10" style="display: flex; justify-content: flex-end">
<!-- 底部分页 -->
<div class="pagination-wrap mt_10">
<el-pagination
v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize"
@@ -95,114 +153,170 @@
</div>
</el-card>
<!-- 审核弹窗 -->
<el-dialog
v-model="roleModalVisible"
:title="modalTitle"
width="500px"
title="提现申请审核"
width="520px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form label-width="80px">
<el-form-item label="申请编号">
<span>{{ showList.sn }}</span>
</el-form-item>
<el-form-item label="用户名称">
<span>{{ showList.memberName }}</span>
</el-form-item>
<el-form-item label="申请金额">
<priceColorScheme :value="showList.applyMoney" :color="$mainColor" />
</el-form-item>
<el-form-item label="提现状态">
<span>{{ paramTypeFilter(showList.applyStatus) }}</span>
</el-form-item>
<el-form-item label="申请时间">
<span>{{ showList.createTime }}</span>
</el-form-item>
<el-form-item label="审核备注">
<el-input v-model="audit" type="textarea" />
</el-form-item>
</el-form>
<div class="dialog-body">
<el-descriptions :column="1" border size="small" class="custom-dialog-descriptions">
<el-descriptions-item label="申请编号">
<span class="font-mono">{{ showList.sn }}</span>
</el-descriptions-item>
<el-descriptions-item label="客户ID">
<span class="font-mono">{{ showList.memberId || "-" }}</span>
</el-descriptions-item>
<el-descriptions-item label="客户名称">
<span class="font-semibold">{{ showList.memberName }}</span>
</el-descriptions-item>
<el-descriptions-item label="申请金额">
<span class="money-text font-semibold">¥ {{ formatMoney(showList.applyMoney) }}</span>
</el-descriptions-item>
<el-descriptions-item label="申请时间">
<span>{{ showList.createTime }}</span>
</el-descriptions-item>
</el-descriptions>
<div class="remark-form-item" style="margin-top: 14px">
<div class="remark-label" style="font-size: 13px; color: #606266; margin-bottom: 6px">
审核备注说明(拒绝时必填):
</div>
<el-input
v-model="audit"
type="textarea"
:rows="3"
maxlength="200"
show-word-limit
placeholder="请输入审核备注说明..."
/>
</div>
</div>
<template #footer>
<template v-if="showList.applyStatus === 'APPLY'">
<el-button @click="submitRole(false)">拒绝</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitRole(true)">通过</el-button>
</template>
<el-button @click="roleModalVisible = false">取消</el-button>
<el-button
type="danger"
:loading="submitLoading"
@click="submitRole(false)"
>
拒绝
</el-button>
<el-button
type="primary"
:loading="submitLoading"
@click="submitRole(true)"
>
通过
</el-button>
</template>
</el-dialog>
<!-- 查看详情弹窗 -->
<el-dialog
v-model="queryModalVisible"
:title="modalTitle"
width="500px"
title="提现申请详情"
width="520px"
:close-on-click-modal="false"
destroy-on-close
>
<el-form label-width="100px">
<el-form-item label="申请编号:">
<span>{{ showList.sn }}</span>
</el-form-item>
<el-form-item label="用户名称:">
<span>{{ showList.memberName }}</span>
</el-form-item>
<el-form-item label="申请金额:">
<priceColorScheme :value="showList.applyMoney" :color="$mainColor" />
</el-form-item>
<el-form-item label="提现状态:">
<span>{{ paramTypeFilter(showList.applyStatus) }}</span>
</el-form-item>
<el-form-item label="申请时间:">
<span>{{ showList.createTime }}</span>
</el-form-item>
<el-form-item label="审核时间:">
<span>{{ showList.inspectTime }}</span>
</el-form-item>
<el-form-item label="审核备注:">
<span>{{ showList.inspectRemark || "暂无备注" }}</span>
</el-form-item>
</el-form>
<div class="dialog-body">
<el-descriptions :column="1" border size="small" class="custom-dialog-descriptions">
<el-descriptions-item label="申请编号">
<span class="font-mono">{{ showList.sn }}</span>
</el-descriptions-item>
<el-descriptions-item label="客户ID">
<span class="font-mono">{{ showList.memberId || "-" }}</span>
</el-descriptions-item>
<el-descriptions-item label="客户名称">
<span class="font-semibold">{{ showList.memberName }}</span>
</el-descriptions-item>
<el-descriptions-item label="申请金额">
<span class="money-text font-semibold">¥ {{ formatMoney(showList.applyMoney) }}</span>
</el-descriptions-item>
<el-descriptions-item label="提现状态">
<el-tag :type="applyStatusTagType(showList.applyStatus)" size="small">
{{ paramTypeFilter(showList.applyStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="申请时间">
<span>{{ showList.createTime }}</span>
</el-descriptions-item>
<el-descriptions-item label="审核时间">
<span>{{ showList.inspectTime || "-" }}</span>
</el-descriptions-item>
<el-descriptions-item label="审核备注">
<span>{{ showList.inspectRemark || "暂无备注" }}</span>
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<template v-if="showList.applyStatus === 'APPLY'">
<el-button @click="submitRole(false)">拒绝</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitRole(true)">通过</el-button>
</template>
<el-button v-else @click="queryModalVisible = false">取消</el-button>
<el-button @click="queryModalVisible = false">关 闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { useTable } from "@/utils/useTable";
import { getUserWithdrawApply, withdrawApply } from "@/api/member";
import { customRouterPush } from "@/utils/filters";
import { ElMessage } from "element-plus";
defineOptions({ name: "withdrawApply" });
// 模板 el-form 的 ref(原 ref="searchForm",与 data 同名会互相覆盖,改名为 searchFormRef)
const route = useRoute();
const searchFormRef = ref(null);
// 模板 el-table 的 ref
const table = ref(null);
const selectDate = ref(null);
const modalTitle = ref("");
const loading = ref(true);
const audit = ref("");
const roleModalVisible = ref(false);
const queryModalVisible = ref(false);
const searchForm = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
startDate: "",
endDate: "",
memberName: "",
applyStatus: "",
});
const selectDate = ref(null);
const submitLoading = ref(false);
const selectList = ref([]);
const selectCount = ref(0);
const showList = ref({});
const data = ref([]);
const total = ref(0);
const { loading, data, total, searchForm, getDataList, handleSearch, resetSearch } = useTable(
getUserWithdrawApply,
{
pageSize: 20,
immediate: false,
form: {
sort: "createTime",
order: "desc",
memberId: "",
memberName: "",
sn: "",
applyStatus: "",
startDate: "",
endDate: "",
},
beforeRequest: (p) => {
const params = { ...p };
if (selectDate.value && selectDate.value.length === 2) {
params.startDate = selectDate.value[0];
params.endDate = selectDate.value[1];
} else {
params.startDate = "";
params.endDate = "";
}
return params;
},
}
);
function formatMoney(val) {
const num = Number(val || 0);
return num.toFixed(2);
}
function viewMemberDetail(row) {
if (!row || !row.memberId) return;
customRouterPush({ name: "member-detail", query: { id: row.memberId } });
}
function paramTypeFilter(val) {
if (val === "APPLY") return "申请中";
@@ -211,41 +325,47 @@ function paramTypeFilter(val) {
if (val === "FAIL_AUDITING") return "审核拒绝";
if (val === "SUCCESS") return "提现成功";
if (val === "ERROR") return "提现失败";
return "未知状态";
return val || "未知状态";
}
function applyStatusText(status) {
if (status === "APPLY") return "申请中";
if (status === "VIA_AUDITING") return "审核通过";
if (status === "WAIT_USER_CONFIRM") return "用户确认";
if (status === "SUCCESS") return "提现成功";
if (status === "ERROR") return "提现失败";
return "审核拒绝";
if (status === "FAIL_AUDITING") return "审核拒绝";
return status || "-";
}
function applyStatusTagType(status) {
if (status === "APPLY") return "warning";
if (status === "VIA_AUDITING") return "success";
if (status === "WAIT_USER_CONFIRM") return "warning";
if (status === "SUCCESS") return "primary";
if (status === "ERROR") return "primary";
if (status === "ERROR") return "danger";
return "danger";
}
function openAudit(row) {
showList.value = { ...row };
audit.value = "";
roleModalVisible.value = true;
}
function openQuery(row) {
showList.value = { ...row };
modalTitle.value = "查看";
queryModalVisible.value = true;
}
function submitRole(res) {
const params = {};
params.applyId = showList.value.id;
params.result = res;
params.remark = audit.value;
if (res === false && params.remark === "") {
ElMessage.error("审核备注不能为空");
const params = {
applyId: showList.value.id,
result: res,
remark: audit.value ? audit.value.trim() : "",
};
if (res === false && !params.remark) {
ElMessage.error("审核拒绝时备注说明不能为空");
return;
}
submitLoading.value = true;
@@ -265,50 +385,36 @@ function submitRole(res) {
submitLoading.value = false;
});
}
function init() {
function handleReset() {
selectDate.value = null;
searchForm.memberId = "";
searchForm.memberName = "";
searchForm.sn = "";
searchForm.applyStatus = "";
searchForm.startDate = "";
searchForm.endDate = "";
searchForm.pageNumber = 1;
resetSearch();
}
function changeSort(e) {
searchForm.sort = e.prop || e.key || "createTime";
searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
getDataList();
}
function changePage(v) {
searchForm.pageNumber = v;
getDataList();
clearSelectAll();
}
function changePageSize(v) {
searchForm.pageNumber = 1;
searchForm.pageSize = v;
getDataList();
}
function handleSearch() {
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
getDataList();
}
function handleReset() {
searchFormRef.value.resetFields();
searchForm.pageNumber = 1;
searchForm.pageSize = 20;
selectDate.value = null;
searchForm.startDate = "";
searchForm.endDate = "";
searchForm.memberName = "";
getDataList();
}
function changeSort(e) {
searchForm.sort = e.prop || e.key;
searchForm.order = e.order === "ascending" ? "asc" : e.order === "descending" ? "desc" : "";
if (!e.order) {
searchForm.order = "";
}
getDataList();
}
function clearSelectAll() {
if (table.value) {
table.value.clearSelection();
}
}
function changeSelect(e) {
selectList.value = e;
selectCount.value = e.length;
}
function selectDateRange(v) {
if (v && v.length === 2) {
searchForm.startDate = v[0];
@@ -318,30 +424,97 @@ function selectDateRange(v) {
searchForm.endDate = "";
}
}
function getDataList() {
loading.value = true;
getUserWithdrawApply(searchForm).then((res) => {
loading.value = false;
if (res.success) {
data.value = res.result.records;
total.value = res.result.total;
}
});
}
onMounted(() => {
init();
if (route.query && route.query.applyStatus) {
searchForm.applyStatus = route.query.applyStatus;
}
getDataList();
});
</script>
<style lang="scss" scoped>
.ops a {
color: #2d8cf0;
cursor: pointer;
text-decoration: none;
.member-withdraw-apply-page {
padding: 16px 20px;
background-color: #f6f8fa;
min-height: calc(100vh - 110px);
}
.ops span {
display: inline-block;
margin: 0 8px;
color: #dcdee2;
.font-mono {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.font-semibold {
font-weight: 600;
}
.text-dark {
color: #303133;
}
.text-muted {
color: #909399;
}
/* 筛选卡片 */
.filter-card {
border-radius: 8px;
border: 1px solid #ebeef5;
:deep(.el-card__body) {
padding: 16px 16px 2px;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
.el-form-item {
margin-bottom: 14px;
margin-right: 16px;
}
.form-actions {
margin-left: auto;
}
}
}
/* 表格卡片 */
.table-card {
border-radius: 8px;
border: 1px solid #ebeef5;
.card-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
}
.custom-dialog-descriptions {
:deep(.el-descriptions__label) {
width: 100px;
background-color: #fafafa;
color: #606266;
}
.money-text {
font-size: 15px;
color: #f56c6c;
}
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
padding-top: 6px;
}
</style>