业绩管理模块的静态样式

This commit is contained in:
2025-07-06 22:25:17 +08:00
parent 06debc411c
commit c3b2fcd433
18 changed files with 1317 additions and 233 deletions

BIN
dist 9091.zip Normal file

Binary file not shown.

BIN
dist 9092.zip Normal file

Binary file not shown.

View File

@ -1,4 +1,8 @@
// export const downLoadImage = 'http://1.94.237.210:3457/file/download/'
export const downLoadImage = 'http://27.30.77.229:9092/file/download/'
// export const downLoadImage = 'http://27.30.77.229:9092/file/download/'
export const downLoadImage = 'http://160.202.242.36:9092/file/download/'
// export const downLoadImage = 'http://160.202.242.36:9091/file/download/'

View File

@ -7,11 +7,14 @@ import router from "../router";
const myAxios = axios.create({
withCredentials: true,
// baseURL:'http://localhost:9091'
baseURL:'http://localhost:9092'
//baseURL:'http://localhost:9092'
// baseURL:'http://1.94.237.210:3457'
//baseURL:'http://1.94.237.210:8088'
//baseURL:'http://27.30.77.229:9091/'
//baseURL:'http://27.30.77.229:9092/'
// baseURL:'http://160.202.242.36:9092/'
baseURL:'http://160.202.242.36:9092/'
});

View File

@ -54,9 +54,9 @@
<a-menu-item key="/workDetail">工作详情</a-menu-item>
</a-sub-menu>
<a-menu-item key="/community">
<a-menu-item key="/performanceManagement">
<CommentOutlined />
<span>社群</span>
<span>业绩管理</span>
</a-menu-item>
</a-menu>

View File

@ -32,11 +32,6 @@ export const routes = [
name: '工作详情',
component: () => import("../view/work/workDetail.vue"),
},
{
path: '/community',
name: '社群',
component: () => import("../view/community/community.vue"),
},
{
path: '/userList',
name: '用户列表',
@ -122,6 +117,26 @@ export const routes = [
name:'课程订单',
component: ()=> import("../view/course/courseOrder.vue")
},
{
path:'/performanceManagement',
name:'主管业绩报表',
component: ()=> import("../view/performance/performanceManagement.vue")
},
{
path:'/employeePerformaince',
name:'员工业绩报表',
component: ()=> import("../view/performance/employeePerformaince.vue")
},
{
path:'/customerOrder',
name:'客户订单明细',
component: ()=> import("../view/performance/customerOrder.vue")
},
{
path:'/customerDetail',
name:'订单明细详情',
component: ()=> import("../view/performance/customerDetail.vue")
},
]
},
]

View File

@ -1,4 +0,0 @@
<template>
<div>465</div>
</template>

View File

@ -71,7 +71,7 @@ const editorConfig = {
if (res.code === 1) {
// 拼接完整 URL 地址再插入到富文本中
const imageUrl = 'http://27.30.77.229:9092/file/download/' + res.data
const imageUrl = 'http://160.202.242.36:9092/file/download/' + res.data
insertFn(imageUrl)
} else {
console.error('上传失败:', res.message)

View File

@ -31,11 +31,6 @@
<div class="phone-section-content" v-html="formData.detail"></div>
</div>
<!-- 推广码说明 -->
<div class="phone-section">
<h3 class="phone-section-title">推广码说明</h3>
<div class="phone-section-content" v-html="formData.promoCodeDesc"></div>
</div>
</div>
</div>
</div>
@ -128,39 +123,6 @@
</div>
</div>
<div class="commission-group">
<div class="input-group">
<label class="input-label">
<span class="label-text">一级佣金比例%</span>
<input
v-model.number="formData.firstLevelRate"
type="number"
min="1"
max="99"
class="input-field"
required
placeholder="1-99"
@input="validateRates"
>
</label>
</div>
<div class="input-group">
<label class="input-label">
<span class="label-text">二级佣金比例%</span>
<input
v-model.number="formData.secondLevelRate"
type="number"
min="1"
max="99"
class="input-field"
required
placeholder="1-99"
@input="validateRates"
>
</label>
</div>
</div>
</div>
</div>
@ -176,14 +138,6 @@
/>
</div>
<div class="rich-text-group">
<span class="label-text">推广码说明</span>
<RichTextEditor
v-model="formData.promoCodeDesc"
:disable="false"
@content-change="(html:any) => formData.promoCodeDesc = html"
/>
</div>
</div>
</div>
@ -191,6 +145,10 @@
<span>立即创建</span>
<div class="button-sparkles"></div>
</button>
<button type="button" class="submit-button-return" @click="returnBack">
<span>返回</span>
<div class="button-sparkles"></div>
</button>
</form>
</template>
@ -268,7 +226,6 @@ const encode64 = (text: string): string => {
const encryptValue = (value: any, key: string): any => {
// 数值型字段直接返回,不加密
if (key === 'originPrice' || key === 'discountPrice' ||
key === 'firstLevelRate' || key === 'secondLevelRate' ||
key === 'image' || key ==='name'||key === 'type') {
return value;
}
@ -292,17 +249,9 @@ const validatePrices = () => {
return true;
};
const validateRates = () => {
if (formData.secondLevelRate >= formData.firstLevelRate) {
return false;
}
return true;
};
const handleSubmit = async () => {
// 验证价格和佣金比例
const isPriceValid = validatePrices();
const isRateValid = validateRates();
let errorMessage = '';
@ -310,9 +259,6 @@ const handleSubmit = async () => {
errorMessage += '折扣价必须小于原价\n';
}
if (!isRateValid) {
errorMessage += '二级佣金比例必须小于一级佣金比例\n';
}
if (errorMessage) {
alert(errorMessage);
@ -370,11 +316,8 @@ const validateForm = (): boolean => {
'type',
'image',
'detail',
'promoCodeDesc',
'originPrice',
'discountPrice',
'firstLevelRate',
'secondLevelRate'
'discountPrice'
];
return requiredFields.every(field => {
@ -382,6 +325,10 @@ const validateForm = (): boolean => {
return value !== '' && value !== null && value !== undefined;
});
};
const returnBack=()=>{
router.push('/courseManagement')
}
</script>
@ -643,17 +590,10 @@ const validateForm = (): boolean => {
width: 100%;
}
.rich-text-columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
align-items: start;
}
.rich-text-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
height: 100%;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
@ -850,5 +790,27 @@ const validateForm = (): boolean => {
margin-left: 20px;
margin-bottom: 10px;
}
.submit-button-return {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #ffa940 0%, #ffe7ba 100%);
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.submit-button-return:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
}
</style>

View File

@ -289,28 +289,12 @@ const limitPrice = (value: number, field: keyof CourseDetail) => {
if (value < 0) (editData.value as any)[field] = 0;
};
const limitRate = (value: number, field: keyof CourseDetail) => {
if (!editData.value) return;
// 确保是数字
if (isNaN(value)) {
(editData.value as any)[field] = 0;
return;
}
// 限制佣金比例在0-100之间
if (value < 0) (editData.value as any)[field] = 0;
if (value > 100) (editData.value as any)[field] = 100;
};
// 添加输入时实时限制 - 立即修正值
const handlePriceInput = (value: number, field: keyof CourseDetail) => {
limitPrice(value, field);
};
const handleRateInput = (value: number, field: keyof CourseDetail) => {
limitRate(value, field);
};
</script>
<template>
@ -349,14 +333,7 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
<label>折扣价</label>
<div class="value">¥{{ courseData.discountPrice }}</div>
</div>
<div class="info-item">
<label>一级佣金比例</label>
<div class="value">{{ courseData.firstLevelRate }}%</div>
</div>
<div class="info-item">
<label>二级佣金比例</label>
<div class="value">{{ courseData.secondLevelRate }}%</div>
</div>
</div>
</div>
@ -366,10 +343,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
<div class="rich-content" v-html="courseData.detail"></div>
</div>
<div class="rich-section" v-if="courseData.promoCodeDesc">
<h2 class="section-title">推广码说明</h2>
<div class="rich-content" v-html="courseData.promoCodeDesc"></div>
</div>
</div>
<!-- 编辑页 -->
@ -433,43 +406,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item
label="一级佣金比例(%"
name="firstLevelRate"
>
<a-input-number
v-model:value="editData.firstLevelRate"
style="width: 100%"
:min="0"
:max="100"
@keypress="handleNumberInput"
@paste="handlePaste"
@change="(val:any) => handleRateInput(val, 'firstLevelRate')"
placeholder="0-100"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
label="二级佣金比例(%"
name="secondLevelRate"
>
<a-input-number
v-model:value="editData.secondLevelRate"
style="width: 100%"
:min="0"
:max="100"
@keypress="handleNumberInput"
@paste="handlePaste"
@change="(val:any) => handleRateInput(val, 'secondLevelRate')"
placeholder="0-100"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="课程图片">
<div class="file-upload">
@ -517,14 +453,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
/>
</div>
<div class="rich-text-group">
<span class="label-text">推广码说明</span>
<RichTextEditor
v-model="editData.promoCodeDesc"
:disable="false"
@content-change="(html: string) => { if (editData) editData.promoCodeDesc = html }"
/>
</div>
</div>
</div>
</div>
@ -619,9 +547,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
font-weight: 500;
}
.section {
margin-bottom: 2.5rem;
}
.section-title {
font-size: 1.4rem;
@ -631,10 +556,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
margin: 1.5rem 0;
}
.section-content {
line-height: 1.6;
color: #444;
}
.rich-section {
margin-bottom: 2.5rem;
@ -710,9 +631,6 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
margin-top: 2rem;
}
.ql-container {
min-height: 120px;
}
.file-upload {
position: relative;
@ -809,12 +727,7 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
margin-top: 1.5rem;
}
.rich-text-columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
align-items: start;
}
.rich-text-group {
display: flex;
@ -862,24 +775,5 @@ const handleRateInput = (value: number, field: keyof CourseDetail) => {
border: none !important;
}
.textarea-field {
min-height: 100px;
resize: vertical;
}
.textarea-field:focus {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
outline: none;
}
.textarea-field {
width: 100%;
padding: 0.8rem 1.2rem;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
transition: all 0.3s ease;
font-size: 1rem;
background: white;
}
</style>

View File

@ -84,14 +84,6 @@
{{ record.orderCount }}
</template>
<template v-if="column.key === 'firstLevelRate'">
{{ record.firstLevelRate }}%
</template>
<template v-if="column.key === 'secondLevelRate'">
{{ record.secondLevelRate }}%
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
@ -118,13 +110,13 @@
>
详情
</a-button>
<a-button
size="small"
type="link"
@click="chapterDetails(record.id)"
>
章节
</a-button>
<!-- <a-button-->
<!-- size="small"-->
<!-- type="link"-->
<!-- @click="chapterDetails(record.id)"-->
<!-- >-->
<!-- 章节-->
<!-- </a-button>-->
</a-space>
</template>
</template>
@ -227,24 +219,7 @@ const columns = [
sorter: true,
sortDirections: ['ascend', 'descend']
},
{
title: '一级佣金比例',
dataIndex: 'firstLevelRate',
key: 'firstLevelRate',
width: 65,
align: 'center',
sorter: true,
sortDirections: ['ascend', 'descend']
},
{
title: '二级佣金比例',
dataIndex: 'secondLevelRate',
key: 'secondLevelRate',
width: 65,
align: 'center',
sorter: true,
sortDirections: ['ascend', 'descend']
},
{
title: '操作',
key: 'action',
@ -286,6 +261,12 @@ const toggleShelves = async (record: Course) => {
// 课程名称搜索方法
const handleCourseSearch = async () => {
// 添加空值校验
if (!searchCourseName.value.trim()) {
message.warning('课程名称不能为空');
return;
}
searchParams.value.name = searchCourseName.value;
searchParams.value.type = searchCourseType.value;
searchParams.value.isShelves = searchIsShelves.value;
@ -308,6 +289,15 @@ const getCourseList = async () => {
tableData.value = res.data.records;
// 同步总条数到分页组件
pagination.value.total = res.data.total;
// 添加搜索成功提示 - 只有实际搜索操作时显示
if (searchParams.value.name || searchParams.value.type || searchParams.value.isShelves) {
if (res.data.records.length > 0) {
message.success(`搜索成功,共找到 ${res.data.records.length} 条记录`);
} else {
message.info('没有找到匹配的课程');
}
}
} else {
message.error(res.message || '请求失败');
}
@ -478,12 +468,12 @@ const showDetails = (id: string) => {
}
// 查看章节详情
const chapterDetails = (id: string) => {
router.push({
path: '/chapterDetail',
query: { id: String(id) }
})
}
// const chapterDetails = (id: string) => {
// router.push({
// path: '/chapterDetail',
// query: { id: String(id) }
// })
// }
</script>
<style scoped>

View File

@ -91,6 +91,13 @@
>
删除
</a-button>
<a-button
size="small"
primary
>
退款
</a-button>
</a-space>
</template>
</template>
@ -155,6 +162,19 @@ const columns = [
width: 75,
align: 'center'
},
{
title: '用户ID',
dataIndex: 'userId',
width: 90,
key: 'userId',
align: 'center'
},{
title: '课程ID',
dataIndex: 'courseId',
width: 90,
key: 'courseId',
align: 'center'
},
{
title: '课程名称',
dataIndex: 'name',
@ -232,7 +252,7 @@ const columns = [
title: '操作',
key: 'action',
fixed: 'right',
width: 95,
width: 130,
align: 'center'
}
];

View File

@ -0,0 +1,133 @@
<script setup lang="ts">
// 这里可以定义一些响应式数据或者方法,但是由于是静态页面,这里暂时不需要
</script>
<template>
<div class="order-details">
<!-- 订单状态 -->
<div class="order-status card">
<span class="status-label">订单状态</span>
<span class="status-value status-refunded">已退款</span>
</div>
<!-- 商品信息 -->
<div class="product-info card">
<img src="https://via.placeholder.com/100" alt="商品图片" class="product-image"/>
<div class="product-text">
<p class="product-title">陈正康考研英语核心2000词汇(考研英语核心2000词汇)</p>
<p class="product-subtitle">23考研·训练营</p>
</div>
<span class="product-price">¥9.9</span>
</div>
<!-- 订单信息 -->
<div class="order-info card">
<p>订单编号202506190848352695125648642</p>
<p>下单时间2025-06-19 08:48:35</p>
<p>退款时间2025-07-02 11:00:00</p>
</div>
<!-- 价格明细 -->
<div class="price-details card">
<div class="price-row"><span>课程价格</span><span>¥19.9</span></div>
<div class="price-row"><span>折扣优惠</span><span>-¥10</span></div>
<div class="price-row"><span>订单金额</span><span>¥9.9</span></div>
<div class="price-row refund"><span>退款金额</span><span>¥9.9</span></div>
</div>
</div>
</template>
<style scoped>
.order-details {
width: 100%;
max-width: 600px;
margin: 30px auto;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.card {
background: linear-gradient(to right bottom, #f9f9f9, #ffffff);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
padding: 20px;
margin-bottom: 20px;
transition: all 0.3s ease-in-out;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}
/* 订单状态 */
.order-status {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.status-label {
color: #333;
}
.status-value {
font-weight: bold;
padding: 5px 10px;
border-radius: 6px;
}
.status-refunded {
color: #fff;
background-color: #e57373;
}
/* 商品信息 */
.product-info {
display: flex;
align-items: center;
}
.product-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 8px;
margin-right: 15px;
}
.product-text {
flex: 1;
}
.product-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.product-subtitle {
font-size: 14px;
color: #777;
}
.product-price {
font-size: 18px;
font-weight: bold;
color: #d32f2f;
}
/* 订单信息 */
.order-info p {
margin: 6px 0;
font-size: 14px;
color: #555;
}
/* 价格明细 */
.price-details {
line-height: 1.8;
}
.price-row {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #444;
}
.price-row.refund span {
color: #d32f2f;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,347 @@
<template>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="订单号">
<a-input-search
style="width: 300px"
placeholder="请输入订单号"
enter-button
@search="handleSearch"
v-model:value="searchOrderNumber"
class="custom-search"
/>
</a-form-item>
<a-button class="custom-button" @click="reset">重置搜索</a-button>
</a-space>
</a-form>
</div>
<!-- 数据-->
<a-table
:columns="columns"
:data-source="tableData"
:scroll="{ x: 2500, y: 550 }"
:loading="loading"
bordered
rowKey="id"
>
<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'">
¥{{record.amount }}
</template>
<!-- 状态 -->
<template v-if="column.key === 'status'">
<a-tag :color="record.status === '交易成功' ? 'green' : 'orange'">
{{ record.status }}
</a-tag>
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<a-space :size="8">
<a-button
size="small"
@click="customerDetail(record.id)"
>
详情
</a-button>
<a-button
size="small"
danger
@click="deleteOrder(record.id)"
>
删除
</a-button>
</a-space>
</template>
</template>
</a-table>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { message, Modal } from "ant-design-vue";
import router from "../../router";
const loading = ref(false);
const searchOrderNumber = ref("");
// 客户订单明细表
const columns = [
{
title: '编号',
dataIndex: 'id',
width: 80,
key: 'id',
fixed: 'left',
align: 'center'
},
{
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
width: 200,
fixed: 'left',
align: 'center'
},
{
title: '用户名',
dataIndex: 'userName',
key: 'userName',
width: 120,
align: 'center'
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 120,
align: 'center'
},
{
title: '课程名称',
dataIndex: 'courseName',
key: 'courseName',
width: 150,
align: 'center'
},
{
title: '类别',
dataIndex: 'category',
key: 'category',
width: 100,
align: 'center'
},
{
title: '图片',
dataIndex: 'image',
key: 'image',
width: 100,
align: 'center'
},
{
title: '金额',
dataIndex: 'amount',
key: 'amount',
width: 100,
align: 'center'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
align: 'center'
},
{
title: '下单时间',
dataIndex: 'orderTime',
key: 'orderTime',
width: 180,
align: 'center'
},
{
title: '支付时间',
dataIndex: 'paymentTime',
key: 'paymentTime',
width: 180,
align: 'center'
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 100,
align: 'center'
}
];
// 客户订单接口定义
interface OrderRecord {
id: number;
orderNumber: string;
userName: string;
phone: string;
courseName: string;
category: string;
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 = () => {
if (!searchOrderNumber.value.trim()) {
tableData.value = [...originalTableData.value];
return;
}
const filtered = originalTableData.value.filter(item =>
item.orderNumber.includes(searchOrderNumber.value)
);
if (filtered.length === 0) {
message.warning("未找到匹配的订单");
tableData.value = [];
} else {
tableData.value = filtered;
}
};
// 删除订单
const deleteOrder = (id: number) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除该订单吗?删除后数据将无法恢复!',
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 = () => {
searchOrderNumber.value = "";
tableData.value = [...originalTableData.value];
};
// 加载模拟数据
onMounted(() => {
loading.value = true;
setTimeout(() => {
const mockData = generateMockData();
tableData.value = mockData;
originalTableData.value = mockData;
loading.value = false;
}, 800);
});
const customerDetail =()=>{
router.push('/customerDetail')
}
</script>
<style scoped>
:deep(.ant-divider-vertical) {
border-color: rgba(0, 0, 0, 0.15);
height: 1.2em;
margin: 0 4px;
}
.search-box {
margin-bottom: 10px;
}
.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;
}
.search-box {
margin-bottom: 10px;
}
.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;
}
:deep(.ant-table-thead) > tr > th {
background-color: #f0f5ff !important;
font-weight: 600;
}
:deep(.ant-table-row:hover) {
background-color: #fafafa !important;
}
</style>

View File

@ -0,0 +1,329 @@
<script setup lang="ts">
import {ref, onMounted} from "vue";
import {useRoute, useRouter} from "vue-router";
import {message} from "ant-design-vue";
// 修改列定义为主管业绩相关字段
const columns = [
{
title: '员工姓名',
dataIndex: 'name',
key: 'name',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 120,
align: 'center'
},
{
title: '员工数',
dataIndex: 'staffCount',
key: 'staffCount',
width: 80,
align: 'center'
},
{
title: '推广数',
dataIndex: 'promotionCount',
key: 'promotionCount',
width: 80,
align: 'center'
},
{
title: '比例',
dataIndex: 'ratio',
key: 'ratio',
width: 80,
align: 'center'
},
{
title: '下单量',
dataIndex: 'orderCount',
key: 'orderCount',
width: 80,
align: 'center'
},
{
title: '总订单',
dataIndex: 'totalOrders',
key: 'totalOrders',
width: 90,
align: 'center'
},
{
title: '退款',
dataIndex: 'refund',
key: 'refund',
width: 80,
align: 'center'
},
{
title: '净成交',
dataIndex: 'netAmount',
key: 'netAmount',
width: 100,
align: 'center'
},
{
title: '待释放',
dataIndex: 'pendingRelease',
key: 'pendingRelease',
width: 90,
align: 'center'
},
{
title: '可结算',
dataIndex: 'settleable',
key: 'settleable',
width: 90,
align: 'center'
},
{
title: '已回退',
dataIndex: 'returned',
key: 'returned',
width: 90,
align: 'center'
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 130,
align: 'center'
}
];
// 定义主管业绩数据结构
interface ManagerPerformance {
id: number;
name: string;
phone: string;
staffCount: number;
promotionCount: number;
ratio: string;
orderCount: number;
totalOrders: number;
refund: number;
netAmount: number;
pendingRelease: number;
settleable: number;
returned: number;
}
const tableData = ref<ManagerPerformance[]>([]);
const loading = ref(false);
const searchName = ref("");
const searchPhone = ref("");
// 生成模拟数据
const generateMockData = () => {
const mockData: ManagerPerformance[] = [];
const names = ['张员工', '李员工', '王员工', '赵员工', '钱员工', '孙员工', '周员工', '吴员工'];
const phones = ['13800138000', '13900139000', '13700137000', '13600136000', '13500135000', '13400134000', '13300133000', '13200132000'];
for (let i = 0; i < 15; i++) {
const nameIndex = i % names.length;
const phoneIndex = i % phones.length;
mockData.push({
id: i + 1,
name: names[nameIndex],
phone: phones[phoneIndex],
staffCount: Math.floor(Math.random() * 20) + 5,
promotionCount: Math.floor(Math.random() * 100) + 50,
ratio: `${Math.floor(Math.random() * 20) + 10}%`,
orderCount: Math.floor(Math.random() * 500) + 100,
totalOrders: Math.floor(Math.random() * 1000) + 500,
refund: Math.floor(Math.random() * 50) + 10,
netAmount: Math.floor(Math.random() * 50000) + 10000,
pendingRelease: Math.floor(Math.random() * 10000) + 5000,
settleable: Math.floor(Math.random() * 30000) + 10000,
returned: Math.floor(Math.random() * 5000) + 1000
});
}
return mockData;
};
const originalTableData = ref<ManagerPerformance[]>([]);
// 搜索功能
const handleSearch = () => {
if (!searchName.value.trim() && !searchPhone.value.trim()) {
tableData.value = [...originalTableData.value];
return;
}
const filtered = originalTableData.value.filter(item => {
const nameMatch = searchName.value ? item.name.includes(searchName.value) : true;
const phoneMatch = searchPhone.value ? item.phone.includes(searchPhone.value) : true;
return nameMatch && phoneMatch;
});
if (filtered.length === 0) {
message.warning("未找到匹配员工业绩数据");
tableData.value = [];
} else {
tableData.value = filtered;
}
};
// 重置搜索
const reset = () => {
searchName.value = "";
searchPhone.value = "";
tableData.value = [...originalTableData.value];
};
const router = useRouter();
// 查看员工绩效明细 - 添加跳转功能
const viewStaffPerformance = (record: ManagerPerformance) => {
// 跳转到员工绩效页面并传递主管ID作为参数
router.push({
path: '/customerOrder',
query: {
managerId: record.id,
managerName: record.name
}
});
};
onMounted(() => {
loading.value = true;
// 模拟数据加载延迟
setTimeout(() => {
const mockData = generateMockData();
tableData.value = mockData;
originalTableData.value = mockData;
loading.value = false;
}, 800);
});
const goBack = () => {
router.push('/project');
};
</script>
<template>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="员工姓名">
<a-input-search
style="width: 300px"
placeholder="请输入员工姓名"
class="custom-search"
enter-button
v-model:value="searchName"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input-search
style="width: 300px"
placeholder="请输入手机号"
v-model:value="searchPhone"
@pressEnter="handleSearch"
enter-button
class="custom-search"
/>
</a-form-item>
<a-button @click="reset">重置</a-button>
<a-button class="custom-button" @click="goBack">返回</a-button>
</a-space>
</a-form>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="tableData"
:scroll="{ x: 1500 }"
:loading="loading"
bordered
rowKey="id"
size="middle"
>
<template #bodyCell="{ column, record }">
<!-- 金额格式化 -->
<template v-if="['netAmount', 'pendingRelease', 'settleable', 'returned','refund'].includes(column.dataIndex)">
¥{{ record[column.dataIndex as keyof ManagerPerformance].toLocaleString() }}
</template>
<!-- 操作列 - 添加点击事件 -->
<template v-if="column.key === 'action'">
<a-button
size="small"
@click="viewStaffPerformance(record)"
>
客户订单明细
</a-button>
</template>
</template>
</a-table>
</template>
<style scoped>
.search-box {
margin-bottom: 20px;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
:deep(.ant-table-thead) > tr > th {
background-color: #f0f5ff !important;
font-weight: 600;
}
: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 {
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;
}
</style>

View File

@ -0,0 +1,329 @@
<script setup lang="ts">
import {ref, onMounted} from "vue";
import {useRoute, useRouter} from "vue-router";
import {message} from "ant-design-vue";
// 修改列定义为主管业绩相关字段
const columns = [
{
title: '主管姓名',
dataIndex: 'name',
key: 'name',
width: 100,
fixed: 'left',
align: 'center'
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 120,
align: 'center'
},
{
title: '员工数',
dataIndex: 'staffCount',
key: 'staffCount',
width: 80,
align: 'center'
},
{
title: '推广数',
dataIndex: 'promotionCount',
key: 'promotionCount',
width: 80,
align: 'center'
},
{
title: '比例',
dataIndex: 'ratio',
key: 'ratio',
width: 80,
align: 'center'
},
{
title: '下单量',
dataIndex: 'orderCount',
key: 'orderCount',
width: 80,
align: 'center'
},
{
title: '总订单',
dataIndex: 'totalOrders',
key: 'totalOrders',
width: 90,
align: 'center'
},
{
title: '退款',
dataIndex: 'refund',
key: 'refund',
width: 80,
align: 'center'
},
{
title: '净成交',
dataIndex: 'netAmount',
key: 'netAmount',
width: 100,
align: 'center'
},
{
title: '待释放',
dataIndex: 'pendingRelease',
key: 'pendingRelease',
width: 90,
align: 'center'
},
{
title: '可结算',
dataIndex: 'settleable',
key: 'settleable',
width: 90,
align: 'center'
},
{
title: '已回退',
dataIndex: 'returned',
key: 'returned',
width: 90,
align: 'center'
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 130,
align: 'center'
}
];
// 定义主管业绩数据结构
interface ManagerPerformance {
id: number;
name: string;
phone: string;
staffCount: number;
promotionCount: number;
ratio: string;
orderCount: number;
totalOrders: number;
refund: number;
netAmount: number;
pendingRelease: number;
settleable: number;
returned: number;
}
const tableData = ref<ManagerPerformance[]>([]);
const loading = ref(false);
const searchName = ref("");
const searchPhone = ref("");
// 生成模拟数据
const generateMockData = () => {
const mockData: ManagerPerformance[] = [];
const names = ['张主管', '李经理', '王总监', '赵主任', '钱组长', '孙课长', '周经理', '吴主管'];
const phones = ['13800138000', '13900139000', '13700137000', '13600136000', '13500135000', '13400134000', '13300133000', '13200132000'];
for (let i = 0; i < 15; i++) {
const nameIndex = i % names.length;
const phoneIndex = i % phones.length;
mockData.push({
id: i + 1,
name: names[nameIndex],
phone: phones[phoneIndex],
staffCount: Math.floor(Math.random() * 20) + 5,
promotionCount: Math.floor(Math.random() * 100) + 50,
ratio: `${Math.floor(Math.random() * 20) + 10}%`,
orderCount: Math.floor(Math.random() * 500) + 100,
totalOrders: Math.floor(Math.random() * 1000) + 500,
refund: Math.floor(Math.random() * 50) + 10,
netAmount: Math.floor(Math.random() * 50000) + 10000,
pendingRelease: Math.floor(Math.random() * 10000) + 5000,
settleable: Math.floor(Math.random() * 30000) + 10000,
returned: Math.floor(Math.random() * 5000) + 1000
});
}
return mockData;
};
const originalTableData = ref<ManagerPerformance[]>([]);
// 搜索功能
const handleSearch = () => {
if (!searchName.value.trim() && !searchPhone.value.trim()) {
tableData.value = [...originalTableData.value];
return;
}
const filtered = originalTableData.value.filter(item => {
const nameMatch = searchName.value ? item.name.includes(searchName.value) : true;
const phoneMatch = searchPhone.value ? item.phone.includes(searchPhone.value) : true;
return nameMatch && phoneMatch;
});
if (filtered.length === 0) {
message.warning("未找到匹配的主管业绩数据");
tableData.value = [];
} else {
tableData.value = filtered;
}
};
// 重置搜索
const reset = () => {
searchName.value = "";
searchPhone.value = "";
tableData.value = [...originalTableData.value];
};
const router = useRouter();
// 查看员工绩效明细 - 添加跳转功能
const viewStaffPerformance = (record: ManagerPerformance) => {
// 跳转到员工绩效页面并传递主管ID作为参数
router.push({
path: '/employeePerformaince',
query: {
managerId: record.id,
managerName: record.name
}
});
};
onMounted(() => {
loading.value = true;
// 模拟数据加载延迟
setTimeout(() => {
const mockData = generateMockData();
tableData.value = mockData;
originalTableData.value = mockData;
loading.value = false;
}, 800);
});
const goBack = () => {
router.push('/project');
};
</script>
<template>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="主管姓名">
<a-input-search
style="width: 300px"
placeholder="请输入主管姓名"
class="custom-search"
enter-button
v-model:value="searchName"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input-search
style="width: 300px"
placeholder="请输入手机号"
v-model:value="searchPhone"
@pressEnter="handleSearch"
enter-button
class="custom-search"
/>
</a-form-item>
<a-button @click="reset">重置</a-button>
<a-button class="custom-button" @click="goBack">返回</a-button>
</a-space>
</a-form>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data-source="tableData"
:scroll="{ x: 1500 }"
:loading="loading"
bordered
rowKey="id"
size="middle"
>
<template #bodyCell="{ column, record }">
<!-- 金额格式化 -->
<template v-if="['netAmount', 'pendingRelease', 'settleable', 'returned','refund'].includes(column.dataIndex)">
¥{{ record[column.dataIndex as keyof ManagerPerformance].toLocaleString() }}
</template>
<!-- 操作列 - 添加点击事件 -->
<template v-if="column.key === 'action'">
<a-button
size="small"
@click="viewStaffPerformance(record)"
>
员工绩效明细
</a-button>
</template>
</template>
</a-table>
</template>
<style scoped>
.search-box {
margin-bottom: 20px;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
:deep(.ant-table-thead) > tr > th {
background-color: #f0f5ff !important;
font-weight: 600;
}
: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 {
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;
}
</style>

View File

@ -201,6 +201,10 @@
<span>立即创建</span>
<div class="button-sparkles"></div>
</button>
<button type="button" class="submit-button-return" @click="returnBack">
<span>返回</span>
<div class="button-sparkles"></div>
</button>
</form>
</template>
@ -433,6 +437,10 @@ const limitPromoterCount = (event: Event) => {
formData.maxPromoterCount = value;
};
const returnBack =()=>{
router.push('/project')
}
</script>
@ -589,7 +597,29 @@ const limitPromoterCount = (event: Event) => {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
}
.submit-button-return {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #ffa940 0%, #ffe7ba 100%);
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.submit-button-return:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
}
.button-sparkles {
position: absolute;
width: 20px;

View File

@ -76,6 +76,10 @@
<span>发布通知</span>
<div class="button-sparkles"></div>
</button>
<button type="button" class="submit-button-return" @click="returnBack">
<span>返回</span>
<div class="button-sparkles"></div>
</button>
</form>
</template>
@ -149,6 +153,10 @@ const handleSubmit = async () => {
alert('通知发布失败,请检查网络或数据格式');
}
};
const returnBack=()=>{
router.push('/projectNotice')
}
</script>
<style scoped>
@ -439,4 +447,28 @@ const handleSubmit = async () => {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.submit-button-return {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #ffa940 0%, #ffe7ba 100%);
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.submit-button-return:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
}
</style>