feat(distribution): 优化分销提现页面功能

fix(setting): 更新海报和邀请卡背景图尺寸说明
style(table): 统一全局表格表头样式
refactor(home): 优化首页待办事项点击跳转
refactor(main): 优化主布局侧边栏收起动画
chore(menu): 优化菜单导航结构
docs(readme): 更新项目文档信息
feat(task): 新增定时任务管理页面
This commit is contained in:
pikachu1995@126.com
2026-08-11 15:16:32 +08:00
parent 4be36d61c7
commit 6933fb79ec
11 changed files with 435 additions and 150 deletions

View File

@@ -0,0 +1,11 @@
import { managerApiUrl, getRequest, postRequest } from '@/libs/axios';
// 获取定时任务列表
export const getTimedTaskList = () => {
return getRequest(`${managerApiUrl}/manager/setting/timed-task`);
};
// 手动触发定时任务
export const triggerTimedTask = (type) => {
return postRequest(`${managerApiUrl}/manager/setting/timed-task/trigger/${type}`);
};

View File

@@ -333,8 +333,8 @@ util.initAllMenuData = function(constRoutes, data) {
util.initMenuData = function(vm, data) {
const menuRoutes = [];
let menuData = data;
// 顶部菜单
let navList = [];
// 顶部菜单 —— 首页固定置顶
let navList = [{ name: 'home_index', title: '首页' }];
menuData.forEach(e => {
let nav = {
name: e.name,
@@ -357,11 +357,16 @@ util.initMenuData = function(vm, data) {
}
}
} else {
// 默认第一个
currNav = navList[0].name;
vm.$store.commit("setCurrNavTitle", navList[0].title);
// 默认首页
currNav = 'home_index';
vm.$store.commit("setCurrNavTitle", '首页');
}
vm.$store.commit("setCurrNav", currNav);
// 首页不需要二级菜单
if (currNav === 'home_index') {
vm.$store.commit("updateMenulist", []);
return;
}
for (let item of menuData) {
if (item.name == currNav) {
// 过滤

View File

@@ -2,7 +2,7 @@
@import "./main.scss";
</style>
<template>
<div class="main">
<div class="main" :class="{ 'home-only': !menuList.length }">
<div class="sidebar-menu-con menu-bar">
<shrinkable-menu />
</div>
@@ -113,6 +113,9 @@ export default {
loading() {
return this.$store.state.app.loading;
},
menuList() {
return this.$store.state.app.menuList || [];
},
pageTagsList() {
return this.$store.state.app.pageOpenedList;
},

View File

@@ -7,10 +7,10 @@
class="search-form"
@keyup.enter="handleSearch"
>
<el-form-item label="员名称" class="flex" prop="memberName">
<el-form-item label="分销员名称" class="flex" prop="memberName">
<el-input
v-model="searchForm.memberName"
placeholder="请输入员名称"
placeholder="请输入分销员名称"
clearable
style="width: 240px"
/>
@@ -23,16 +23,6 @@
style="width: 240px"
/>
</el-form-item>
<el-form-item label="状态" style="width: 240px">
<el-select v-model="searchForm.distributionCashStatus" clearable style="width: 150px">
<el-option
v-for="item in cashStatusList"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
@@ -40,6 +30,13 @@
</el-card>
<el-card>
<el-tabs v-model="tabStatus" @tab-change="handleTabChange">
<el-tab-pane label="全部" name="" />
<el-tab-pane :label="applyCountLabel" name="APPLY" />
<el-tab-pane label="已通过" name="VIA_AUDITING" />
<el-tab-pane label="已拒绝" name="FAIL_AUDITING" />
</el-tabs>
<el-table
ref="table"
v-loading="loading"
@@ -49,18 +46,14 @@
style="width: 100%"
>
<el-table-column prop="sn" label="编号" min-width="200" />
<el-table-column prop="distributionName" label="会员名称" min-width="120" />
<el-table-column prop="distributionId" label="分销员ID" min-width="180" />
<el-table-column prop="distributionName" label="分销员名称" min-width="120" />
<el-table-column label="申请金额" min-width="90">
<template #default="{ row }">
<priceColorScheme v-if="row" :value="row.price" :color="$mainColor" />
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" min-width="130" />
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="idNumber" label="身份证号" min-width="120" />
<el-table-column prop="settlementBankAccountName" label="结算银行开户行名称" min-width="120" />
<el-table-column prop="settlementBankAccountNum" label="结算银行开户账号" min-width="120" />
<el-table-column prop="settlementBankBranchName" label="结算银行开户支行名称" min-width="120" />
<el-table-column prop="updateTime" label="处理时间" min-width="130" />
<el-table-column label="状态" min-width="100">
<template #default="{ row }">
@@ -95,51 +88,104 @@
</div>
</el-card>
<el-dialog
v-model="modalVisible"
:title="modalTitle"
width="500px"
:close-on-click-modal="false"
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
size="560px"
direction="rtl"
:close-on-click-modal="true"
append-to-body
destroy-on-close
class="cash-detail-drawer"
>
<el-form ref="form" :model="form" label-width="100px">
<el-form-item label="编号">
<el-input v-model="form.sn" disabled clearable style="width: 100%" />
</el-form-item>
<el-form-item label="会员名称">
<el-input v-model="form.distributionName" disabled clearable style="width: 100%" />
</el-form-item>
<el-form-item label="金额">
<el-input v-model="form.price" disabled clearable style="width: 100%" />
</el-form-item>
<el-form-item v-if="handleStatus == 'edit'" label="是否通过" prop="result">
<el-radio-group v-model="result">
<el-radio-button value="VIA_AUDITING">通过</el-radio-button>
<el-radio-button value="FAIL_AUDITING">拒绝</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<template v-if="handleStatus == 'edit'" #footer>
<el-button @click="modalVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交</el-button>
<div class="detail-body">
<div class="detail-section-title">申请信息</div>
<div class="detail-meta">
<div class="detail-meta-row">
<span class="detail-meta-label">编号</span>
<span class="detail-meta-value">{{ form.sn || '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">分销员ID</span>
<span class="detail-meta-value">{{ form.distributionId || '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">分销员名称</span>
<span class="detail-meta-value">{{ form.distributionName || '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">申请金额()</span>
<span class="detail-meta-value detail-meta-price">{{ form.price != null ? Number(form.price).toFixed(2) : '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">状态</span>
<span class="detail-meta-value">
<span v-if="form.distributionCashStatus === 'APPLY'">待处理</span>
<span v-else-if="form.distributionCashStatus === 'VIA_AUDITING'">已通过</span>
<span v-else-if="form.distributionCashStatus === 'FAIL_AUDITING'">已拒绝</span>
<span v-else>-</span>
</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">申请时间</span>
<span class="detail-meta-value">{{ form.createTime || '-' }}</span>
</div>
<div v-if="form.updateTime && form.distributionCashStatus !== 'APPLY'" class="detail-meta-row">
<span class="detail-meta-label">处理时间</span>
<span class="detail-meta-value">{{ form.updateTime || '-' }}</span>
</div>
</div>
<div class="detail-section-title">银行卡信息</div>
<div class="detail-meta">
<div class="detail-meta-row">
<span class="detail-meta-label">持卡人</span>
<span class="detail-meta-value">{{ form.name || '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">银行</span>
<span class="detail-meta-value">{{ form.settlementBankAccountName || '-' }}</span>
</div>
<div class="detail-meta-row">
<span class="detail-meta-label">银行卡号</span>
<span class="detail-meta-value">{{ form.settlementBankAccountNum || '-' }}</span>
</div>
</div>
<template v-if="handleStatus === 'edit'">
<div class="detail-section-title">审核操作</div>
<div class="audit-action">
<el-radio-group v-model="result">
<el-radio-button value="VIA_AUDITING">通过</el-radio-button>
<el-radio-button value="FAIL_AUDITING">拒绝</el-radio-button>
</el-radio-group>
</div>
</template>
</div>
<template v-if="handleStatus === 'edit'" #footer>
<div class="drawer-footer">
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">提交审核</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
</div>
</template>
<script>
import { auditDistributionCash, getDistributionCash } from "@/api/distribution";
import { cashStatusList } from "./dataJson";
export default {
name: "distributionCash",
data() {
return {
cashStatusList,
tabStatus: "",
applyCount: 0,
loading: true,
modalVisible: false,
modalTitle: "",
result: "FAIL_AUDITING",
drawerVisible: false,
drawerTitle: "",
result: "VIA_AUDITING",
searchForm: {
pageNumber: 1,
pageSize: 20,
@@ -149,17 +195,38 @@ export default {
handleStatus: "edit",
form: {
sn: "",
memberName: "",
distributionId: "",
distributionName: "",
price: "",
name: "",
settlementBankAccountName: "",
settlementBankAccountNum: "",
},
submitLoading: false,
data: [],
total: 0,
};
},
computed: {
applyCountLabel() {
return this.applyCount > 0 ? `待处理(${this.applyCount})` : "待处理";
},
},
methods: {
init() {
this.getDataList();
this.loadApplyCount();
},
loadApplyCount() {
getDistributionCash({
distributionCashStatus: "APPLY",
pageSize: 1,
pageNumber: 1,
}).then((res) => {
if (res.success) {
this.applyCount = res.result.total || 0;
}
});
},
changePage() {
this.getDataList();
@@ -175,7 +242,11 @@ export default {
},
getDataList() {
this.loading = true;
getDistributionCash(this.searchForm).then((res) => {
const params = { ...this.searchForm };
if (this.tabStatus) {
params.distributionCashStatus = this.tabStatus;
}
getDistributionCash(params).then((res) => {
this.loading = false;
if (res.success) {
this.data = res.result.records;
@@ -183,61 +254,60 @@ export default {
}
});
},
handleTabChange(name) {
this.tabStatus = name;
this.searchForm.pageNumber = 1;
this.getDataList();
},
handleSubmit() {
let result = "拒绝";
if (this.result == "VIA_AUDITING") {
result = "通过";
}
this.$refs.form.validate((valid) => {
if (valid) {
this.$Modal.confirm({
title: "确认审核",
content: "您确认要审核" + result + "么?",
loading: true,
onOk: () => {
auditDistributionCash(this.form.id, { result: this.result }).then((res) => {
if (res.success) {
this.$Modal.remove();
this.$Message.success("审核成功");
this.getDataList();
this.modalVisible = false;
} else {
this.modalVisible = false;
}
});
},
let result = this.result == "VIA_AUDITING" ? "通过" : "拒绝";
this.$Modal.confirm({
title: "确认审核",
content: "您确认要审核" + result + "么?",
loading: true,
onOk: () => {
auditDistributionCash(this.form.id, { result: this.result }).then((res) => {
if (res.success) {
this.$Modal.remove();
this.$Message.success("审核成功");
this.drawerVisible = false;
this.getDataList();
this.loadApplyCount();
} else {
this.$Modal.remove();
}
});
}
},
});
},
edit(v) {
this.modalTitle = "审核";
this.drawerTitle = "审核";
this.handleStatus = "edit";
this.$refs.form?.resetFields();
this.result = "VIA_AUDITING";
const data = {};
for (let attr in v) {
if (v[attr] === null) {
v[attr] = "";
}
data[attr] = v[attr] === null ? "" : v[attr];
}
this.form = JSON.parse(JSON.stringify(v));
this.modalVisible = true;
this.form = data;
this.drawerVisible = true;
},
view(v) {
this.modalTitle = "查看";
this.drawerTitle = "查看";
this.handleStatus = "view";
this.$refs.form?.resetFields();
const data = {};
for (let attr in v) {
if (v[attr] === null) {
v[attr] = "";
}
data[attr] = v[attr] === null ? "" : v[attr];
}
let str = JSON.stringify(v);
let data = JSON.parse(str);
this.form = data;
this.modalVisible = true;
this.drawerVisible = true;
},
},
mounted() {
// 处理路由参数,自动跳转到指定标签页
const tabStatus = this.$route.query.tabStatus;
if (tabStatus) {
this.tabStatus = tabStatus;
}
this.init();
},
};
@@ -253,4 +323,71 @@ export default {
margin: 0 8px;
color: #dcdee2;
}
.audit-action {
margin-top: 4px;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
<style lang="scss">
.cash-detail-drawer {
.el-drawer__header {
margin-bottom: 0;
padding: 16px 24px;
border-bottom: 1px solid #e8eaec;
}
.el-drawer__body {
padding: 20px 24px 0;
overflow-x: hidden;
overflow-y: auto;
}
.el-drawer__footer {
padding: 12px 24px;
border-top: 1px solid #e8eaec;
}
}
.cash-detail-drawer .detail-body {
min-height: 200px;
}
.cash-detail-drawer .detail-section-title {
font-size: 14px;
font-weight: 600;
color: #17233d;
margin: 20px 0 12px;
&:first-child { margin-top: 0; }
}
.cash-detail-drawer .detail-meta {
display: flex;
flex-direction: column;
gap: 10px;
background: #f8f8f9;
border: 1px solid #e8eaec;
border-radius: 8px;
padding: 14px 16px;
}
.cash-detail-drawer .detail-meta-row {
display: flex;
align-items: flex-start;
font-size: 14px;
line-height: 22px;
}
.cash-detail-drawer .detail-meta-label {
color: #808695;
min-width: 110px;
flex-shrink: 0;
}
.cash-detail-drawer .detail-meta-value {
color: #515a6e;
flex: 1;
word-break: break-all;
}
.cash-detail-drawer .detail-meta-price {
font-weight: 600;
color: #f56c6c;
font-size: 15px;
}
</style>

View File

@@ -160,7 +160,7 @@
<img :src="posterForm.backgroundImage" alt="平台海报背景" class="poster-bg-thumb" />
</div>
<upload-pic-input v-model="posterForm.backgroundImage" :show-input="false" />
<div class="form-desc">建议上传竖版图片用于平台海报背景</div>
<div class="form-desc">建议尺寸与邀请卡预览画布一致 630×1000 像素端上海报若用其它比例将以封面填充居中裁剪预览区样式与邀请卡页一致便于对照效果</div>
</div>
</el-form-item>
<el-form-item label="推荐语">
@@ -208,7 +208,7 @@
<img :src="inviteCardForm.backgroundImage" alt="邀请卡背景" class="poster-bg-thumb" />
</div>
<upload-pic-input v-model="inviteCardForm.backgroundImage" :show-input="false" />
<div class="form-desc">建议上传竖版图片用于邀请卡背景</div>
<div class="form-desc">建议尺寸630×1000 像素端上若用其它比例将以封面填充居中裁剪</div>
</div>
</el-form-item>
<el-form-item label="推荐语">

View File

@@ -63,7 +63,7 @@
<div class="counts">{{ $store.state.notices.refund || 0 }}</div>
<div>待审核售后</div>
</div>
<div class="todo-item">
<div class="todo-item" @click="goDistributionCash">
<div class="counts">
{{ $store.state.notices.distributionCash || 0 }}
</div>
@@ -243,6 +243,13 @@ export default {
name,
});
},
// 跳转到分销提现,并打开待处理标签页
goDistributionCash() {
this.$router.push({
name: 'distributionCash',
query: { tabStatus: 'APPLY' }
});
},
// top10热卖商品
async toHotGoods() {
let res = await hotGoods(this.params);

View File

@@ -1,5 +1,5 @@
<template>
<div class="shrinkable-menu">
<div class="shrinkable-menu" :class="{ 'no-sub-menu': !menuList.length }">
<el-menu
class="nav-menu-dark"
:default-active="currNav"
@@ -10,6 +10,7 @@
</el-menu-item>
</el-menu>
<el-menu
v-if="menuList.length"
:key="`${currNav}-${activeMenuName}`"
class="sub-menu"
:default-active="activeMenuName"
@@ -104,6 +105,11 @@ export default {
selectNav(name) {
this.$store.commit("setCurrNav", name);
this.setStore("currNav", name);
if (name === 'home_index') {
this.$store.commit("updateMenulist", []);
this.$router.push({ name: 'home_index' });
return;
}
util.initRouter(this);
},
},
@@ -115,6 +121,11 @@ export default {
height: 100%;
width: 200px;
display: flex;
transition: width 0.3s;
&.no-sub-menu {
width: 80px;
}
}
.nav-menu-dark {

View File

@@ -89,6 +89,7 @@
z-index: -1;
margin-left: 200px;
width: calc(100% - 200px);
transition: margin-left 0.3s, width 0.3s;
background: #f0f0f0;
display: flex;
align-items: center;
@@ -249,7 +250,7 @@
overflow: auto;
background-color: #f0f0f0;
z-index: 1;
transition: left 0.3s;
transition: left 0.3s, width 0.3s;
.single-page {
position: relative;
@@ -290,6 +291,19 @@
background: #fff;
}
/* 首页:无二级菜单时,内容区跟随侧边栏收缩 */
.main.home-only {
.single-page-con {
left: 80px;
width: calc(100% - 80px);
}
.tags-con {
margin-left: 80px;
width: calc(100% - 80px);
}
}
.nav-item {
font-size: 14px;
}

View File

@@ -0,0 +1,91 @@
<template>
<div>
<el-card>
<el-tabs v-model="activeTab">
<el-tab-pane label="每日任务" name="每日任务" />
<el-tab-pane label="每小时任务" name="每小时" />
<el-tab-pane label="每分钟任务" name="每分钟" />
</el-tabs>
<el-table v-loading="loading" :data="currentTasks" border style="width: 100%; margin-top: 12px">
<el-table-column prop="name" label="任务名称" width="220" />
<el-table-column prop="cron" label="Cron表达式" width="140" />
<el-table-column label="操作" width="120" align="center">
<template #default="{ row }">
<el-button
type="primary"
size="small"
:loading="row.triggering"
@click="handleTrigger(row)"
>
手动触发
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import { ElMessage, ElMessageBox } from "element-plus";
import { getTimedTaskList, triggerTimedTask } from "@/api/timedTask.js";
export default {
name: "timed-task-manage",
data() {
return {
loading: false,
activeTab: "每日任务",
taskList: [],
};
},
computed: {
currentTasks() {
return this.taskList.filter((t) => t.group === this.activeTab);
},
},
methods: {
init() {
this.loading = true;
getTimedTaskList()
.then((res) => {
this.loading = false;
if (res.success) {
this.taskList = res.result.map((t) => ({ ...t, triggering: false }));
}
})
.catch(() => {
this.loading = false;
});
},
handleTrigger(row) {
ElMessageBox.confirm(`确认要手动触发 "${row.name}" 吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
row.triggering = true;
triggerTimedTask(row.id)
.then((res) => {
row.triggering = false;
if (res.success) {
ElMessage.success(`"${row.name}" 已触发执行`);
} else {
ElMessage.error(res.message || "触发失败");
}
})
.catch(() => {
row.triggering = false;
ElMessage.error("触发失败");
});
})
.catch(() => {});
},
},
mounted() {
this.init();
},
};
</script>