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

521 lines
12 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, useRoute} from "vue-router";
import {message} from "ant-design-vue";
import myAxios from "../../api/myAxios.ts";
const visible = ref(false); // 控制弹窗显示
const newRate = ref(""); // 新比例值
const updating = ref(false); // 更新状态
// 获取路由参数
const route = useRoute();
const router = useRouter();
const managerId = ref(route.query.managerId as string || "");
// 定义员工绩效数据结构
interface EmployeePerformance {
id: number;
nickName: string;
phoneNumber: string;
promoCount: number;
orderCount: number;
totalAmount: number;
netAmount: number;
tokenLess: number; // 待释放
toString: number; // 可结算
refunded: number; // 已回退
userId: number;
}
// 修改列定义以匹配接口返回的字段
const columns = [
{
title: 'id',
dataIndex: 'id',
key: 'id',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: 'userId',
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'
}
];
const tableData = ref<EmployeePerformance[]>([]);
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 fetchEmployeePerformance = async () => {
if (!managerId.value) {
message.error("缺少主管ID参数");
return;
}
loading.value = true;
const storedToken = localStorage.getItem('token');
try {
const params = {
current: pagination.current,
pageSize: pagination.pageSize,
nickName: searchName.value,
phoneNumber: searchPhone.value,
supervisorUserId: managerId.value, // 使用路由传递的managerId
sortField: "",
sortOrder: "",
};
const response: any = await myAxios.post(
"/perform/staff/page",
params,
{ headers: { Authorization: storedToken } }
);
console.log(response)
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;
fetchEmployeePerformance();
};
// 搜索功能
const handleSearch = () => {
pagination.current = 1;
fetchEmployeePerformance();
};
// 重置搜索
const reset = () => {
searchName.value = "";
searchPhone.value = "";
pagination.current = 1;
fetchEmployeePerformance();
};
// 查看客户订单明细
const viewCustomerOrders = (record: EmployeePerformance) => {
router.push({
path: '/customerOrder',
query: {
employeeId: record.userId,
employeeName: record.nickName
}
});
};
// 返回上一页
const goBack = () => {
router.push('/performanceManagement');
};
const proportionRate = ref(''); // 默认值
// 获取抽成比例
const fetchProportionRate = async () => {
const storedToken = localStorage.getItem('token');
try {
const response: any = await myAxios.post(
"/perform/query/level/rate",
{ level: "second" }, // 按照接口要求发送level参数
{ headers: { Authorization: storedToken } }
);
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: "second",
rate: parseFloat(rateValue.toFixed(2)) // 确保转换为数字类型
},
{ headers: { Authorization: storedToken } }
);
if (response.code === 1) {
message.success("抽成比例更新成功");
// 修复语法错误:移除命名参数
proportionRate.value = parseFloat(rateValue.toFixed(2)).toString();
visible.value = false;
} else {
message.error(response.message || "更新抽成比例失败");
}
} catch (error) {
console.error("更新抽成比例失败:", error);
message.error("更新抽成比例失败,请重试");
} finally {
updating.value = false;
}
};
onMounted(() => {
fetchProportionRate();
// 确保有managerId参数
if (!managerId.value) {
message.warning("未获取到主管ID");
goBack();
return;
}
fetchEmployeePerformance();
});
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;
};
</script>
<template>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="员工姓名">
<a-input
style="width: 200px"
placeholder="请输入员工姓名"
v-model:value="searchName"
@click="handleSearch"
@input="filterNameInput"
class="custom-search"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input
style="width: 200px"
placeholder="请输入手机号"
v-model:value="searchPhone"
class="custom-search"
@input="filterPhoneInput"
/>
</a-form-item>
<a-button class="custom-button" @click="handleSearch" style="margin-right: 10px">搜索</a-button>
<a-button @click="reset">重置</a-button>
<a-button @click="goBack" style="margin-left: 10px" class="custom-button">返回</a-button>
抽成占比<a-tag color="orange">{{ proportionRate }}</a-tag>
<a-button @click="openRateModal" 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', 'tokenLess', 'toString', 'refunded','toSettle','settled'].includes(column.dataIndex)">
¥{{ (record[column.dataIndex as keyof EmployeePerformance] || 0).toLocaleString() }}
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<a-button
size="small"
@click="viewCustomerOrders(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);
display: flex;
align-items: center;
}
:deep(.ant-table-thead) > tr > th {
background-color: #f0f5ff !important;
font-weight: 600;
}
:deep(.ant-table-row:hover) {
background-color: #fafafa !important;
}
.search-box {
margin-bottom: 0;
margin-right: 16px;
}
.search-box {
display: flex;
align-items: center;
}
.custom-button {
background-color: #ffa940;
border-color: #ffa940;
color: #fff;
}
.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-color: #ffa940;
}
.custom-search :deep(.ant-input-search-button:hover),
.custom-search :deep(.ant-input-search-button:focus) {
background-color: #fa8c16;
border-color: #fa8c16;
}
.custom-search :deep(.ant-input) {
border-right-color: #ffa940;
}
.custom-search :deep(.ant-input-search-button) {
background-color: #ffa940;
border-color: #ffa940;
}
</style>