新模块的第一次测试修复了部分bug
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -27,6 +27,7 @@ declare module 'vue' {
|
|||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
AImage: typeof import('ant-design-vue/es')['Image']
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||||
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||||
|
BIN
dist7月14日.zip
Normal file
BIN
dist7月14日.zip
Normal file
Binary file not shown.
@ -14,7 +14,7 @@ const myAxios = axios.create({
|
|||||||
//baseURL:'http://27.30.77.229:9092/'
|
//baseURL:'http://27.30.77.229:9092/'
|
||||||
//baseURL:'http://160.202.242.36:9091/'
|
//baseURL:'http://160.202.242.36:9091/'
|
||||||
// baseURL:'http://160.202.242.36:9092/'
|
// baseURL:'http://160.202.242.36:9092/'
|
||||||
|
// baseURL:'http://160.202.242.36:9092'
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<a-menu-item key="/adminList">管理员列表</a-menu-item>
|
<a-menu-item key="/adminList">管理员列表</a-menu-item>
|
||||||
<a-menu-item key="staffList">员工列表</a-menu-item>
|
<a-menu-item key="/staffList">员工列表</a-menu-item>
|
||||||
<a-menu-item key="supervisorList">主管列表</a-menu-item>
|
<a-menu-item key="/supervisorList">主管列表</a-menu-item>
|
||||||
<a-menu-item key="userList">普通用户列表</a-menu-item>
|
<a-menu-item key="/userList">普通用户列表</a-menu-item>
|
||||||
|
<a-menu-item key="/managerInformation">经理信息</a-menu-item>
|
||||||
</a-sub-menu>
|
</a-sub-menu>
|
||||||
<a-sub-menu>
|
<a-sub-menu>
|
||||||
<template #title>
|
<template #title>
|
||||||
@ -61,11 +62,18 @@
|
|||||||
<a-menu-item key="/workList">工作列表</a-menu-item>
|
<a-menu-item key="/workList">工作列表</a-menu-item>
|
||||||
<a-menu-item key="/workDetail">工作详情</a-menu-item>
|
<a-menu-item key="/workDetail">工作详情</a-menu-item>
|
||||||
</a-sub-menu>
|
</a-sub-menu>
|
||||||
|
<a-sub-menu>
|
||||||
<a-menu-item key="/performanceManagement">
|
<template #title>
|
||||||
<CommentOutlined />
|
<span>
|
||||||
|
<FieldTimeOutlined />
|
||||||
<span>业绩管理</span>
|
<span>业绩管理</span>
|
||||||
</a-menu-item>
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-menu-item key="/performanceManagement">业绩报表</a-menu-item>
|
||||||
|
<a-menu-item key="/supervisorPerformanceRanking">主管绩效排行</a-menu-item>
|
||||||
|
<a-menu-item key="/employeePerformanceRanking">员工绩效排行</a-menu-item>
|
||||||
|
|
||||||
|
</a-sub-menu>
|
||||||
<a-menu-item key="/employeeApplication">
|
<a-menu-item key="/employeeApplication">
|
||||||
<CommentOutlined />
|
<CommentOutlined />
|
||||||
<span>员工申请管理</span>
|
<span>员工申请管理</span>
|
||||||
|
@ -162,6 +162,21 @@ export const routes = [
|
|||||||
name:'员工申请详情',
|
name:'员工申请详情',
|
||||||
component: ()=> import("../view/employeeApplication/employeeDetail.vue")
|
component: ()=> import("../view/employeeApplication/employeeDetail.vue")
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path:'/managerInformation',
|
||||||
|
name:'经理信息',
|
||||||
|
component: ()=> import("../view/userList/managerInformation.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path:'/supervisorPerformanceRanking',
|
||||||
|
name:'主管绩效排行',
|
||||||
|
component: ()=> import("../view/performance/supervisorPerformanceRanking.vue")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path:'/employeePerformanceRanking',
|
||||||
|
name:'员工绩效排行',
|
||||||
|
component: ()=> import("../view/performance/employeePerformanceRanking.vue")
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
@ -120,6 +120,10 @@
|
|||||||
@input="validatePrices"
|
@input="validatePrices"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
<!-- 新增错误提示 -->
|
||||||
|
<div v-if="priceError" class="price-error">
|
||||||
|
{{ priceError }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -158,6 +162,7 @@ import RichTextEditor from '../components/RichTextEditor.vue';
|
|||||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||||
import myAxios from "../../api/myAxios.ts";
|
import myAxios from "../../api/myAxios.ts";
|
||||||
import router from "../../router";
|
import router from "../../router";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
|
||||||
interface CourseForm {
|
interface CourseForm {
|
||||||
name: string;
|
name: string;
|
||||||
@ -173,6 +178,8 @@ interface CourseForm {
|
|||||||
|
|
||||||
const fileInput = ref<HTMLInputElement | null>(null);
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
const fileName = ref('');
|
const fileName = ref('');
|
||||||
|
// 新增价格错误状态
|
||||||
|
const priceError = ref('');
|
||||||
|
|
||||||
const handleFileUpload = async (event: Event) => {
|
const handleFileUpload = async (event: Event) => {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
@ -241,28 +248,27 @@ const encryptValue = (value: any, key: string): any => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 验证函数
|
// 修改验证函数:添加错误提示
|
||||||
const validatePrices = () => {
|
const validatePrices = () => {
|
||||||
|
priceError.value = '';
|
||||||
|
|
||||||
if (formData.discountPrice >= formData.originPrice) {
|
if (formData.discountPrice >= formData.originPrice) {
|
||||||
|
message.warn('折扣价必须小于原价')
|
||||||
|
priceError.value = '折扣价必须小于原价';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// 验证价格和佣金比例
|
// 重置错误状态
|
||||||
|
priceError.value = '';
|
||||||
|
|
||||||
|
// 验证价格
|
||||||
const isPriceValid = validatePrices();
|
const isPriceValid = validatePrices();
|
||||||
|
|
||||||
let errorMessage = '';
|
|
||||||
|
|
||||||
if (!isPriceValid) {
|
if (!isPriceValid) {
|
||||||
errorMessage += '折扣价必须小于原价\n';
|
return; // 验证失败时不提交
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (errorMessage) {
|
|
||||||
alert(errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -456,13 +456,6 @@ const showDetails = (id: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看章节详情
|
|
||||||
// const chapterDetails = (id: string) => {
|
|
||||||
// router.push({
|
|
||||||
// path: '/chapterDetail',
|
|
||||||
// query: { id: String(id) }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
v-model:value="searchOrderNumber"
|
v-model:value="searchOrderNumber"
|
||||||
class="custom-search"
|
class="custom-search"
|
||||||
|
@input="filterOrderInput"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
@ -30,13 +31,6 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-button class="custom-button" @click="reset">重置搜索</a-button>
|
<a-button class="custom-button" @click="reset">重置搜索</a-button>
|
||||||
<a-button
|
|
||||||
class="custom-button danger"
|
|
||||||
:disabled="selectedRowKeys.length === 0"
|
|
||||||
@click="showDeleteConfirm"
|
|
||||||
>
|
|
||||||
批量删除
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@ -45,10 +39,9 @@
|
|||||||
<a-table
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="tableData"
|
:data-source="tableData"
|
||||||
:scroll="{ x: 2000, y: 550 }"
|
:scroll="{ x: 1800, y: 550 }"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
:row-selection="rowSelection"
|
|
||||||
bordered
|
bordered
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
@change="handleTableChange"
|
@change="handleTableChange"
|
||||||
@ -127,13 +120,7 @@ const searchParams = ref({
|
|||||||
orderStatus: ""
|
orderStatus: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
// 行选择配置
|
|
||||||
const rowSelection = ref({
|
|
||||||
selectedRowKeys: selectedRowKeys,
|
|
||||||
onChange: (selectedKeys: number[]) => {
|
|
||||||
selectedRowKeys.value = selectedKeys;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 订单表格列定义
|
// 订单表格列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -150,62 +137,17 @@ const columns = [
|
|||||||
{
|
{
|
||||||
title: '订单号',
|
title: '订单号',
|
||||||
dataIndex: 'orderNumber',
|
dataIndex: 'orderNumber',
|
||||||
width: 200,
|
width: 150,
|
||||||
key: 'orderNumber',
|
key: 'orderNumber',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '课程图片',
|
|
||||||
dataIndex: 'image',
|
|
||||||
key: 'image',
|
|
||||||
width: 75,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '用户ID',
|
title: '用户ID',
|
||||||
dataIndex: 'userId',
|
dataIndex: 'userId',
|
||||||
width: 90,
|
width: 90,
|
||||||
key: 'userId',
|
key: 'userId',
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},{
|
|
||||||
title: '课程ID',
|
|
||||||
dataIndex: 'courseId',
|
|
||||||
width: 90,
|
|
||||||
key: 'courseId',
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '课程名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
width: 140,
|
|
||||||
key: 'name',
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '课程类别',
|
|
||||||
dataIndex: 'type',
|
|
||||||
width: 75,
|
|
||||||
key: 'type',
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '课程原价',
|
|
||||||
dataIndex: 'originPrice',
|
|
||||||
width: 95,
|
|
||||||
key: 'originPrice',
|
|
||||||
align: 'center',
|
|
||||||
sorter: true,
|
|
||||||
sortDirections: ['ascend', 'descend']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '折扣价格',
|
|
||||||
dataIndex: 'discountPrice',
|
|
||||||
key: 'discountPrice',
|
|
||||||
width: 95,
|
|
||||||
align: 'center',
|
|
||||||
sorter: true,
|
|
||||||
sortDirections: ['ascend', 'descend']
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '订单金额',
|
title: '订单金额',
|
||||||
@ -216,13 +158,6 @@ const columns = [
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
sortDirections: ['ascend', 'descend']
|
sortDirections: ['ascend', 'descend']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '交易号',
|
|
||||||
dataIndex: 'transactionNumber',
|
|
||||||
key: 'transactionNumber',
|
|
||||||
width: 150,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '订单状态',
|
title: '订单状态',
|
||||||
dataIndex: 'orderStatus',
|
dataIndex: 'orderStatus',
|
||||||
@ -239,15 +174,6 @@ const columns = [
|
|||||||
sorter: true,
|
sorter: true,
|
||||||
sortDirections: ['ascend', 'descend']
|
sortDirections: ['ascend', 'descend']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '更新时间',
|
|
||||||
dataIndex: 'updateTime',
|
|
||||||
key: 'updateTime',
|
|
||||||
width: 150,
|
|
||||||
align: 'center',
|
|
||||||
sorter: true,
|
|
||||||
sortDirections: ['ascend', 'descend']
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
@ -257,6 +183,18 @@ const columns = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const filterOrderInput = (e: Event) => {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
let value = input.value;
|
||||||
|
|
||||||
|
// 使用正则表达式过滤非数字字符(只保留数字)
|
||||||
|
value = value.replace(/\D/g, '');
|
||||||
|
|
||||||
|
// 更新输入框值
|
||||||
|
input.value = value;
|
||||||
|
searchOrderNumber.value = value;
|
||||||
|
};
|
||||||
|
|
||||||
// 订单搜索方法
|
// 订单搜索方法
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
searchParams.value.orderNumber = searchOrderNumber.value;
|
searchParams.value.orderNumber = searchOrderNumber.value;
|
||||||
@ -394,44 +332,6 @@ const deleteOrder = (id: number) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 批量删除确认弹窗
|
|
||||||
const showDeleteConfirm = () => {
|
|
||||||
if (selectedRowKeys.value.length === 0) {
|
|
||||||
message.warning('请至少选择一个订单');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Modal.confirm({
|
|
||||||
title: '确认批量删除',
|
|
||||||
content: `确定要删除选中的 ${selectedRowKeys.value.length} 个订单吗?删除后数据将无法恢复!`,
|
|
||||||
okText: '确认',
|
|
||||||
cancelText: '取消',
|
|
||||||
onOk: deleteBatchOrders
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 批量删除方法
|
|
||||||
const deleteBatchOrders = async () => {
|
|
||||||
try {
|
|
||||||
const storedToken = localStorage.getItem('token');
|
|
||||||
const res: any = await myAxios.post(
|
|
||||||
"/courseOrder/delBatch",
|
|
||||||
{ ids: selectedRowKeys.value },
|
|
||||||
{ headers: { Authorization: storedToken } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.code === 1) {
|
|
||||||
message.success(`成功删除 ${selectedRowKeys.value.length} 个订单`);
|
|
||||||
selectedRowKeys.value = []; // 清空选择
|
|
||||||
await getOrderList(); // 刷新列表
|
|
||||||
} else {
|
|
||||||
message.error(res.message || '批量删除失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('批量删除失败:', error);
|
|
||||||
message.error('批量删除操作失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 重置搜索条件
|
// 重置搜索条件
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
|
@ -46,6 +46,13 @@ const columns = [
|
|||||||
: '-';
|
: '-';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '身份证号',
|
||||||
|
dataIndex: 'idCard',
|
||||||
|
key: 'idCard',
|
||||||
|
width: 140,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '审核状态',
|
title: '审核状态',
|
||||||
dataIndex: 'reviewStatus',
|
dataIndex: 'reviewStatus',
|
||||||
@ -56,6 +63,7 @@ const columns = [
|
|||||||
{ text: '已通过', value: '已通过' },
|
{ text: '已通过', value: '已通过' },
|
||||||
{ text: '待审核', value: '待审核' },
|
{ text: '待审核', value: '待审核' },
|
||||||
{ text: '已拒绝', value: '已拒绝' },
|
{ text: '已拒绝', value: '已拒绝' },
|
||||||
|
{ text: '已撤回', value: '已撤回' },
|
||||||
],
|
],
|
||||||
filterMultiple: false,
|
filterMultiple: false,
|
||||||
},
|
},
|
||||||
@ -154,20 +162,27 @@ const fetchAdvancementData = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看简历
|
const handleViewResume = (fileName: string) => {
|
||||||
const handleViewResume = (resumeCode: string) => {
|
const storedToken = localStorage.getItem('token');
|
||||||
// 这里实现查看简历的逻辑
|
const baseURL = myAxios.defaults.baseURL || '';
|
||||||
// 例如:打开一个新窗口,显示简历
|
|
||||||
// window.open(`/api/resume/view?code=${resumeCode}`, '_blank');
|
|
||||||
|
|
||||||
// 或者调用API获取简历内容
|
// 直接构建下载URL
|
||||||
// fetchResumeContent(resumeCode);
|
const downloadUrl = `${baseURL}/file/download/${encodeURIComponent(fileName)}`;
|
||||||
|
|
||||||
// 暂时使用消息提示
|
// 创建隐藏链接
|
||||||
message.info(`查看简历:${resumeCode}`);
|
const link = document.createElement('a');
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
link.style.display = 'none';
|
||||||
|
|
||||||
|
// 添加认证头(注意:这种方法不一定有效)
|
||||||
|
link.setAttribute('Authorization', `Bearer ${storedToken}`);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 处理表格变化事件(分页、排序、筛选)
|
// 处理表格变化事件(分页、排序、筛选)
|
||||||
const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
|
const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
|
||||||
// 更新分页参数
|
// 更新分页参数
|
||||||
|
@ -11,12 +11,11 @@
|
|||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
v-model:value="searchOrderNumber"
|
v-model:value="searchOrderNumber"
|
||||||
class="custom-search"
|
class="custom-search"
|
||||||
|
@input="filterOrderNumber"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-button class="custom-button" @click="reset">重置搜索</a-button>
|
<a-button class="custom-button" @click="reset">重置搜索</a-button>
|
||||||
<a-button class="custom-button" @click="goBack">返回</a-button>
|
<a-button class="custom-button" @click="goBack">返回</a-button>
|
||||||
|
|
||||||
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@ -25,60 +24,92 @@
|
|||||||
<a-table
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="tableData"
|
:data-source="tableData"
|
||||||
:scroll="{ x: 2500, y: 550 }"
|
:scroll="{ x: 1500, y: 550 }"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
@change="handleTableChange"
|
||||||
bordered
|
bordered
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<!-- 图片 -->
|
|
||||||
<template v-if="column.key === 'image'">
|
|
||||||
<a-avatar :src="record.image" shape="square" :size="64"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 金额 -->
|
<!-- 金额 -->
|
||||||
<template v-if="column.key === 'amount'">
|
<template v-if="column.key === 'totalAmount'">
|
||||||
¥{{record.amount }}
|
¥{{ record.totalAmount }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 状态 -->
|
<!-- 状态 -->
|
||||||
<template v-if="column.key === 'status'">
|
<template v-if="column.key === 'orderStatus'">
|
||||||
<a-tag :color="record.status === '交易成功' ? 'green' : 'orange'">
|
<a-tag :color="record.orderStatus === '交易成功' ? 'green' : 'orange'">
|
||||||
{{ record.status }}
|
{{ record.orderStatus }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 操作列 -->
|
<!-- 提成状态 -->
|
||||||
<template v-if="column.key === 'action'">
|
<template v-if="column.key === 'commissionStatus'">
|
||||||
<a-space :size="8">
|
<a-tag :color="getCommissionStatusColor(record.commissionStatus)">
|
||||||
<a-button
|
{{ record.commissionStatus }}
|
||||||
size="small"
|
</a-tag>
|
||||||
@click="customerDetail"
|
|
||||||
>
|
|
||||||
详情
|
|
||||||
</a-button>
|
|
||||||
<a-button
|
|
||||||
size="small"
|
|
||||||
danger
|
|
||||||
@click="deleteOrder(record.id)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref, watch,nextTick } from "vue";
|
||||||
import { message, Modal } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import router from "../../router";
|
import router from "../../router";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import myAxios from "../../api/myAxios.ts";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const searchOrderNumber = ref("");
|
const searchOrderNumber = ref("");
|
||||||
|
const pagination = ref({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ['10', '20', '50'],
|
||||||
|
showTotal: (total: number) => `共 ${total} 条记录`,
|
||||||
|
});
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
// 客户订单明细表
|
// 修复:确保正确获取路由参数
|
||||||
|
const employeeId = ref<number>(0);
|
||||||
|
|
||||||
|
const filterOrderNumber = (event: Event) => {
|
||||||
|
const inputElement = event.target as HTMLInputElement;
|
||||||
|
|
||||||
|
// 保存当前光标位置
|
||||||
|
const start = inputElement.selectionStart;
|
||||||
|
const end = inputElement.selectionEnd;
|
||||||
|
|
||||||
|
// 移除非数字字符
|
||||||
|
searchOrderNumber.value = searchOrderNumber.value.replace(/[^\d]/g, '');
|
||||||
|
|
||||||
|
// 恢复光标位置(避免光标跳到末尾)
|
||||||
|
nextTick(() => {
|
||||||
|
inputElement.setSelectionRange(start, end);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 客户订单接口定义
|
||||||
|
interface OrderRecord {
|
||||||
|
id: number;
|
||||||
|
nickName: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
orderNumber: string;
|
||||||
|
totalAmount: number;
|
||||||
|
orderStatus: string;
|
||||||
|
firstRate: number;
|
||||||
|
secondRate: number;
|
||||||
|
firstReward: number;
|
||||||
|
secondReward: number;
|
||||||
|
commissionStatus: string;
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableData = ref<OrderRecord[]>([]);
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '编号',
|
title: '编号',
|
||||||
@ -88,6 +119,14 @@ const columns = [
|
|||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '用户Id',
|
||||||
|
dataIndex: 'userId',
|
||||||
|
width: 80,
|
||||||
|
key: 'userId',
|
||||||
|
fixed: 'left',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '订单号',
|
title: '订单号',
|
||||||
dataIndex: 'orderNumber',
|
dataIndex: 'orderNumber',
|
||||||
@ -98,208 +137,253 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'userName',
|
dataIndex: 'nickName',
|
||||||
key: 'userName',
|
key: 'nickName',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '手机号',
|
title: '手机号',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phoneNumber',
|
||||||
key: 'phone',
|
key: 'phoneNumber',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '课程名称',
|
title: '订单金额',
|
||||||
dataIndex: 'courseName',
|
dataIndex: 'totalAmount',
|
||||||
key: 'courseName',
|
key: 'totalAmount',
|
||||||
width: 150,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '类别',
|
|
||||||
dataIndex: 'category',
|
|
||||||
key: 'category',
|
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '图片',
|
title: '订单状态',
|
||||||
dataIndex: 'image',
|
dataIndex: 'orderStatus',
|
||||||
key: 'image',
|
key: 'orderStatus',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '金额',
|
title: '一级提成比例',
|
||||||
dataIndex: 'amount',
|
dataIndex: 'firstRate',
|
||||||
key: 'amount',
|
key: 'firstRate',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }: { text: number }) => `${(text * 100).toFixed(0)}%`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '二级提成比例',
|
||||||
|
dataIndex: 'secondRate',
|
||||||
|
key: 'secondRate',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }: { text: number }) => `${(text * 100).toFixed(0)}%`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '一级提成金额',
|
||||||
|
dataIndex: 'firstReward',
|
||||||
|
key: 'firstReward',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '二级提成金额',
|
||||||
|
dataIndex: 'secondReward',
|
||||||
|
key: 'secondReward',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提成状态',
|
||||||
|
dataIndex: 'commissionStatus',
|
||||||
|
key: 'commissionStatus',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '创建时间',
|
||||||
dataIndex: 'status',
|
dataIndex: 'createTime',
|
||||||
key: 'status',
|
key: 'createTime',
|
||||||
width: 100,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '下单时间',
|
|
||||||
dataIndex: 'orderTime',
|
|
||||||
key: 'orderTime',
|
|
||||||
width: 180,
|
width: 180,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '支付时间',
|
|
||||||
dataIndex: 'paymentTime',
|
|
||||||
key: 'paymentTime',
|
|
||||||
width: 180,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
key: 'action',
|
|
||||||
fixed: 'right',
|
|
||||||
width: 100,
|
|
||||||
align: 'center'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 客户订单接口定义
|
// 获取提成状态颜色
|
||||||
interface OrderRecord {
|
const getCommissionStatusColor = (status: string) => {
|
||||||
id: number;
|
const statusColors: Record<string, string> = {
|
||||||
orderNumber: string;
|
'待提成': 'orange',
|
||||||
userName: string;
|
'已提成': 'green',
|
||||||
phone: string;
|
'已取消': 'red'
|
||||||
courseName: string;
|
};
|
||||||
category: string;
|
return statusColors[status] || 'blue';
|
||||||
image: string;
|
|
||||||
amount: number;
|
|
||||||
status: string;
|
|
||||||
orderTime: string;
|
|
||||||
paymentTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<OrderRecord[]>([]);
|
|
||||||
const originalTableData = ref<OrderRecord[]>([]);
|
|
||||||
|
|
||||||
// 生成模拟数据
|
|
||||||
const generateMockData = () => {
|
|
||||||
const mockData: OrderRecord[] = [];
|
|
||||||
const names = ['陈新知', '李小明', '王芳', '张伟', '赵敏', '刘洋', '周杰', '吴婷'];
|
|
||||||
const courses = [
|
|
||||||
'UI设计实战课程',
|
|
||||||
'Java高级开发',
|
|
||||||
'Python数据分析',
|
|
||||||
'前端框架精讲',
|
|
||||||
'产品经理实战',
|
|
||||||
'人工智能入门',
|
|
||||||
'云计算基础',
|
|
||||||
'网络安全实战'
|
|
||||||
];
|
|
||||||
const categories = ['考证', '技能提升', '职业发展', '兴趣爱好'];
|
|
||||||
const statuses = ['交易成功', '待支付', '已取消', '退款中'];
|
|
||||||
|
|
||||||
// 生成20条订单记录
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
const nameIndex = i % names.length;
|
|
||||||
const courseIndex = i % courses.length;
|
|
||||||
const categoryIndex = i % categories.length;
|
|
||||||
const statusIndex = i % statuses.length;
|
|
||||||
|
|
||||||
// 生成随机订单号
|
|
||||||
const orderNumber = `2025${(Math.floor(Math.random() * 12) + 1).toString().padStart(2, '0')}${(Math.floor(Math.random() * 28) + 1).toString().padStart(2, '0')}${Math.floor(Math.random() * 1000000).toString().padStart(6, '0')}`;
|
|
||||||
|
|
||||||
// 生成随机时间
|
|
||||||
const orderDate = `2025-${(Math.floor(Math.random() * 6) + 1).toString().padStart(2, '0')}-${(Math.floor(Math.random() * 28) + 1).toString().padStart(2, '0')}`;
|
|
||||||
const orderTime = `${Math.floor(Math.random() * 24).toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}`;
|
|
||||||
const paymentTime = `${Math.floor(Math.random() * 24).toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}`;
|
|
||||||
|
|
||||||
mockData.push({
|
|
||||||
id: i + 1,
|
|
||||||
orderNumber: orderNumber,
|
|
||||||
userName: names[nameIndex],
|
|
||||||
phone: `1${Math.floor(Math.random() * 9000000000) + 1000000000}`,
|
|
||||||
courseName: courses[courseIndex],
|
|
||||||
category: categories[categoryIndex],
|
|
||||||
image: `https://picsum.photos/200?random=${i}`, // 随机图片
|
|
||||||
amount: Math.floor(Math.random() * 1000) + 100,
|
|
||||||
status: statuses[statusIndex],
|
|
||||||
orderTime: `${orderDate} ${orderTime}`,
|
|
||||||
paymentTime: `${orderDate} ${paymentTime}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 搜索功能
|
// 获取订单数据
|
||||||
const handleSearch = () => {
|
const fetchOrderData = async () => {
|
||||||
if (!searchOrderNumber.value.trim()) {
|
loading.value = true;
|
||||||
tableData.value = [...originalTableData.value];
|
try {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
message.error('未找到登录信息');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = originalTableData.value.filter(item =>
|
const params = {
|
||||||
item.orderNumber.includes(searchOrderNumber.value)
|
current: pagination.value.current,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
orderNumber: searchOrderNumber.value,
|
||||||
|
staffUserId: employeeId.value
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("发送请求参数:", params); // 调试日志
|
||||||
|
|
||||||
|
const response:any = await myAxios.post(
|
||||||
|
'/perform/user/page',
|
||||||
|
params,
|
||||||
|
{ headers: { Authorization: token } }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
if (response.code === 1) {
|
||||||
message.warning("未找到匹配的订单");
|
const data = response.data;
|
||||||
tableData.value = [];
|
tableData.value = data.records || [];
|
||||||
|
pagination.value.total = data.total;
|
||||||
} else {
|
} else {
|
||||||
tableData.value = filtered;
|
message.error(response.message || '获取订单数据失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取订单数据失败:', error);
|
||||||
|
message.error('获取订单数据失败,请稍后再试');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除订单
|
|
||||||
const deleteOrder = (id: number) => {
|
// 搜索功能
|
||||||
Modal.confirm({
|
const handleSearch = () => {
|
||||||
title: '确认删除',
|
pagination.value.current = 1;
|
||||||
content: '确定要删除该订单吗?删除后数据将无法恢复!',
|
fetchOrderData();
|
||||||
okText: '确认',
|
|
||||||
cancelText: '取消',
|
|
||||||
onOk: async () => {
|
|
||||||
// 前端删除
|
|
||||||
const index = tableData.value.findIndex(item => item.id === id);
|
|
||||||
if (index !== -1) {
|
|
||||||
tableData.value.splice(index, 1);
|
|
||||||
message.success('删除成功');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置按钮
|
// 重置搜索
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
searchOrderNumber.value = "";
|
searchOrderNumber.value = "";
|
||||||
tableData.value = [...originalTableData.value];
|
pagination.value.current = 1;
|
||||||
|
fetchOrderData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载模拟数据
|
// 处理分页变化
|
||||||
onMounted(() => {
|
const handleTableChange = (pag: any) => {
|
||||||
loading.value = true;
|
pagination.value = { ...pagination.value, ...pag };
|
||||||
setTimeout(() => {
|
fetchOrderData();
|
||||||
const mockData = generateMockData();
|
};
|
||||||
tableData.value = mockData;
|
|
||||||
originalTableData.value = mockData;
|
|
||||||
loading.value = false;
|
|
||||||
}, 800);
|
|
||||||
});
|
|
||||||
|
|
||||||
const customerDetail =()=>{
|
|
||||||
router.push('/customerDetail')
|
// 返回员工绩效页面
|
||||||
|
const goBack = () => {
|
||||||
|
router.push('/performanceManagement');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安全解析路由参数 - 修复版
|
||||||
|
const parseEmployeeId = () => {
|
||||||
|
// 1. 直接使用 route.params.employeeId 可能不准确
|
||||||
|
// 2. 使用 route.params 对象中的具体键名
|
||||||
|
const idParam = route.params.employeeId || route.params.id;
|
||||||
|
|
||||||
|
// 调试信息
|
||||||
|
console.log("路由参数对象:", route.params);
|
||||||
|
console.log("原始员工ID参数:", idParam);
|
||||||
|
|
||||||
|
// if (!idParam) {
|
||||||
|
// console.error("未找到员工ID参数");
|
||||||
|
// message.error("未找到员工ID参数,请返回重试");
|
||||||
|
// employeeId.value = 0;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (Array.isArray(idParam)) {
|
||||||
|
// 处理数组类型的参数(如多个匹配)
|
||||||
|
employeeId.value = parseInt(idParam[0]) || 0;
|
||||||
|
} else if (typeof idParam === 'string') {
|
||||||
|
// 处理字符串类型的参数
|
||||||
|
employeeId.value = parseInt(idParam) || 0;
|
||||||
|
} else if (typeof idParam === 'number') {
|
||||||
|
// 直接是数字类型
|
||||||
|
employeeId.value = idParam;
|
||||||
|
} else {
|
||||||
|
// 处理其他情况
|
||||||
|
employeeId.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const goBack = () => {
|
console.log("解析后的员工ID:", employeeId.value);
|
||||||
router.push('/employeePerformaince');
|
|
||||||
|
// if (employeeId.value <= 0) {
|
||||||
|
// console.error("无效的员工ID参数:", idParam);
|
||||||
|
// message.error("无效的员工ID,请返回重试");
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 初始加载数据 - 修复版
|
||||||
|
onMounted(() => {
|
||||||
|
console.log("组件挂载,当前路由:", route.path);
|
||||||
|
console.log("路由参数:", route.params);
|
||||||
|
|
||||||
|
parseEmployeeId();
|
||||||
|
|
||||||
|
// 如果员工ID有效,则获取数据
|
||||||
|
if (employeeId.value > 0) {
|
||||||
|
fetchOrderData();
|
||||||
|
} else {
|
||||||
|
// 尝试从路由查询参数中获取
|
||||||
|
const queryId = route.query.employeeId || route.query.id;
|
||||||
|
if (queryId) {
|
||||||
|
console.log("尝试从查询参数获取员工ID:", queryId);
|
||||||
|
employeeId.value = parseInt(queryId.toString()) || 0;
|
||||||
|
if (employeeId.value > 0) {
|
||||||
|
fetchOrderData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听路由参数变化 - 修复版
|
||||||
|
watch(
|
||||||
|
() => route.params,
|
||||||
|
(newParams) => {
|
||||||
|
console.log("路由参数变化:", newParams);
|
||||||
|
parseEmployeeId();
|
||||||
|
if (employeeId.value > 0) {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
fetchOrderData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听路由查询参数变化
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
(newQuery) => {
|
||||||
|
console.log("路由查询参数变化:", newQuery);
|
||||||
|
const queryId = newQuery.employeeId || newQuery.id;
|
||||||
|
if (queryId) {
|
||||||
|
employeeId.value = parseInt(queryId.toString()) || 0;
|
||||||
|
if (employeeId.value > 0) {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
fetchOrderData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: false }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 样式保持不变 */
|
||||||
:deep(.ant-divider-vertical) {
|
:deep(.ant-divider-vertical) {
|
||||||
border-color: rgba(0, 0, 0, 0.15);
|
border-color: rgba(0, 0, 0, 0.15);
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
|
@ -299,6 +299,7 @@ const updateProportionRate = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
fetchProportionRate();
|
||||||
// 确保有managerId参数
|
// 确保有managerId参数
|
||||||
if (!managerId.value) {
|
if (!managerId.value) {
|
||||||
message.warning("未获取到主管ID");
|
message.warning("未获取到主管ID");
|
||||||
@ -307,7 +308,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchEmployeePerformance();
|
fetchEmployeePerformance();
|
||||||
fetchProportionRate();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
383
src/view/performance/employeePerformanceRanking.vue
Normal file
383
src/view/performance/employeePerformanceRanking.vue
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import myAxios from "../../api/myAxios.ts";
|
||||||
|
import {
|
||||||
|
message,
|
||||||
|
Table as ATable,
|
||||||
|
Pagination as APagination,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
DatePicker
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
interface PerformanceRank {
|
||||||
|
nickName: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
empCount: number;
|
||||||
|
promoCount: number;
|
||||||
|
orderCount: number;
|
||||||
|
totalAmount: number;
|
||||||
|
netAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PerformanceRankResponse {
|
||||||
|
code: number;
|
||||||
|
data: {
|
||||||
|
records: PerformanceRank[];
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
current: number;
|
||||||
|
pages: number;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序选项
|
||||||
|
const sortOptions = [
|
||||||
|
{ label: '员工数量', value: 'empCount' },
|
||||||
|
{ label: '推广人数', value: 'promoCount' },
|
||||||
|
{ label: '下单数量', value: 'orderCount' },
|
||||||
|
{ label: '订单总金额', value: 'totalAmount' },
|
||||||
|
{ label: '净成交金额', value: 'netAmount' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 排序方向选项
|
||||||
|
const orderOptions = [
|
||||||
|
{ label: '升序', value: 'asc' },
|
||||||
|
{ label: '降序', value: 'desc' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
sortField: "netAmount", // 默认排序字段为净成交金额
|
||||||
|
sortOrder: "asc", // 默认排序方向为升序
|
||||||
|
nickName: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
startDate: "",
|
||||||
|
endDate: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绩效排行数据
|
||||||
|
const performanceRanks = ref<PerformanceRank[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{ title: '用户昵称', dataIndex: 'nickName', key: 'nickName' },
|
||||||
|
{ title: '手机号', dataIndex: 'phoneNumber', key: 'phoneNumber' },
|
||||||
|
{ title: '员工数量', dataIndex: 'empCount', key: 'empCount' },
|
||||||
|
{ title: '推广人数', dataIndex: 'promoCount', key: 'promoCount' },
|
||||||
|
{ title: '下单数量', dataIndex: 'orderCount', key: 'orderCount' },
|
||||||
|
{
|
||||||
|
title: '订单总金额',
|
||||||
|
dataIndex: 'totalAmount',
|
||||||
|
key: 'totalAmount',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '净成交',
|
||||||
|
dataIndex: 'netAmount',
|
||||||
|
key: 'netAmount',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取绩效排行数据
|
||||||
|
const fetchPerformanceRank = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
// 准备请求参数 - 确保金额参数为数字类型
|
||||||
|
const params = {
|
||||||
|
current: queryParams.current,
|
||||||
|
pageSize: queryParams.pageSize,
|
||||||
|
sortField: queryParams.sortField,
|
||||||
|
sortOrder: queryParams.sortOrder,
|
||||||
|
nickName: queryParams.nickName,
|
||||||
|
phoneNumber: queryParams.phoneNumber,
|
||||||
|
startDate: queryParams.startDate,
|
||||||
|
endDate: queryParams.endDate
|
||||||
|
};
|
||||||
|
|
||||||
|
const response:any = await myAxios.post<PerformanceRankResponse>(
|
||||||
|
'/perform/rank/staff/page',
|
||||||
|
params,
|
||||||
|
{ headers: { Authorization: token } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.code === 1) {
|
||||||
|
// 确保金额字段为数字类型
|
||||||
|
performanceRanks.value = response.data.records.map((record:any) => ({
|
||||||
|
...record,
|
||||||
|
totalAmount: Number(record.totalAmount),
|
||||||
|
netAmount: Number(record.netAmount)
|
||||||
|
}));
|
||||||
|
total.value = response.data.total;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取绩效排行失败');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('获取绩效排行出错:', err);
|
||||||
|
message.error(err.message || '获取绩效排行失败,请稍后再试');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理分页变化
|
||||||
|
const handlePageChange = async (page: number, pageSize: number) => {
|
||||||
|
queryParams.current = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理排序变化
|
||||||
|
const handleSortChange = async () => {
|
||||||
|
queryParams.current = 1; // 重置到第一页
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置搜索条件
|
||||||
|
const resetSearch = async () => {
|
||||||
|
queryParams.nickName = '';
|
||||||
|
queryParams.phoneNumber = '';
|
||||||
|
queryParams.startDate = '';
|
||||||
|
queryParams.endDate = '';
|
||||||
|
queryParams.current = 1;
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleSearch = async () => {
|
||||||
|
queryParams.current = 1; // 搜索时重置到第一页
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理日期范围变化
|
||||||
|
// 处理日期范围变化
|
||||||
|
const handleDateChange = (_: any, dateStrings: [string, string]) => {
|
||||||
|
queryParams.startDate = dateStrings[0] || '';
|
||||||
|
queryParams.endDate = dateStrings[1] || '';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="performance-container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>员工绩效排行</h2>
|
||||||
|
|
||||||
|
<div class="filter-container">
|
||||||
|
<!-- 排序条件区域 -->
|
||||||
|
<div class="sort-section">
|
||||||
|
<div class="sort-header">排序条件</div>
|
||||||
|
<div class="sort-controls">
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.sortField"
|
||||||
|
placeholder="选择排序字段"
|
||||||
|
style="width: 150px; margin-right: 10px;"
|
||||||
|
@change="handleSortChange"
|
||||||
|
>
|
||||||
|
<Select.Option v-for="option in sortOptions" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.sortOrder"
|
||||||
|
placeholder="排序方式"
|
||||||
|
style="width: 120px; margin-right: 10px;"
|
||||||
|
@change="handleSortChange"
|
||||||
|
>
|
||||||
|
<Select.Option v-for="option in orderOptions" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索条件区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-header">搜索条件</div>
|
||||||
|
<div class="search-controls">
|
||||||
|
<Input
|
||||||
|
v-model:value="queryParams.phoneNumber"
|
||||||
|
placeholder="手机号"
|
||||||
|
style="width: 180px; margin-right: 10px;"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
v-model:value="queryParams.nickName"
|
||||||
|
placeholder="用户昵称"
|
||||||
|
style="width: 180px; margin-right: 10px;"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
@change="handleDateChange"
|
||||||
|
style="margin-right: 10px; width: 220px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="primary" @click="handleSearch" style="margin-right: 10px;">
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button @click="resetSearch">
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:dataSource="performanceRanks"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="false"
|
||||||
|
:loading="loading"
|
||||||
|
:rowKey="(record:any) => record.phoneNumber"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<!-- 金额格式化 -->
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'totalAmount'">
|
||||||
|
¥{{ record.totalAmount.toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.dataIndex === 'netAmount'">
|
||||||
|
¥{{ record.netAmount.toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<a-pagination
|
||||||
|
class="pagination"
|
||||||
|
v-model:current="queryParams.current"
|
||||||
|
v-model:pageSize="queryParams.pageSize"
|
||||||
|
:total="total"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
show-size-changer
|
||||||
|
show-quick-jumper
|
||||||
|
@change="handlePageChange"
|
||||||
|
@showSizeChange="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.performance-container {
|
||||||
|
padding: 20px;
|
||||||
|
/* 温馨的乳白色背景,带一点暖色调 */
|
||||||
|
background: #fffaf0;
|
||||||
|
border-radius: 12px;
|
||||||
|
/* 轻柔的暖色阴影 */
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 164, 64, 0.2);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.performance-container:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
/* 使用主题色 */
|
||||||
|
color: #ffa940;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
/* 柔和的卡片背景 */
|
||||||
|
background: #fff5e6;
|
||||||
|
border: 1px solid #ffd591;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-section, .search-section {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-header, .search-header {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-controls, .search-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin-top: 25px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 单元格边框去掉横线,更显高级 */
|
||||||
|
:deep(.ant-table-cell) {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页器主题色 */
|
||||||
|
:deep(.ant-pagination-item-active) a {
|
||||||
|
color: #ffa940 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-pagination-item-active) {
|
||||||
|
border-color: #ffa940 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-pagination-prev .ant-pagination-item-link),
|
||||||
|
:deep(.ant-pagination-next .ant-pagination-item-link) {
|
||||||
|
color: #ffa940;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮主题色 */
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
background-color: #ffa940;
|
||||||
|
border-color: #ffa940;
|
||||||
|
}
|
||||||
|
:deep(.ant-btn-primary:hover),
|
||||||
|
:deep(.ant-btn-primary:focus) {
|
||||||
|
background-color: #ff9d24;
|
||||||
|
border-color: #ff9d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下拉选择框主题色 */
|
||||||
|
:deep(.ant-select-selector) {
|
||||||
|
border-color: #ffd591 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-select-focused .ant-select-selector) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 164, 64, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择控件 */
|
||||||
|
:deep(.ant-picker-range) {
|
||||||
|
border-color: #ffd591 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-picker-focused) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 164, 64, 0.2) !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -230,7 +230,7 @@ const fetchProportionRate = async () => {
|
|||||||
{ level: "first" }, // 按照接口要求发送level参数
|
{ level: "first" }, // 按照接口要求发送level参数
|
||||||
{ headers: { Authorization: storedToken } }
|
{ headers: { Authorization: storedToken } }
|
||||||
);
|
);
|
||||||
|
console.log(response)
|
||||||
if (response.code === 1) {
|
if (response.code === 1) {
|
||||||
proportionRate.value = response.data;
|
proportionRate.value = response.data;
|
||||||
} else {
|
} else {
|
||||||
@ -294,8 +294,9 @@ const updateProportionRate = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchManagerPerformance();
|
|
||||||
fetchProportionRate(); // 调用获取抽成比例
|
fetchProportionRate(); // 调用获取抽成比例
|
||||||
|
fetchManagerPerformance();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterNameInput = (e: Event) => {
|
const filterNameInput = (e: Event) => {
|
||||||
@ -409,7 +410,7 @@ const settlement = async () => {
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-form-item label="主管姓名">
|
<a-form-item label="主管姓名">
|
||||||
<a-input
|
<a-input
|
||||||
style="width: 300px;"
|
style="width: 300px;border: 1px solid #ffa940;"
|
||||||
placeholder="请输入主管姓名"
|
placeholder="请输入主管姓名"
|
||||||
v-model:value="searchName"
|
v-model:value="searchName"
|
||||||
@click="handleSearch"
|
@click="handleSearch"
|
||||||
@ -419,7 +420,7 @@ const settlement = async () => {
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="手机号">
|
<a-form-item label="手机号">
|
||||||
<a-input
|
<a-input
|
||||||
style="width: 200px"
|
style="width: 300px;border: 1px solid #ffa940;"
|
||||||
placeholder="请输入手机号"
|
placeholder="请输入手机号"
|
||||||
v-model:value="searchPhone"
|
v-model:value="searchPhone"
|
||||||
class="custom-search"
|
class="custom-search"
|
||||||
@ -486,8 +487,9 @@ const settlement = async () => {
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 样式保持不变 */
|
/* 搜索框样式 */
|
||||||
.search-box {
|
.search-box {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@ -496,27 +498,18 @@ const settlement = async () => {
|
|||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-table-thead) > tr > th {
|
/* 搜索框输入框聚焦和悬浮状态边框为橙色 */
|
||||||
background-color: #f0f5ff !important;
|
.search-box .custom-search :deep(.ant-input:hover),
|
||||||
font-weight: 600;
|
.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;
|
||||||
:deep(.ant-table-row:hover) {
|
|
||||||
background-color: #fafafa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box .ant-form-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box .ant-space {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
.custom-button {
|
.custom-button {
|
||||||
background-color: #ffa940;
|
background-color: #ffa940;
|
||||||
border-color: #ffa940;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
border: 1px solid #ffa940;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-button:hover,
|
.custom-button:hover,
|
||||||
@ -527,23 +520,19 @@ const settlement = async () => {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 搜索按钮样式 */
|
||||||
.custom-search :deep(.ant-input-search-button) {
|
.custom-search :deep(.ant-input-search-button) {
|
||||||
background-color: #ffa940;
|
background-color: #ffa940;
|
||||||
border-color: #ffa940;
|
border: 1px solid #ffa940;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-search :deep(.ant-input-search-button:hover),
|
.custom-search :deep(.ant-input-search-button:hover),
|
||||||
.custom-search :deep(.ant-input-search-button:focus) {
|
.custom-search :deep(.ant-input-search-button:focus) {
|
||||||
background-color: #fa8c16;
|
background-color: #fa8c16;
|
||||||
border-color: #fa8c16;
|
border: 1px solid #ffa940;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-search :deep(.ant-input) {
|
.custom-search :deep(.ant-input) {
|
||||||
border-right-color: #ffa940;
|
border-right-color: #ffa940;
|
||||||
}
|
}
|
||||||
.custom-search :deep(.ant-input-search-button) {
|
|
||||||
background-color: #ffa940;
|
|
||||||
border-color: #ffa940;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
378
src/view/performance/supervisorPerformanceRanking.vue
Normal file
378
src/view/performance/supervisorPerformanceRanking.vue
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import myAxios from "../../api/myAxios.ts";
|
||||||
|
import {
|
||||||
|
message,
|
||||||
|
Table as ATable,
|
||||||
|
Pagination as APagination,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
DatePicker
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
interface PerformanceRank {
|
||||||
|
nickName: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
empCount: number;
|
||||||
|
promoCount: number;
|
||||||
|
orderCount: number;
|
||||||
|
totalAmount: number;
|
||||||
|
netAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PerformanceRankResponse {
|
||||||
|
code: number;
|
||||||
|
data: {
|
||||||
|
records: PerformanceRank[];
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
current: number;
|
||||||
|
pages: number;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序选项
|
||||||
|
const sortOptions = [
|
||||||
|
{ label: '员工数量', value: 'empCount' },
|
||||||
|
{ label: '推广人数', value: 'promoCount' },
|
||||||
|
{ label: '下单数量', value: 'orderCount' },
|
||||||
|
{ label: '订单总金额', value: 'totalAmount' },
|
||||||
|
{ label: '净成交金额', value: 'netAmount' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 排序方向选项
|
||||||
|
const orderOptions = [
|
||||||
|
{ label: '升序', value: 'asc' },
|
||||||
|
{ label: '降序', value: 'desc' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
sortField: "",
|
||||||
|
sortOrder: "",
|
||||||
|
nickName: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
startDate: "",
|
||||||
|
endDate: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绩效排行数据
|
||||||
|
const performanceRanks = ref<PerformanceRank[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{ title: '用户昵称', dataIndex: 'nickName', key: 'nickName' },
|
||||||
|
{ title: '手机号', dataIndex: 'phoneNumber', key: 'phoneNumber' },
|
||||||
|
{ title: '员工数量', dataIndex: 'empCount', key: 'empCount' },
|
||||||
|
{ title: '推广人数', dataIndex: 'promoCount', key: 'promoCount' },
|
||||||
|
{ title: '下单数量', dataIndex: 'orderCount', key: 'orderCount' },
|
||||||
|
{
|
||||||
|
title: '订单总金额',
|
||||||
|
dataIndex: 'totalAmount',
|
||||||
|
key: 'totalAmount',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '净成交',
|
||||||
|
dataIndex: 'netAmount',
|
||||||
|
key: 'netAmount',
|
||||||
|
customRender: ({ text }: { text: number }) => `¥${text.toFixed(2)}`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchPerformanceRank();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取绩效排行数据
|
||||||
|
const fetchPerformanceRank = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
try {
|
||||||
|
// 准备请求参数
|
||||||
|
const params = {
|
||||||
|
current: queryParams.current,
|
||||||
|
pagesize: queryParams.pageSize,
|
||||||
|
sortField: queryParams.sortField,
|
||||||
|
sortOrder: queryParams.sortOrder,
|
||||||
|
nickName: queryParams.nickName,
|
||||||
|
phoneNumber: queryParams.phoneNumber,
|
||||||
|
startDate: queryParams.startDate,
|
||||||
|
endDate: queryParams.endDate
|
||||||
|
};
|
||||||
|
|
||||||
|
const response:any = await myAxios.post<PerformanceRankResponse>(
|
||||||
|
'/perform/rank/supervisor/page',
|
||||||
|
params,
|
||||||
|
{ headers: { Authorization: token } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.code === 1) {
|
||||||
|
performanceRanks.value = response.data.records;
|
||||||
|
total.value = response.data.total;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || '获取绩效排行失败');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('获取绩效排行出错:', err);
|
||||||
|
message.error(err.message || '获取绩效排行失败,请稍后再试');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理分页变化
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
queryParams.current = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理排序变化
|
||||||
|
const handleTableChange = (_: any, __: any, sorter: any) => {
|
||||||
|
if (sorter.field) {
|
||||||
|
queryParams.sortField = sorter.field;
|
||||||
|
queryParams.sortOrder = sorter.order === 'ascend' ? 'asc' : 'desc';
|
||||||
|
} else {
|
||||||
|
queryParams.sortField = '';
|
||||||
|
queryParams.sortOrder = '';
|
||||||
|
}
|
||||||
|
fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置搜索条件
|
||||||
|
const resetSearch = () => {
|
||||||
|
queryParams.nickName = '';
|
||||||
|
queryParams.phoneNumber = '';
|
||||||
|
queryParams.startDate = '';
|
||||||
|
queryParams.endDate = '';
|
||||||
|
queryParams.sortField = '';
|
||||||
|
queryParams.sortOrder = '';
|
||||||
|
fetchPerformanceRank();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理日期范围变化
|
||||||
|
const handleDateChange = (dates: any, dateStrings: [string, string]) => {
|
||||||
|
if (dates) {
|
||||||
|
queryParams.startDate = dateStrings[0];
|
||||||
|
queryParams.endDate = dateStrings[1];
|
||||||
|
} else {
|
||||||
|
queryParams.startDate = '';
|
||||||
|
queryParams.endDate = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="performance-container">
|
||||||
|
<div class="header">
|
||||||
|
<h2>主管绩效排行</h2>
|
||||||
|
|
||||||
|
<div class="filter-container">
|
||||||
|
<!-- 排序条件区域 -->
|
||||||
|
<div class="sort-section">
|
||||||
|
<div class="sort-header">排序条件</div>
|
||||||
|
<div class="sort-controls">
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.sortField"
|
||||||
|
placeholder="选择排序字段"
|
||||||
|
style="width: 120px; margin-right: 10px;"
|
||||||
|
>
|
||||||
|
<Select.Option v-for="option in sortOptions" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.sortOrder"
|
||||||
|
placeholder="排序方式"
|
||||||
|
style="width: 100px; margin-right: 10px;"
|
||||||
|
>
|
||||||
|
<Select.Option v-for="option in orderOptions" :key="option.value" :value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索条件区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-header">搜索条件</div>
|
||||||
|
<div class="search-controls">
|
||||||
|
<Input
|
||||||
|
v-model:value="queryParams.phoneNumber"
|
||||||
|
placeholder="手机号"
|
||||||
|
style="width: 150px; margin-right: 10px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
v-model:value="queryParams.nickName"
|
||||||
|
placeholder="用户昵称"
|
||||||
|
style="width: 150px; margin-right: 10px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DatePicker.RangePicker
|
||||||
|
@change="handleDateChange"
|
||||||
|
style="margin-right: 10px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="primary" @click="fetchPerformanceRank" style="margin-right: 10px;">
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button @click="resetSearch">
|
||||||
|
清空
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-table
|
||||||
|
:dataSource="performanceRanks"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="false"
|
||||||
|
:loading="loading"
|
||||||
|
:rowKey="(record:any) => record.phoneNumber"
|
||||||
|
bordered
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'totalAmount'">
|
||||||
|
¥{{ record.totalAmount.toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.dataIndex === 'netAmount'">
|
||||||
|
¥{{ record.netAmount.toFixed(2) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<a-pagination
|
||||||
|
class="pagination"
|
||||||
|
v-model:current="queryParams.current"
|
||||||
|
v-model:pageSize="queryParams.pageSize"
|
||||||
|
:total="total"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
show-size-changer
|
||||||
|
show-quick-jumper
|
||||||
|
@change="handlePageChange"
|
||||||
|
@showSizeChange="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.performance-container {
|
||||||
|
padding: 20px;
|
||||||
|
/* 温馨的乳白色背景,带一点暖色调 */
|
||||||
|
background: #fffaf0;
|
||||||
|
border-radius: 12px;
|
||||||
|
/* 轻柔的暖色阴影 */
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 164, 64, 0.2);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.performance-container:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
/* 使用主题色 */
|
||||||
|
color: #ffa940;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
/* 柔和的卡片背景 */
|
||||||
|
background: #fff5e6;
|
||||||
|
border: 1px solid #ffd591;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-section, .search-section {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-header, .search-header {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-controls, .search-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin-top: 25px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 单元格边框去掉横线,更显高级 */
|
||||||
|
:deep(.ant-table-cell) {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页器主题色 */
|
||||||
|
:deep(.ant-pagination-item-active) a {
|
||||||
|
color: #ffa940 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-pagination-item-active) {
|
||||||
|
border-color: #ffa940 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-pagination-prev .ant-pagination-item-link),
|
||||||
|
:deep(.ant-pagination-next .ant-pagination-item-link) {
|
||||||
|
color: #ffa940;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮主题色 */
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
background-color: #ffa940;
|
||||||
|
border-color: #ffa940;
|
||||||
|
}
|
||||||
|
:deep(.ant-btn-primary:hover),
|
||||||
|
:deep(.ant-btn-primary:focus) {
|
||||||
|
background-color: #ff9d24;
|
||||||
|
border-color: #ff9d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下拉选择框主题色 */
|
||||||
|
:deep(.ant-select-selector) {
|
||||||
|
border-color: #ffd591 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-select-focused .ant-select-selector) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 164, 64, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 日期选择控件 */
|
||||||
|
:deep(.ant-picker-range) {
|
||||||
|
border-color: #ffd591 !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-picker-focused) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 164, 64, 0.2) !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 搜索框 -->
|
<!-- 搜索框 -->
|
||||||
<div class="search-box">
|
<div class="search-box" v-if="hasPermission">
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-space>
|
<a-space>
|
||||||
@ -762,8 +762,46 @@ const getUserList = async () => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 新增权限控制变量
|
||||||
|
const hasPermission = ref(true);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// 检查用户权限
|
||||||
|
await checkPermission();
|
||||||
|
// 如果有权限才加载数据
|
||||||
|
if (hasPermission.value) {
|
||||||
|
getUserList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查用户权限
|
||||||
|
const checkPermission = async () => {
|
||||||
|
try {
|
||||||
|
// 获取当前用户角色
|
||||||
|
const userRole = store.loginUser.userRole;
|
||||||
|
|
||||||
|
// 如果是管理员,显示提示并隐藏内容
|
||||||
|
if (userRole === 'admin') {
|
||||||
|
hasPermission.value = false;
|
||||||
|
// 使用Modal显示提示
|
||||||
|
Modal.warning({
|
||||||
|
title: '权限提示',
|
||||||
|
content: '管理员无权限查看管理员列表',
|
||||||
|
okText: '确定',
|
||||||
|
onOk() {
|
||||||
|
// 可以添加额外的处理逻辑
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hasPermission.value = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('权限检查失败:', error);
|
||||||
|
message.error('权限检查失败');
|
||||||
|
hasPermission.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(getUserList);
|
|
||||||
|
|
||||||
// ID查询方法
|
// ID查询方法
|
||||||
interface User {
|
interface User {
|
||||||
|
506
src/view/userList/managerInformation.vue
Normal file
506
src/view/userList/managerInformation.vue
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted} from 'vue';
|
||||||
|
import myAxios from "../../api/myAxios.ts";
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { downLoadImage } from '../../api/ImageUrl.ts';
|
||||||
|
import { EditOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
interface ManagerInfo {
|
||||||
|
id: number;
|
||||||
|
nickName: string;
|
||||||
|
userAvatar: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
userPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const managerInfo = ref<Partial<ManagerInfo>>({});
|
||||||
|
const loading = ref(true);
|
||||||
|
const error = ref(false);
|
||||||
|
const avatarError = ref(false);
|
||||||
|
const editModalVisible = ref(false);
|
||||||
|
const editForm = ref<Partial<ManagerInfo>>({});
|
||||||
|
const formLoading = ref(false);
|
||||||
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
|
const previewAvatar = ref<string | null>(null); // 预览图URL(带完整前缀)
|
||||||
|
const isUploading = ref(false);
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
nickName: [
|
||||||
|
{ required: true, message: '请输入昵称', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 16, message: '昵称长度应为2-16个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
phoneNumber: [
|
||||||
|
{ required: true, message: '请输入11位手机号', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
userPassword: [
|
||||||
|
{
|
||||||
|
min: 6,
|
||||||
|
max: 20,
|
||||||
|
message: '密码长度应为6-20个字符',
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]+$/,
|
||||||
|
message: '密码需同时包含数字、小写字母、大写字母,且不能包含空格和特殊字符',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchManagerInfo = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
error.value = false;
|
||||||
|
const storedToken = localStorage.getItem('token');
|
||||||
|
|
||||||
|
if (!storedToken) {
|
||||||
|
message.error('未找到登录信息');
|
||||||
|
error.value = true;
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: any = await myAxios.post(
|
||||||
|
'/userInfo/query/manager',
|
||||||
|
{},
|
||||||
|
{ headers: { Authorization: storedToken } }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('API响应:', response);
|
||||||
|
|
||||||
|
if (response.code === 1) {
|
||||||
|
const data = response.data;
|
||||||
|
managerInfo.value = {
|
||||||
|
id: data.id,
|
||||||
|
nickName: data.nickName,
|
||||||
|
userAvatar: data.userAvatar,
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
userPassword: data.userPassword
|
||||||
|
};
|
||||||
|
console.log(managerInfo.value)
|
||||||
|
} else {
|
||||||
|
throw new Error(response.data?.message || '获取经理信息失败');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('获取经理信息出错:', err);
|
||||||
|
message.error(err.message || '获取经理信息失败,请稍后再试');
|
||||||
|
error.value = true;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const openEditModal = () => {
|
||||||
|
editForm.value = {
|
||||||
|
...managerInfo.value,
|
||||||
|
userPassword: managerInfo.value.userPassword || '' // 回显原始密码
|
||||||
|
};
|
||||||
|
|
||||||
|
previewAvatar.value = managerInfo.value.userAvatar
|
||||||
|
? `${downLoadImage}${managerInfo.value.userAvatar}`
|
||||||
|
: null;
|
||||||
|
editModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleFileChange = async () => {
|
||||||
|
if (!fileInput.value?.files || fileInput.value.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = fileInput.value.files[0];
|
||||||
|
const storedToken = localStorage.getItem('token');
|
||||||
|
if (!storedToken) {
|
||||||
|
message.error('未找到登录信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUploading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 临时预览图(本地URL)
|
||||||
|
previewAvatar.value = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('biz', 'avatar');
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const uploadResponse: any = await myAxios.post(
|
||||||
|
'/file/upload',
|
||||||
|
formData,
|
||||||
|
{ headers: { Authorization: storedToken, 'Content-Type': 'multipart/form-data' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uploadResponse.code === 1) {
|
||||||
|
// 接口返回的 data 直接是头像标识,这里直接作为相对路径使用
|
||||||
|
let newAvatarUrl = uploadResponse.data;
|
||||||
|
// 若需要拼接前缀,根据实际场景调整,比如:
|
||||||
|
// newAvatarUrl = `自定义前缀/${uploadResponse.data}`;
|
||||||
|
|
||||||
|
// 按之前逻辑,若返回路径带 downLoadImage 前缀则处理(根据实际接口返回决定是否保留)
|
||||||
|
if (newAvatarUrl.startsWith(downLoadImage)) {
|
||||||
|
newAvatarUrl = newAvatarUrl.replace(downLoadImage, '');
|
||||||
|
}
|
||||||
|
editForm.value.userAvatar = newAvatarUrl;
|
||||||
|
message.success('头像上传成功');
|
||||||
|
} else {
|
||||||
|
throw new Error(uploadResponse.message || '上传头像失败');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('上传头像出错:', err);
|
||||||
|
message.error(err.message || '上传头像失败,请稍后再试');
|
||||||
|
// 恢复原有预览图
|
||||||
|
previewAvatar.value = managerInfo.value.userAvatar
|
||||||
|
? `${downLoadImage}${managerInfo.value.userAvatar}`
|
||||||
|
: null;
|
||||||
|
} finally {
|
||||||
|
isUploading.value = false;
|
||||||
|
// 清除临时URL,避免内存泄漏
|
||||||
|
if (previewAvatar.value?.startsWith('blob:')) {
|
||||||
|
URL.revokeObjectURL(previewAvatar.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交修改
|
||||||
|
const submitEdit = async () => {
|
||||||
|
formLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storedToken = localStorage.getItem('token');
|
||||||
|
if (!storedToken) {
|
||||||
|
message.error('未找到登录信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalUserAvatar = editForm.value.userAvatar || managerInfo.value.userAvatar;
|
||||||
|
if (!finalUserAvatar) {
|
||||||
|
throw new Error('头像信息缺失');
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitData = {
|
||||||
|
id: editForm.value.id,
|
||||||
|
nickName: editForm.value.nickName,
|
||||||
|
userAvatar: finalUserAvatar,
|
||||||
|
phoneNumber: editForm.value.phoneNumber,
|
||||||
|
userPassword: editForm.value.userPassword || undefined // 不传空字符串
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifyResponse: any = await myAxios.post(
|
||||||
|
'/userInfo/modify/manager',
|
||||||
|
submitData,
|
||||||
|
{ headers: { Authorization: storedToken } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modifyResponse.code === 1) {
|
||||||
|
message.success('经理信息更新成功');
|
||||||
|
managerInfo.value = {
|
||||||
|
...managerInfo.value,
|
||||||
|
nickName: submitData.nickName,
|
||||||
|
userAvatar: submitData.userAvatar,
|
||||||
|
phoneNumber: submitData.phoneNumber,
|
||||||
|
userPassword: submitData.userPassword || managerInfo.value.userPassword // 更新密码字段
|
||||||
|
};
|
||||||
|
editModalVisible.value = false;
|
||||||
|
} else {
|
||||||
|
throw new Error(modifyResponse.message || '更新经理信息失败');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('更新经理信息出错:', err);
|
||||||
|
message.error(err.message || '更新经理信息失败,请稍后再试');
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAvatarError = () => {
|
||||||
|
avatarError.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchManagerInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
const editFormRef = ref();
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
// 手动校验表单
|
||||||
|
const valid = await editFormRef.value?.validate();
|
||||||
|
if (valid) {
|
||||||
|
// 校验通过,执行提交
|
||||||
|
await submitEdit();
|
||||||
|
} else {
|
||||||
|
// 校验失败,不提交
|
||||||
|
message.warning('表单校验未通过,请检查输入内容');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePhoneInput = (value: string) => {
|
||||||
|
const numericValue = value.replace(/[^0-9]/g, '');
|
||||||
|
editForm.value.phoneNumber = numericValue;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="manager-container">
|
||||||
|
<h2 class="manager-title">经理信息</h2>
|
||||||
|
|
||||||
|
<div v-if="loading" class="loading-container">
|
||||||
|
<a-spin size="large" />
|
||||||
|
<p>加载经理信息中...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="error" class="error-container">
|
||||||
|
<a-alert type="error" message="获取经理信息失败" show-icon />
|
||||||
|
<a-button type="primary" @click="fetchManagerInfo" class="retry-btn">重新加载</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="managerInfo" class="manager-info-card">
|
||||||
|
<div class="avatar-container">
|
||||||
|
<a-avatar
|
||||||
|
:size="120"
|
||||||
|
:src="downLoadImage + managerInfo.userAvatar"
|
||||||
|
@error="handleAvatarError"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">ID:</span>
|
||||||
|
<span class="info-value">{{ managerInfo.id || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">昵称:</span>
|
||||||
|
<span class="info-value">{{ managerInfo.nickName || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">手机号:</span>
|
||||||
|
<span class="info-value">{{ managerInfo.phoneNumber || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="openEditModal"
|
||||||
|
class="edit-button"
|
||||||
|
>
|
||||||
|
<EditOutlined /> 编辑信息
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="no-data">
|
||||||
|
<a-empty description="暂无经理信息" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 编辑模态框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="editModalVisible"
|
||||||
|
title="编辑经理信息"
|
||||||
|
:confirm-loading="formLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="editModalVisible = false"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
layout="vertical"
|
||||||
|
:model="editForm"
|
||||||
|
:rules="rules"
|
||||||
|
ref="editFormRef"
|
||||||
|
>
|
||||||
|
<a-form-item label="昵称" name="nickName" :rules="rules.nickName">
|
||||||
|
<a-input v-model:value="editForm.nickName" placeholder="请输入昵称" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="手机号" name="phoneNumber" :rules="rules.phoneNumber">
|
||||||
|
<a-input
|
||||||
|
v-model:value="editForm.phoneNumber"
|
||||||
|
placeholder="请输入11位手机号"
|
||||||
|
@input="handlePhoneInput"
|
||||||
|
maxlength="11"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="头像">
|
||||||
|
<div class="avatar-upload-container">
|
||||||
|
<div v-if="previewAvatar" class="avatar-preview">
|
||||||
|
<img :src="previewAvatar" alt="头像预览" class="preview-image" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref="fileInput"
|
||||||
|
@change="handleFileChange"
|
||||||
|
accept="image/*"
|
||||||
|
class="file-input"
|
||||||
|
/>
|
||||||
|
<div v-if="isUploading" class="upload-status">
|
||||||
|
<a-spin size="small" />
|
||||||
|
<span>上传中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="修改密码" name="userPassword" :rules="rules.userPassword">
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="editForm.userPassword"
|
||||||
|
placeholder="如需修改密码,请在此输入新密码(6-20位)"
|
||||||
|
/>
|
||||||
|
<small style="color: #8c8c8c">留空则不更改原密码</small>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.manager-container {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #57b8ff;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manager-info-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 15px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #57606f;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container, .error-container, .no-data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
background-color: #57b8ff;
|
||||||
|
border-color: #57b8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #57b8ff;
|
||||||
|
border-color: #57b8ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-tip {
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新增头像上传样式 */
|
||||||
|
.avatar-upload-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-preview {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #57b8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.manager-container {
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1 +1 @@
|
|||||||
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/imageurl.ts","./src/api/myaxios.ts","./src/router/index.ts","./src/router/routes.ts","./src/store/index.ts","./src/store/userstore.ts","./src/types/wangeditor.d.ts","./src/app.vue","./src/layout/managelayout.vue","./src/layout/manage/manageheader.vue","./src/layout/manage/managesidebar.vue","./src/view/index.vue","./src/view/login.vue","./src/view/test.vue","./src/view/community/community.vue","./src/view/components/richtexteditor.vue","./src/view/course/addcourse.vue","./src/view/course/chapterdetail.vue","./src/view/course/coursedetail.vue","./src/view/course/coursemanagement.vue","./src/view/course/courseorder.vue","./src/view/project/addproject.vue","./src/view/project/addprojectnotice.vue","./src/view/project/moneydetail.vue","./src/view/project/noticedetail.vue","./src/view/project/project.vue","./src/view/project/projectdetail.vue","./src/view/project/projectnotice.vue","./src/view/project/promotioncode.vue","./src/view/settlement/applicationrecord.vue","./src/view/settlement/moneyrecord.vue","./src/view/settlement/withdrawalapplicationrecord.vue","./src/view/userlist/userlist.vue","./src/view/work/workdetail.vue","./src/view/work/worklist.vue"],"version":"5.6.3"}
|
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/imageurl.ts","./src/api/myaxios.ts","./src/router/index.ts","./src/router/routes.ts","./src/store/index.ts","./src/store/userstore.ts","./src/types/wangeditor.d.ts","./src/app.vue","./src/layout/managelayout.vue","./src/layout/manage/manageheader.vue","./src/layout/manage/managesidebar.vue","./src/view/index.vue","./src/view/login.vue","./src/view/test.vue","./src/view/components/richtexteditor.vue","./src/view/course/addcourse.vue","./src/view/course/chapterdetail.vue","./src/view/course/coursedetail.vue","./src/view/course/coursemanagement.vue","./src/view/course/courseorder.vue","./src/view/employeeapplication/employeeapplication.vue","./src/view/employeeapplication/employeedetail.vue","./src/view/performance/customerdetail.vue","./src/view/performance/customerorder.vue","./src/view/performance/employeeperformaince.vue","./src/view/performance/employeeperformanceranking.vue","./src/view/performance/performancemanagement.vue","./src/view/performance/supervisorperformanceranking.vue","./src/view/project/addproject.vue","./src/view/project/addprojectnotice.vue","./src/view/project/moneydetail.vue","./src/view/project/noticedetail.vue","./src/view/project/project.vue","./src/view/project/projectdetail.vue","./src/view/project/projectnotice.vue","./src/view/project/promotioncode.vue","./src/view/settlement/applicationrecord.vue","./src/view/settlement/moneyrecord.vue","./src/view/settlement/withdrawalapplicationrecord.vue","./src/view/userlist/adminlist.vue","./src/view/userlist/managerinformation.vue","./src/view/userlist/stafflist.vue","./src/view/userlist/supervisorlist.vue","./src/view/userlist/userlist.vue","./src/view/work/workdetail.vue","./src/view/work/worklist.vue"],"version":"5.6.3"}
|
Reference in New Issue
Block a user