Files
qingcheng-Web/src/view/performance/performanceManagement.vue

538 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import {ref, onMounted, reactive} from "vue";
import {useRouter} from "vue-router";
import {message, Modal} from "ant-design-vue";
import myAxios from "../../api/myAxios.ts";
const visible = ref(false); // 控制弹窗显示
const newRate = ref(""); // 新比例值
const updating = ref(false); // 更新状态
// 修改列定义以匹配接口返回的字段
const columns = [
{
title: 'id',
dataIndex: 'id',
key: 'id',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: '主管ID',
dataIndex: 'userId',
key: 'userId',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: '主管姓名',
dataIndex: 'nickName',
key: 'nickName',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: '手机号',
dataIndex: 'phoneNumber',
key: 'phoneNumber',
width: 120,
align: 'center'
},
{
title: '订单总金额',
dataIndex: 'totalAmount',
key: 'totalAmount',
width: 90,
align: 'center'
},
{
title: '净成交',
dataIndex: 'netAmount',
key: 'netAmount',
width: 100,
align: 'center'
}, {
title: '推广数',
dataIndex: 'promoCount',
key: 'promoCount',
width: 80,
align: 'center'
},
{
title: '员工数',
dataIndex: 'empCount',
key: 'empCount',
width: 80,
align: 'center'
},
{
title: '下单量',
dataIndex: 'orderCount',
key: 'orderCount',
width: 80,
align: 'center'
},
{
title: '待释放',
dataIndex: 'toRelease',
key: 'toRelease',
width: 90,
align: 'center'
},
{
title: '可结算',
dataIndex: 'toSettle',
key: 'toSettle',
width: 90,
align: 'center'
},
{
title: '已结算',
dataIndex: 'settled',
key: 'settled',
width: 90,
align: 'center'
},
{
title: '已回退',
dataIndex: 'refunded',
key: 'refunded',
width: 90,
align: 'center'
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 130,
align: 'center'
}
];
// 定义主管业绩数据结构以匹配接口
interface ManagerPerformance {
id: number;
nickName: string;
phoneNumber: string;
empCount: number;
promoCount: number;
orderCount: number;
totalAmount: number;
netAmount: number;
toRelease: number;
toSettle: number;
refunded: number;
userId: number;
}
const tableData = ref<ManagerPerformance[]>([]);
const loading = ref(false);
const searchName = ref("");
const searchPhone = ref("");
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total} 条记录`,
pageSizeOptions: ['10', '20', '30', '50'],
});
// 获取主管业绩数据
const fetchManagerPerformance = async () => {
loading.value = true;
const storedToken = localStorage.getItem('token');
try {
const params = {
current: pagination.current,
pageSize: pagination.pageSize,
nickName: searchName.value,
phoneNumber: searchPhone.value,
// 移除不必要的参数
sortField: "id",
sortOrder: "",
startDate: "",
endDate: ""
};
// 修正请求格式直接发送params作为请求体
const response: any = await myAxios.post(
"/perform/supervisor/page",
params, // 直接发送params对象作为请求体
{ headers: { Authorization: storedToken } }
);
if (response.code === 1) {
tableData.value = response.data.records;
pagination.total = response.data.total || 0;
} else {
message.error(response.message || "获取数据失败");
}
} catch (error) {
console.error("获取主管业绩数据失败:", error);
message.error("获取数据失败,请重试");
} finally {
loading.value = false;
}
};
// 处理分页变化
const handlePaginationChange = (pag: { current: number; pageSize: number }) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
fetchManagerPerformance();
};
const handleSearch = () => {
// 验证手机号如果输入了手机号但不是11位
if (searchPhone.value && searchPhone.value.length !== 11) {
message.warning("请输入11位手机号码");
return; // 不执行搜索
}
pagination.current = 1; // 重置到第一页
fetchManagerPerformance();
};
// 重置搜索
const reset = () => {
searchName.value = "";
searchPhone.value = "";
pagination.current = 1;
fetchManagerPerformance();
};
const router = useRouter();
// 查看员工绩效明细
const viewStaffPerformance = (record: ManagerPerformance) => {
router.push({
path: '/employeePerformaince',
query: {
managerId: record.userId
}
});
};
const proportionRate = ref(''); // 默认值
// 获取抽成比例
const fetchProportionRate = async () => {
const storedToken = localStorage.getItem('token');
try {
const response: any = await myAxios.post(
"/perform/query/level/rate",
{ level: "first" }, // 按照接口要求发送level参数
{ headers: { Authorization: storedToken } }
);
console.log(response)
if (response.code === 1) {
proportionRate.value = response.data;
} else {
message.error(response.message || "获取抽成比例失败");
}
} catch (error) {
console.error("获取抽成比例失败:", error);
message.error("获取抽成比例失败,请重试");
}
};
// 打开修改比例弹窗
const openRateModal = () => {
newRate.value = proportionRate.value.toString();
visible.value = true;
};
// 更新比例
const updateProportionRate = async () => {
// 验证输入
if (!newRate.value) {
message.warning("请输入抽成比例");
return;
}
const rateValue = parseFloat(newRate.value);
if (isNaN(rateValue)) {
message.warning("请输入有效的数字");
return;
}
if (rateValue < 0 || rateValue > 1) {
message.warning("比例值应在0到1之间");
return;
}
updating.value = true;
const storedToken = localStorage.getItem('token');
try {
const response: any = await myAxios.post(
"/perform/update/rate",
{
level: "first",
rate: rateValue.toFixed(2) // 保留一位小数
},
{ headers: { Authorization: storedToken } }
);
if (response.code === 1) {
message.success("抽成比例更新成功");
proportionRate.value = rateValue.toFixed(2);
visible.value = false;
} else {
message.error(response.message || "更新抽成比例失败");
}
} catch (error) {
console.error("更新抽成比例失败:", error);
message.error("更新抽成比例失败,请重试");
} finally {
updating.value = false;
}
};
onMounted(() => {
fetchProportionRate(); // 调用获取抽成比例
fetchManagerPerformance();
});
const filterNameInput = (e: Event) => {
const input = e.target as HTMLInputElement;
let value = input.value;
// 使用正则表达式过滤非中文字符
// value = value.replace(/[^\u4e00-\u9fa5]/g, '');
value = value.replace(/[^a-zA-Z\u4e00-\u9fa5]/g, '');
// 更新输入框值
input.value = value;
searchName.value = value;
};
const filterPhoneInput = (e: Event) => {
const input = e.target as HTMLInputElement;
let value = input.value;
// 1. 移除非数字字符
value = value.replace(/\D/g, '');
// 2. 限制最多11位数字
if (value.length > 11) {
value = value.slice(0, 11);
}
// 更新输入框值
input.value = value;
searchPhone.value = value;
};
const filterRateInput = (e: Event) => {
const input = e.target as HTMLInputElement;
let value = input.value;
// 1. 移除非数字和非小数点的字符(但允许小数点)
value = value.replace(/[^\d.]/g, '');
// 2. 确保只有一个小数点
const decimalParts = value.split('.');
if (decimalParts.length > 2) {
// 如果有多个小数点,只保留第一个和第二个之间的内容
value = decimalParts[0] + '.' + decimalParts.slice(1).join('');
}
// 3. 限制小数点后最多两位数字
if (decimalParts.length > 1) {
value = decimalParts[0] + '.' + decimalParts[1].slice(0, 2);
}
// 4. 如果以小数点开头在前面添加0
if (value.startsWith('.')) {
value = '0' + value;
}
// 5. 更新输入框值
input.value = value;
newRate.value = value;
};
const settlement = async () => {
const storedToken = localStorage.getItem('token');
// 添加确认提示
const confirmSettlement = () => {
return new Promise((resolve) => {
Modal.confirm({
title: '确定要执行一键结算吗?',
content: '此操作会将所有可结算的金额全部加到已结算上,请谨慎操作',
onOk() { resolve(true); },
onCancel() { resolve(false); }
});
});
};
const proceed = await confirmSettlement();
if (!proceed) return;
try {
loading.value = true;
const response: any = await myAxios.post(
"/perform/unite/settle",
{}, // 空请求体
{ headers: { Authorization: storedToken } }
);
if (response.code === 1) {
message.success("结算成功,页面即将刷新");
// 1.5秒后刷新页面数据
setTimeout(() => {
fetchManagerPerformance(); // 刷新主管业绩数据
fetchProportionRate(); // 刷新抽成比例
}, 1500);
} else {
message.error(response.message || "结算失败");
}
} catch (error) {
console.error("结算请求失败:", error);
message.error("结算失败,请重试");
} finally {
loading.value = false;
}
};
</script>
<template>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="主管姓名">
<a-input
style="width: 300px;border: 1px solid #ffa940;"
placeholder="请输入主管姓名"
v-model:value="searchName"
@click="handleSearch"
@input="filterNameInput"
class="custom-search"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input
style="width: 300px;border: 1px solid #ffa940;"
placeholder="请输入手机号"
v-model:value="searchPhone"
class="custom-search"
@input="filterPhoneInput"
/>
</a-form-item>
<a-button @click="handleSearch" style="margin-right: 10px" class="custom-button">搜索</a-button>
<a-button @click="reset">重置</a-button>
抽成占比<a-tag color="orange">{{ proportionRate }}</a-tag>
<a-button @click="openRateModal" style="margin-left: 10px" class="custom-button">修改比例</a-button>
<a-button @click="settlement" style="margin-left: 10px" class="custom-button">一键结算</a-button>
</a-space>
</a-form>
</div>
<!-- 修改比例弹窗 -->
<a-modal
v-model:visible="visible"
title="修改抽成比例"
@ok="updateProportionRate"
:confirm-loading="updating"
:mask-closable="false"
>
<a-form layout="vertical">
<a-form-item label="新比例值0-1之间">
<a-input
v-model:value="newRate"
placeholder="请输入比例值如0.35"
@keyup.enter="updateProportionRate"
@input="filterRateInput"
/>
<div style="margin-top: 8px; color: #999">
提示比例值应为0到1之间的小数如0.35表示35%保留两位小数
</div>
</a-form-item>
</a-form>
</a-modal>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="tableData"
:scroll="{ x: 1600, y: 550 }"
:loading="loading"
:pagination="pagination"
@change="handlePaginationChange"
bordered
rowKey="id"
>
<template #bodyCell="{ column, record }">
<!-- 金额格式化 -->
<template v-if="['totalAmount', 'netAmount', 'toRelease', 'tosettle', 'refunded','settled','toSettle'].includes(column.dataIndex)">
¥{{ (record[column.dataIndex as keyof ManagerPerformance] || 0).toLocaleString() }}
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<a-button
size="small"
@click="viewStaffPerformance(record)"
>
员工绩效明细
</a-button>
</template>
</template>
</a-table>
</template>
<style scoped>
/* 搜索框样式 */
.search-box {
margin-bottom: 20px;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* 搜索框输入框聚焦和悬浮状态边框为橙色 */
.search-box .custom-search :deep(.ant-input:hover),
.search-box .custom-search :deep(.ant-input:focus) {
border-color: #ffa940 !important;
box-shadow: 0 0 0 2px rgba(255, 169, 64, 0.2) !important;
}
/* 按钮样式 */
.custom-button {
background-color: #ffa940;
color: #fff;
border: 1px solid #ffa940;
}
.custom-button:hover,
.custom-button:focus {
background-color: #ffa940;
border-color: #ffa940;
color: #fff;
opacity: 0.9;
}
/* 搜索按钮样式 */
.custom-search :deep(.ant-input-search-button) {
background-color: #ffa940;
border: 1px solid #ffa940;
}
.custom-search :deep(.ant-input-search-button:hover),
.custom-search :deep(.ant-input-search-button:focus) {
background-color: #fa8c16;
border: 1px solid #ffa940;
}
.custom-search :deep(.ant-input) {
border-right-color: #ffa940;
}
</style>