瀏覽代碼

add:绩效管理

zoie 1 月之前
父節點
當前提交
36234e291f

+ 1 - 0
components.d.ts

@@ -40,6 +40,7 @@ declare module '@vue/runtime-core' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElLink: typeof import('element-plus/es')['ElLink']
     ElLink: typeof import('element-plus/es')['ElLink']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenu: typeof import('element-plus/es')['ElMenu']

+ 47 - 0
src/api/performance/index.ts

@@ -0,0 +1,47 @@
+import $http from '../index'
+
+/**
+ * 绩效点管理接口
+ */
+// 绩效点列表查询
+export const PerformancePoints_List = (params: any) => $http.post('/salary/PerformancePoints/List', params)
+// 新增绩效点
+export const PerformancePoints_Add = (params: any) => $http.post('/salary/PerformancePoints/Add', params)
+// 修改绩效点
+export const PerformancePoints_Edit = (params: any) => $http.post('/salary/PerformancePoints/Edit', params)
+// 删除绩效点
+export const PerformancePoints_Del = (params: any) => $http.post('/salary/PerformancePoints/Del', params)
+
+/**
+ * 绩效考核管理接口
+ */
+// 绩效考核列表查询
+export const Performance_List = (params: any) => $http.post('/salary/Performance/List', params)
+export const Performance_User_List = (params: any) => $http.post('/salary/Performance/User_List', params)
+// 新增绩效考核
+export const Performance_Add = (params: any) => $http.post('/salary/Performance/Add', params)
+// 修改绩效考核
+export const Performance_Edit = (params: any) => $http.post('/salary/Performance/Edit', params)
+// 删除绩效考核
+export const Performance_Del = (params: any) => $http.post('/salary/Performance/Del', params)
+// 绩效考核详情
+export const Performance_Detail = (params: any) => $http.post('/salary/Performance/Detail', params)
+// 绩效搜索框列表
+export const Performance_Submit_User = (params: any) => $http.post('/salary/Performance/Submit_User', params)
+// 提交审核
+export const Performance_Edit_Audit = (params: any) => $http.post('/salary/Performance/Edit_Audit', params)
+// 导出Excel
+export const Performance_User_Excel = (params: any) => $http.post('/salary/Performance/User_Excel', params, { responseType: 'blob' })
+export const Performance_Excel = (params: any) => $http.post('/salary/Performance/Excel', params, { responseType: 'blob' })
+
+/**
+ * 绩效指标管理接口
+ */
+// 绩效指标列表查询
+export const PerformanceTarget_List = (params: any) => $http.post('/salary/PerformanceTarget/List', params)
+// 新增绩效指标
+export const PerformanceTarget_Add = (params: any) => $http.post('/salary/PerformanceTarget/Add', params)
+// 修改绩效指标
+export const PerformanceTarget_Edit = (params: any) => $http.post('/salary/PerformanceTarget/Edit', params)
+// 删除绩效指标
+export const PerformanceTarget_Del = (params: any) => $http.post('/salary/PerformanceTarget/Del', params)

+ 16 - 0
src/views/account/users/Users.vue

@@ -7,6 +7,7 @@ import { Edit, Delete,EditPen } from '@element-plus/icons-vue'
 import {User_List, User_Del, User_Leave, ColdVerify_User_List} from '@/api/user/index'
 import {User_List, User_Del, User_Leave, ColdVerify_User_List} from '@/api/user/index'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { ColumnProps } from '@/components/TableBase/interface/index'
 import { ColumnProps } from '@/components/TableBase/interface/index'
+import { PerformanceTarget_List } from '@/api/performance/index'
 
 
 const action = ref(true)
 const action = ref(true)
 const globalStore = GlobalStore()
 const globalStore = GlobalStore()
@@ -44,6 +45,7 @@ const columns: ColumnProps[] = [
   { prop: 'T_expire', label: '是否到期', name: 'T_expire', width: 100 },
   { prop: 'T_expire', label: '是否到期', name: 'T_expire', width: 100 },
   { prop: 'T_marry', label: '婚否', name: 'T_marry' },
   { prop: 'T_marry', label: '婚否', name: 'T_marry' },
   { prop: 'T_verify_cold_uuid', label: '冷链验证平台', name: 'T_verify_cold_uuid', width: 120 },
   { prop: 'T_verify_cold_uuid', label: '冷链验证平台', name: 'T_verify_cold_uuid', width: 120 },
+  { prop: 'T_verify_perf_target', label: '工资级别', name: 'T_verify_perf_target', width: 120 },
   { prop: 'operation', label: '操作', width: 200, fixed: 'right' }
   { prop: 'operation', label: '操作', width: 200, fixed: 'right' }
 ]
 ]
 const openDrawerFrom = () => drawerFromRef.value?.openDrawer()
 const openDrawerFrom = () => drawerFromRef.value?.openDrawer()
@@ -102,12 +104,23 @@ const coldVerifyUserList = ref<any[]>([
 		T_name: ''
 		T_name: ''
 	}
 	}
 ])
 ])
+const performanceTargetOptions = ref<any[]>([])
+const getPerformanceTargetOptions = async () => {
+  const res: any = await PerformanceTarget_List({
+    User_tokey: globalStore.GET_User_tokey,
+    page: 1,
+    page_z: 9999
+  })
+  const list = res.Data?.Data || res.Data || []
+  performanceTargetOptions.value = Array.isArray(list) ? list : []
+}
 const getColdVerifyUserList = async () => {
 const getColdVerifyUserList = async () => {
 	const res: any = await ColdVerify_User_List({})
 	const res: any = await ColdVerify_User_List({})
 	coldVerifyUserList.value = res.Data.List
 	coldVerifyUserList.value = res.Data.List
 }
 }
 onMounted(() => {
 onMounted(() => {
 	getColdVerifyUserList()
 	getColdVerifyUserList()
+  getPerformanceTargetOptions()
 })
 })
 
 
 // search
 // search
@@ -160,6 +173,9 @@ const SearchInfo = () => TableRef.value?.searchTable()
         <el-tag class="ml-2" v-if="row.T_marry === 0">未婚</el-tag>
         <el-tag class="ml-2" v-if="row.T_marry === 0">未婚</el-tag>
         <el-tag class="ml-2" type="danger" v-else>已婚</el-tag>
         <el-tag class="ml-2" type="danger" v-else>已婚</el-tag>
       </template>
       </template>
+      <template #T_verify_perf_target="{ row }">
+        <el-text type="info">{{ +row.T_verify_perf_target === 0 ? '-' : (performanceTargetOptions.find(opt => opt.Id === row.T_verify_perf_target)?.T_name || '-') }}</el-text>
+      </template>
       <template #T_entry_type="{ row }">
       <template #T_entry_type="{ row }">
         <el-tag class="ml-2" v-if="+row.T_entry_type === 1">全职</el-tag>
         <el-tag class="ml-2" v-if="+row.T_entry_type === 1">全职</el-tag>
         <el-tag class="ml-2" type="warning" v-else-if="+row.T_entry_type === 2">兼职</el-tag>
         <el-tag class="ml-2" type="warning" v-else-if="+row.T_entry_type === 2">兼职</el-tag>

+ 28 - 3
src/views/account/users/components/DrawerFrom.vue

@@ -5,6 +5,7 @@ import { reuls_validator } from './relus'
 import { GlobalStore } from '@/stores/index'
 import { GlobalStore } from '@/stores/index'
 import Drawer from '@/components/Drawer/index.vue'
 import Drawer from '@/components/Drawer/index.vue'
 import { User_Power_List } from '@/api/role/index'
 import { User_Power_List } from '@/api/role/index'
+import { PerformanceTarget_List } from '@/api/performance/index'
 import { ref, reactive, onMounted, nextTick } from 'vue'
 import { ref, reactive, onMounted, nextTick } from 'vue'
 import type { FormInstance, FormRules } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import {User_Post_List, User_Add, User_Edit, ColdVerify_User_List} from '@/api/user/index'
 import {User_Post_List, User_Add, User_Edit, ColdVerify_User_List} from '@/api/user/index'
@@ -25,6 +26,7 @@ const _PASS = '******'
 let userPowerList: any = []
 let userPowerList: any = []
 let coldVerifyUserList: any = []
 let coldVerifyUserList: any = []
 let userPostList = ref<any[]>([])
 let userPostList = ref<any[]>([])
+let performanceTargetOptions = ref<any[]>([])
 const globalStore = GlobalStore()
 const globalStore = GlobalStore()
 const formLabelWidth = ref('105px')
 const formLabelWidth = ref('105px')
 const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
 const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
@@ -58,7 +60,8 @@ let form = ref({
   T_spouse_phone: '',
   T_spouse_phone: '',
   T_expire: '',
   T_expire: '',
   T_dept_leader: '',
   T_dept_leader: '',
-  T_verify_cold_uuid: ''
+  T_verify_cold_uuid: '',
+  T_verify_perf_target:  '',
 })
 })
 
 
 const getUserPowerList = async () => {
 const getUserPowerList = async () => {
@@ -75,6 +78,17 @@ const getColdVerifyUserList = async () => {
   coldVerifyUserList = res.Data.List
   coldVerifyUserList = res.Data.List
 }
 }
 
 
+const getPerformanceTargetOptions = async () => {
+  const res: any = await PerformanceTarget_List({
+    User_tokey: globalStore.GET_User_tokey,
+    page: 1,
+    page_z: 9999
+  })
+  // 兼容返回结构 Data 或 Data.Data
+  const list = res.Data?.Data || res.Data || []
+  performanceTargetOptions.value = Array.isArray(list) ? list : []
+}
+
 const changeDept = async (val: number) => {
 const changeDept = async (val: number) => {
   const res: any = await User_Post_List({ T_dept: val })
   const res: any = await User_Post_List({ T_dept: val })
   userPostList.value = res.Data
   userPostList.value = res.Data
@@ -131,7 +145,9 @@ const DataEcho = async (row: any) => {
   const res: any = await User_Post_List({ T_dept: row.T_dept })
   const res: any = await User_Post_List({ T_dept: row.T_dept })
   userPostList.value = res.Data
   userPostList.value = res.Data
   nextTick(() => {
   nextTick(() => {
-    form.value = { ...row }
+    // 编辑时,如果工资级别为 0,则置为空以显示“请选择”
+    const verifyPerfTarget = +row.T_verify_perf_target === 0 ? '' : row.T_verify_perf_target
+    form.value = { ...row, T_verify_perf_target: verifyPerfTarget }
   })
   })
 }
 }
 
 
@@ -140,7 +156,10 @@ onMounted(() => {
     getUserPowerList()
     getUserPowerList()
   }
   }
   if (coldVerifyUserList.length <= 0) {
   if (coldVerifyUserList.length <= 0) {
-	getColdVerifyUserList()
+	  getColdVerifyUserList()
+  }
+  if (performanceTargetOptions.value.length <= 0) {
+    getPerformanceTargetOptions()
   }
   }
 })
 })
 
 
@@ -191,6 +210,7 @@ defineExpose({
           <el-option v-for="item in userPostList" :key="item.Id" :label="item.T_name" :value="item.Id" />
           <el-option v-for="item in userPostList" :key="item.Id" :label="item.T_name" :value="item.Id" />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
+
       <el-form-item label="性别:" :label-width="formLabelWidth" prop="T_sex">
       <el-form-item label="性别:" :label-width="formLabelWidth" prop="T_sex">
         <el-radio-group v-model="form.T_sex" class="ml-4">
         <el-radio-group v-model="form.T_sex" class="ml-4">
           <el-radio :label="1">男</el-radio>
           <el-radio :label="1">男</el-radio>
@@ -289,6 +309,11 @@ defineExpose({
 			<el-option v-for="item in coldVerifyUserList" :key="item.T_uuid" :label="item.T_name" :value="item.T_uuid" />
 			<el-option v-for="item in coldVerifyUserList" :key="item.T_uuid" :label="item.T_name" :value="item.T_uuid" />
 		</el-select>
 		</el-select>
       </el-form-item>
       </el-form-item>
+      <el-form-item label="工资级别:" :label-width="formLabelWidth">
+        <el-select v-model="form.T_verify_perf_target" clearable placeholder="请选择工资级别">
+          <el-option v-for="item in performanceTargetOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
+        </el-select>
+      </el-form-item>
       <el-form-item label="备注:" :label-width="formLabelWidth">
       <el-form-item label="备注:" :label-width="formLabelWidth">
         <el-input v-model="form.T_remark" type="textarea" autocomplete="off" placeholder="备注" />
         <el-input v-model="form.T_remark" type="textarea" autocomplete="off" placeholder="备注" />
       </el-form-item>
       </el-form-item>

+ 1088 - 0
src/views/salary/Performance/Performance.vue

@@ -0,0 +1,1088 @@
+<script setup lang="ts">
+import {onMounted, reactive, ref} from 'vue'
+import TableBase from '@/components/TableBase/index.vue'
+import Drawer from '@/components/Drawer/index.vue'
+import type {FormInstance} from 'element-plus'
+import {ElMessage, ElMessageBox} from 'element-plus'
+import {Plus} from '@element-plus/icons-vue'
+import {ColumnProps} from '@/components/TableBase/interface/index'
+import {useTablePublic} from '@/hooks/useTablePublic'
+import {Performance_Detail, Performance_List, Performance_Submit_User, Performance_Add, Performance_Edit, Performance_Del,
+	 PerformancePoints_List, Performance_Edit_Audit, Performance_Excel} from '@/api/performance/index'
+
+const {resetForm, globalStore, searchOnTableList, updateOnTableList} = useTablePublic()
+
+// 表格引用
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+
+// 初始化查询参数
+const initParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	T_date: '',
+	T_audit: '',
+	T_submit: ''
+})
+
+// 搜索表单数据
+const searchForm = ref({
+	T_date: '',
+	T_audit: '',
+	T_submit: ''
+})
+
+// 用户搜索相关
+const userList = ref<any[]>([])
+const userLoading = ref(false)
+const selectedUser = ref<any>(null)
+
+// 参考 VerifyPercentage.vue 的实现
+interface UserListItem {
+	value: string
+	label: string
+}
+
+const allUserList = ref<UserListItem[]>([])
+const filteredUserList = ref<UserListItem[]>([])
+
+// 审核状态选项
+const auditStatusOptions = [
+	{label: '全部', value: ''},
+	{label: '待提交', value: '1'},
+	{label: '已提交', value: '2'},
+	{label: '已打款', value: '3'}
+]
+
+// 当前查看的绩效详情
+const currentPerformance = ref<any>(null)
+
+// 添加/编辑绩效点表单数据(结构化录入)
+const pointRuleFormRef = ref<FormInstance>()
+const ruleForm = ref({
+  T_date: '',
+  remark: '',
+  topType: 'reporting' as 'reporting' | 'collection'
+})
+
+// 绩效点选项(名称、分值)
+const pointOptions = ref<any[]>([])
+
+// 行明细
+type PointRow = {
+  T_performance_points_id: number | ''
+  T_name: string
+  T_points_numerator: number
+  T_points_denominator: number
+  T_quantity: number
+  T_type: 'reporting' | 'collection' | ''
+  T_remark: string
+}
+const pointRows = ref<PointRow[]>([])
+
+// 计算总工作量:sum(分子/分母*数量)
+const totalWorkload = () => {
+  return pointRows.value.reduce((sum, r) => {
+    const numerator = Number(r.T_points_numerator || 0)
+    const denominator = Number(r.T_points_denominator || 1)
+    const qty = Number(r.T_quantity || 0)
+    if (!denominator) return sum
+    return sum + (numerator / denominator) * qty
+  }, 0)
+}
+
+// 选择绩效点弹窗(表格勾选)
+const selectDialogVisible = ref(false)
+const pointLoading = ref(false)
+const pointQuery = reactive({ T_name: '' })
+const pointPage = reactive({ page: 1, page_z: 20, total: 0 })
+const pointTable = ref<any[]>([])
+const selectedPointRows = ref<any[]>([])
+const pointListWrap = ref<HTMLElement | null>(null)
+
+const alreadySelectedIds = () => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+
+const fetchPointTable = async (append = false) => {
+  try {
+    pointLoading.value = true
+    const res: any = await PerformancePoints_List({
+      page: pointPage.page,
+      page_z: pointPage.page_z,
+      T_name: pointQuery.T_name || ''
+    })
+    if (res && res.Code === 200) {
+      const list = res.Data?.Data || []
+      if (append) {
+        pointTable.value = pointTable.value.concat(list)
+      } else {
+        pointTable.value = list
+      }
+      pointPage.total = res.Data?.Num || 0
+    }
+  } finally {
+    pointLoading.value = false
+  }
+}
+
+const openPointSelector = async () => {
+  selectDialogVisible.value = true
+  pointPage.page = 1
+  await fetchPointTable(false)
+}
+
+const handlePointSelectionChange = (rows: any[]) => {
+  selectedPointRows.value = rows
+}
+
+// 滚动加载更多
+const onPointScroll = (e: Event) => {
+  const el = e.target as HTMLElement
+  if (!el) return
+  const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10
+  const hasMore = pointTable.value.length < pointPage.total
+  if (nearBottom && hasMore && !pointLoading.value) {
+    pointPage.page += 1
+    fetchPointTable(true)
+  }
+}
+
+// 获取绩效点下拉
+const loadPointOptions = async () => {
+  try {
+    const res: any = await PerformancePoints_List({ page: 1, page_z: 9999 })
+    if (res && res.Code === 200) {
+      pointOptions.value = (res.Data?.Data || []).map((x: any) => ({
+        Id: x.Id,
+        T_name: x.T_name,
+        T_points_numerator: x.T_points_numerator,
+        T_points_denominator: x.T_points_denominator
+      }))
+    }
+  } catch (e) {}
+}
+
+// 行变更:选择名称时回填分子分母
+const onSelectPoint = (row: PointRow) => {
+  const found = pointOptions.value.find((o: any) => o.Id === row.T_performance_points_id)
+  if (found) {
+    row.T_points_numerator = Number(found.T_points_numerator || 0)
+    row.T_points_denominator = Number(found.T_points_denominator || 1)
+  } else {
+    row.T_points_numerator = 0
+    row.T_points_denominator = 1
+  }
+}
+
+// 顶部类型变化时同步(与选择一致:报告/实施)
+const onTopTypeChange = () => {
+  const newType = ruleForm.value.topType
+  pointRows.value = pointRows.value.map(r => ({ ...r, T_type: newType }))
+}
+
+const addRow = () => {
+  pointRows.value.push({
+    T_performance_points_id: '',
+    T_name: '',
+    T_points_numerator: 0,
+    T_points_denominator: 1,
+    T_quantity: 1,
+    T_type: 'reporting',
+    T_remark: ''
+  })
+}
+
+const removeRow = (index: number) => {
+  pointRows.value.splice(index, 1)
+}
+
+// 添加/编辑绩效点的弹窗状态
+const isEditDialogVisible = ref(false)
+const isNew = ref(true)
+const currentId = ref<number | null>(null)
+
+// 表格列配置
+const columns: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'T_submit_name', label: '姓名', align: 'center'},
+	{
+		prop: 'T_target_name',
+		label: '工资级别',
+		align: 'center',
+		name: 'T_target_name'
+	},
+	{prop: 'T_perf',label: '绩效工资(元)',align: 'center'},
+	{prop: 'T_date', label: '所属月份', align: 'center'},
+	{prop: 'T_workload', label: '工作量', align: 'center'},
+	{
+		prop: 'T_assess_points',
+		label: '考核工作量',
+		align: 'center',
+		name: 'T_assess_points'
+	},
+	{
+		prop: 'points_total',
+		label: '得分',
+		align: 'center',
+		name: 'points_total'
+	},
+	{
+		prop: 'perf_total',
+		label: '应发绩效',
+		align: 'center',
+		name: 'perf_total'
+	},
+	{
+		prop: 'T_audit',
+		label: '状态',
+		align: 'center',
+		name: 'T_audit'
+	},
+	{prop: 'operation', label: '操作', width: 160, fixed: 'right'}
+]
+
+// 搜索功能
+const searchInfo = () => {
+	initParam.T_date = searchForm.value.T_date
+	initParam.T_audit = searchForm.value.T_audit
+	initParam.T_submit = searchForm.value.T_submit || ''
+	TableRef.value?.searchTable()
+}
+
+// 远程搜索方法 
+const remoteMethod = async (query: string) => {
+	if (query) {
+		userLoading.value = true
+		setTimeout(() => {
+			userLoading.value = false
+			filteredUserList.value = allUserList.value.filter((item: UserListItem) => {
+				return item.label.toLowerCase().includes(query.toLowerCase())
+			})
+		}, 200)
+	} else {
+		filteredUserList.value = allUserList.value
+	}
+}
+
+// 点击显示所有用户
+const showAllUsers = async () => {
+	filteredUserList.value = allUserList.value
+}
+
+// 选择用户
+const selectUser = (user: any) => {
+	selectedUser.value = user
+	searchForm.value.T_submit = user.T_uuid
+}
+
+// 处理选择框变化
+const handleUserChange = (value: string) => {
+	if (!value) {
+		selectedUser.value = null
+		searchForm.value.T_submit = ''
+		return
+	}
+
+	const user = allUserList.value.find(u => u.value === value)
+	if (user) {
+		selectedUser.value = user
+		searchForm.value.T_submit = user.value
+	}
+}
+
+
+// 查看详情
+const viewDetail = async (row: any) => {
+	try {
+		// 调用详情接口获取完整数据
+		const response = await Performance_Detail({
+			User_tokey: globalStore.GET_User_tokey,
+			T_id: row.Id
+		})
+
+		if ((response as any).Code === 200 && response.Data) {
+			// 合并列表数据和详情数据
+			const data = response.Data as any
+			currentPerformance.value = {
+				...row,
+				...data.perf,
+				PointList: data.pointList || []
+			}
+			currentPerformance.value.T_submit_name = row.T_submit_name
+			drawerRef.value?.openDrawer()
+		} else {
+			ElMessage.error((response as any).msg || '获取详情失败')
+		}
+	} catch (error) {
+		console.error('获取绩效详情失败:', error)
+		ElMessage.error('获取详情失败,请稍后重试')
+	}
+}
+
+// 计算总得分 - 公式:工作量/考核工作量*100%
+const calculateTotalScore = (row: any) => {
+	if (!row) return 0
+	const workload = parseFloat(row.T_workload) || 0
+	const assessWorkload = parseFloat(row.T_assess_points) || 0
+	if (assessWorkload === 0) return 0
+	return (workload / assessWorkload) * 100
+}
+
+// 根据得分获取颜色
+const getScoreColor = (score: number) => {
+	return score >= 100 ? '#67C23A' : '#F56C6C' // 绿色 : 红色
+}
+
+// 计算应发绩效:当得分>=100%时,取绩效工资;否则按比例(得分/100)计算,保留两位小数
+const calculatePerfTotal = (row: any) => {
+	if (!row) return '0.00'
+	const baseAmount = parseFloat(row.T_perf) || 0
+	const scorePercent = calculateTotalScore(row)
+	if (scorePercent >= 100) return baseAmount.toFixed(2)
+	const payable = baseAmount * (scorePercent / 100)
+	return payable.toFixed(2)
+}
+
+// 抽屉关闭回调
+const callbackDrawer = (done: () => void) => {
+	done()
+	currentPerformance.value = null
+}
+
+// 打开添加/编辑绩效点弹窗
+const openDialog = (type: 'add' | 'edit', row?: any) => {
+  loadPointOptions()
+  if (type === 'edit' && row) {
+    isNew.value = false
+    currentId.value = row.Id
+    ruleForm.value = {
+      T_date: row.T_date,
+      remark: '',
+      topType: 'reporting'
+    }
+    // 将后端点位映射为行
+    const list = Array.isArray(row.PointList) ? row.PointList : []
+    pointRows.value = list.map((x: any) => ({
+      T_performance_points_id: x.T_performance_points_id || '',
+      T_name: x.PerformancePoints?.T_name || x.T_name || '',
+      T_points_numerator: Number(x.T_points_numerator || 0),
+      T_points_denominator: Number(x.T_points_denominator || 1),
+      T_quantity: Number(x.T_quantity || 1),
+      T_type: (x.T_type as any) || 'reporting',
+      T_remark: x.T_remark || ''
+    }))
+    // 编辑时不自动新增空行
+  } else {
+    isNew.value = true
+    currentId.value = null
+    ruleForm.value = {
+      T_date: '',
+      remark: '',
+      topType: 'reporting'
+    }
+    pointRows.value = []
+    if (pointRuleFormRef.value) {
+      pointRuleFormRef.value.resetFields()
+    }
+  }
+  isEditDialogVisible.value = true
+}
+
+// 导出功能
+const exportData = async () => {
+	try {
+		ElMessage.info('正在导出,请稍候...')
+		
+		const params = {
+			User_tokey: globalStore.GET_User_tokey,
+			T_date: searchForm.value.T_date || '',
+			T_submit: searchForm.value.T_submit || '',
+			T_audit: searchForm.value.T_audit || ''
+		}
+		
+		const response: any = await Performance_Excel(params)
+		
+		// 从响应头获取文件名
+		const contentDisposition = response.headers?.['content-disposition'] || response.headers?.['Content-Disposition']
+		
+		// 生成默认文件名,使用后端相同的命名方式:绩效管理_YYYYMMDD_HHMMSS.xlsx
+		const now = new Date()
+		const dateStr = now.getFullYear().toString() + 
+			(now.getMonth() + 1).toString().padStart(2, '0') + 
+			now.getDate().toString().padStart(2, '0')
+		const timeStr = now.getHours().toString().padStart(2, '0') + 
+			now.getMinutes().toString().padStart(2, '0') + 
+			now.getSeconds().toString().padStart(2, '0')
+		let filename = `绩效管理_${dateStr}_${timeStr}.xlsx`
+		
+		
+		// 创建下载链接
+		const blob = new Blob([response.data || response], { 
+			type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
+		})
+		const url = window.URL.createObjectURL(blob)
+		const link = document.createElement('a')
+		link.href = url
+		link.download = filename
+		document.body.appendChild(link)
+		link.click()
+		document.body.removeChild(link)
+		window.URL.revokeObjectURL(url)
+		
+		ElMessage.success('导出成功')
+	} catch (error) {
+		console.error('导出失败:', error)
+		ElMessage.error('导出失败,请稍后重试')
+	}
+}
+
+
+// 提交绩效点表单
+const submitForm = async () => {
+  if (!pointRuleFormRef.value) return
+  try {
+    await pointRuleFormRef.value.validate()
+    // 组装 Points 数组
+    const points = pointRows.value
+      .filter(r => r.T_performance_points_id)
+      // 去重校验(同月同记录不允许重复)
+      .filter((r, idx, arr) => arr.findIndex(x => x.T_performance_points_id === r.T_performance_points_id) === idx)
+      .map(r => ({
+        T_performance_points_id: r.T_performance_points_id,
+        T_quantity: r.T_quantity,
+        T_points_numerator: r.T_points_numerator,
+        T_points_denominator: r.T_points_denominator,
+        T_type: r.T_type || 'reporting',
+        T_remark: r.T_remark || ''
+      }))
+
+    if (points.length === 0) {
+      ElMessage.error('请至少添加一个绩效点')
+      return
+    }
+
+    // 额外校验表单内是否存在重复点位
+    const ids = points.map(p => p.T_performance_points_id)
+    const uniq = new Set(ids)
+    if (uniq.size !== ids.length) {
+      ElMessage.error('每个月的绩效点不能重复,请检查选择')
+      return
+    }
+
+    const params: any = {
+      T_date: ruleForm.value.T_date,
+      // 若不传 T_workload,后端会自动计算;这里一并传递更直观
+      T_workload: Number(totalWorkload().toFixed(2)),
+      Points: JSON.stringify(points)
+    }
+    if (!isNew.value && currentId.value) {
+      params.T_id = currentId.value
+    }
+
+    const response: any = isNew.value ? await Performance_Add(params) : await Performance_Edit(params)
+    if (response && Number(response.Code) === 200) {
+      ElMessage.success(isNew.value ? '添加成功' : '编辑成功')
+      isEditDialogVisible.value = false
+      updateTableList()
+    } else {
+      ElMessage.error(response?.Msg || (isNew.value ? '添加失败' : '编辑失败'))
+    }
+  } catch (error) {
+    // 验证未通过或异常
+  }
+}
+
+// 删除绩效点
+const deleteItem = async (row: any) => {
+  try {
+    const confirmResult = await ElMessageBox.confirm(
+      `确定要删除${row.T_date}的绩效记录吗?`,
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+    
+    if (confirmResult === 'confirm') {
+      const response = await Performance_Del({ T_id: row.Id })
+      
+      if (response && Number(response.Code) === 200) {
+        ElMessage.success('删除成功')
+        updateTableList() // 更新表格数据
+      } else {
+        ElMessage.error(response?.msg || '删除失败')
+      }
+    }
+  } catch (error) {
+    // 用户取消删除
+  }
+}
+
+// 更新表格数据
+const updateTableList = () => {
+	TableRef.value?.getTableList()
+}
+
+// 提交审核(打款)
+const submitAudit = async (row: any) => {
+	try {
+		const confirmResult = await ElMessageBox.confirm(
+			`确定要将${row.T_date}的绩效记录标记为已打款吗?`,
+			'打款确认',
+			{
+				confirmButtonText: '确定',
+				cancelButtonText: '取消',
+				type: 'warning'
+			}
+		)
+		
+		if (confirmResult === 'confirm') {
+			const response = await Performance_Edit_Audit({
+				User_tokey: globalStore.GET_User_tokey,
+				T_id: row.Id,
+				T_audit: 3
+			})
+			
+			if (response && Number(response.Code) === 200) {
+				ElMessage.success('提交成功')
+				updateTableList() // 更新表格数据
+			} else {
+				ElMessage.error(response?.msg || '提交失败')
+			}
+		}
+	} catch (error) {
+		// 用户取消打款
+	}
+}
+
+onMounted(async () => {
+	// 加载用户数据
+	const result: any = await Performance_Submit_User({})
+	let arr = []
+	console.log(result)
+	if (result?.Data !== null) {
+		arr = result.Data.Data
+	}
+
+	allUserList.value = arr.map((item: any) => {
+		return {value: item.T_uuid, label: item.T_name}
+	})
+
+	updateTableList()
+})
+</script>
+
+<template>
+	<div>
+	<div class="performance-page">
+		<TableBase
+			ref="TableRef"
+			:columns="columns"
+			:requestApi="Performance_List"
+			:initParam="initParam"
+		>
+			<template #table-header>
+				<div class="search-section">
+					<el-row :gutter="20" style="margin-bottom: 0" align="middle">
+						<el-col :span="4">
+							<div style="display: flex; align-items: center;">
+								<span style="white-space: nowrap; margin-right: 8px;">所属月份:</span>
+								<el-date-picker
+									v-model="searchForm.T_date"
+									type="month"
+									placeholder="选择月份"
+									format="YYYY-MM"
+									value-format="YYYY-MM"
+									clearable
+									@change="searchInfo"
+									style="width: 100%;"
+								/>
+							</div>
+						</el-col>
+						<el-col :span="4">
+							<div style="display: flex; align-items: center;">
+								<span style="white-space: nowrap; margin-right: 8px;">姓名:</span>
+								<el-select
+									v-model="searchForm.T_submit"
+									placeholder="搜索姓名"
+									filterable
+									remote
+									reserve-keyword
+									clearable
+									:remote-method="remoteMethod"
+									:loading="userLoading"
+									@change="handleUserChange"
+									@click="showAllUsers"
+									style="width: 100%;"
+								>
+									<el-option
+										v-for="user in filteredUserList"
+										:key="user.value"
+										:label="user.label"
+										:value="user.value"
+									/>
+								</el-select>
+							</div>
+						</el-col>
+
+						<el-col :span="4">
+							<div style="display: flex; align-items: center;">
+								<span style="white-space: nowrap; margin-right: 8px;">状态:</span>
+								<el-select
+									v-model="searchForm.T_audit"
+									placeholder="选择状态"
+									clearable
+									@change="searchInfo"
+									style="width: 100%;"
+								>
+									<el-option
+										v-for="option in auditStatusOptions"
+										:key="option.value"
+										:label="option.label"
+										:value="option.value"
+									/>
+								</el-select>
+							</div>
+						</el-col>
+						<el-col :span="3">
+							<el-button type="primary" @click="searchInfo">查询</el-button>
+						</el-col>
+						<el-col :span="4">
+							<el-button type="success" @click="exportData">导出</el-button>
+						</el-col>
+					</el-row>
+				</div>
+			</template>
+
+			<!-- 工资级别显示 -->
+			<template #T_target_name="{ row }">
+				<span>{{ row.Target?.T_name }}</span>
+			</template>
+			<!-- 考核工作量显示 -->
+			<template #T_assess_points="{ row }">
+				<span>{{ row.T_assess_points || 0 }}</span>
+			</template>
+
+			<!-- 总得分计算显示 -->
+			<template #points_total="{ row }">
+        <span :style="{ color: getScoreColor(calculateTotalScore(row)), fontWeight: 'bold' }">
+          {{ calculateTotalScore(row).toFixed(1) }}%
+        </span>
+			</template>
+
+			<!-- 应发绩效显示 -->
+			<template #perf_total="{ row }">
+				<span>{{ calculatePerfTotal(row) }}</span>
+			</template>
+
+			<!-- 状态显示 -->
+			<template #T_audit="{ row }">
+				<el-tag
+					:type="Number(row.T_audit) === 1 ? 'info' : Number(row.T_audit) === 2 ? 'warning' : 'success'"
+					size="small"
+				>
+					{{
+						Number(row.T_audit) === 1 ? '待提交' : Number(row.T_audit) === 2 ? '已提交' : Number(row.T_audit) === 3 ? '已打款' : '未知'
+					}}
+				</el-tag>
+			</template>
+
+			<!-- 操作列 -->
+
+			<template #right="{ row }">
+				<el-button
+					link
+					type="primary"
+					size="small"
+					@click="viewDetail(row)"
+				>
+					详情
+				</el-button>
+				<el-button
+					:disabled="Number(row.T_audit) !== 2"
+					link
+					type="success"
+					size="small"
+					@click="submitAudit(row)"
+				>
+					打款
+				</el-button>
+				<el-button
+					link
+					type="danger"
+					size="small"
+					@click="deleteItem(row)"
+				>
+					删除
+				</el-button>
+			</template>
+		</TableBase>
+
+		<!-- 绩效详情抽屉 -->
+		<Drawer ref="drawerRef" :handleClose="callbackDrawer" size="60%">
+			<template #header="{ params }">
+				<h4 :id="params.titleId" :class="params.titleClass">
+					绩效详情 - {{ currentPerformance?.T_date }}
+				</h4>
+			</template>
+
+			<div v-if="currentPerformance" class="performance-detail">
+				<!-- 基本信息 -->
+				<div class="detail-section">
+					<h5>基本信息</h5>
+					<el-row :gutter="20">
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">所属月份:</span>
+								<span class="value">{{ currentPerformance.T_date }}</span>
+							</div>
+						</el-col>
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">提交人:</span>
+								<span class="value">{{ currentPerformance.T_submit_name || '未知' }}</span>
+							</div>
+						</el-col>
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">状态:</span>
+								<el-tag
+									:type="currentPerformance.T_audit === 1 ? 'info' : currentPerformance.T_audit === 2 ? 'warning' : 'success'"
+									size="small"
+								>
+									{{
+										currentPerformance.T_audit === 1 ? '待提交' : currentPerformance.T_audit === 2 ? '已提交' : '已打款'
+									}}
+								</el-tag>
+							</div>
+						</el-col>
+					</el-row>
+					<el-row :gutter="20" style="margin-top: 12px;">
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">类型:</span>
+								<span class="value">{{ currentPerformance.T_type === 'reporting' ? '报告' : '实施' }}</span>
+							</div>
+						</el-col>
+	
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">统计时间:</span>
+								<span class="value">{{
+										currentPerformance.CreateTime ? new Date(currentPerformance.CreateTime).toLocaleString() : '未知'
+									}}</span>
+							</div>
+						</el-col>
+					</el-row>
+				</div>
+
+				<!-- 绩效点明细 -->
+				<div class="detail-section" marigin-left="16px">
+					<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
+						<h5>明细</h5>
+					</div>
+					<el-table
+						:data="currentPerformance.PointList || []"
+						border
+						size="small"
+						style="width: 100%"
+						empty-text="暂无绩效详情数据"
+					>
+						<el-table-column type="index" label="序号" width="60" align="center"/>
+						<el-table-column prop="PerformancePoints.T_name" align="center" label="名称" min-width="120"/>
+						<el-table-column label="绩效点分值" align="center" width="120">
+							<template #default="{ row }">
+                <span v-if="row.T_points_denominator && row.T_points_denominator !== 1">
+                  {{ row.T_points_numerator }}/{{ row.T_points_denominator }}
+                </span>
+								<span v-else>
+                  {{ row.T_points_numerator || 0 }}
+                </span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="T_quantity" label="数量" align="center" width="80"/>
+						<el-table-column label="工作量" align="center" width="100">
+							<template #default="{ row }">
+                <span style="color: #E6A23C; font-weight: bold;">
+                  {{
+						((row.T_points_numerator || 0) / (row.T_points_denominator || 1) * (row.T_quantity || 1)).toFixed(1)
+					}}
+                </span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="T_remark" label="备注" min-width="150"/>
+			
+					</el-table>
+
+					<!-- 总计 -->
+					<div class="total-score">
+						<div class="score-summary">
+							<div class="score-item">
+								<span class="label">总工作量:</span>
+								<span class="value">{{ currentPerformance.T_workload || 0 }}</span>
+							</div>
+														<div class="score-item">
+								<span class="label">考核工作量:</span>
+								<span class="value">{{ currentPerformance.T_assess_points || 0 }}</span>
+							</div>
+							<div class="score-item">
+								<span class="label">计算总得分:</span>
+								<span class="value"
+									  :style="{ color: getScoreColor(calculateTotalScore(currentPerformance)), fontWeight: 'bold' }">
+                  {{ calculateTotalScore(currentPerformance).toFixed(1) }}%
+                </span>
+							</div>
+							<div class="score-item">
+								<span class="label">绩效工资:</span>
+								<span class="value">{{ currentPerformance.T_perf || 0 }}元</span>
+							</div>
+							<div class="score-item">
+								<span class="label">应发绩效:</span>
+								<span class="value">{{ calculatePerfTotal(currentPerformance) }}元</span>
+							</div>
+
+						</div>
+					</div>
+				</div>
+			</div>
+		</Drawer>
+	</div>
+
+	<!-- 添加/编辑绩效弹窗 -->
+	<el-dialog
+		v-model="isEditDialogVisible"
+		:title="isNew ? '添加绩效' : '编辑绩效'"
+		width="50%"
+	>
+		<el-form
+			ref="pointRuleFormRef"
+			:model="ruleForm"
+			label-width="100px"
+		>
+			<el-form-item label="所属月份" prop="T_date" :rules="[{ required: true, message: '请选择所属月份', trigger: 'blur' }]">
+				<el-date-picker
+					v-model="ruleForm.T_date"
+					type="month"
+					placeholder="选择月份"
+					format="YYYY-MM"
+					value-format="YYYY-MM"
+					style="width: 100%;"
+				/>
+			</el-form-item>
+			<el-form-item label="类型">
+				<el-select v-model="ruleForm.topType" style="width: 100%;" @change="onTopTypeChange">
+					<el-option label="报告" value="reporting" />
+					<el-option label="实施" value="collection" />
+				</el-select>
+			</el-form-item>
+
+			<div style="margin: 12px 20px; color: #606266;">明细</div>
+			<el-table :data="pointRows" border size="small" style="width: 98%; margin-left: 20px;">
+				<el-table-column type="index" label="序号" width="60" align="center" />
+				<el-table-column label="名称" min-width="160" align="center">
+					<template #default="{ row }">
+						<span>{{ row.T_name }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="绩效点分值" width="140" align="center">
+					<template #default="{ row }">
+						<span v-if="row.T_points_denominator && row.T_points_denominator !== 1">{{ row.T_points_numerator }}/{{ row.T_points_denominator }}</span>
+						<span v-else>{{ row.T_points_numerator }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="类型" width="120" align="center">
+					<template #default="{ row }">
+						<el-select v-model="row.T_type" style="width: 100%">
+							<el-option label="报告" value="reporting" />
+							<el-option label="实施" value="collection" />
+						</el-select>
+					</template>
+				</el-table-column>
+				<el-table-column label="数量" width="120" align="center">
+					<template #default="{ row }">
+						<el-input 
+							v-model="row.T_quantity" 
+							type="number" 
+							style="width: 100%" 
+							@input="row.T_quantity = Number(row.T_quantity) || 0"
+						/>
+					</template>
+				</el-table-column>
+				<el-table-column label="备注" min-width="160">
+					<template #default="{ row }">
+						<el-input v-model="row.T_remark" placeholder="备注" />
+					</template>
+				</el-table-column>
+				<el-table-column label="操作" width="100" align="center">
+					<template #default="{ $index }">
+						<el-button link type="danger" @click="removeRow($index)">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+
+			<div style="display:flex; justify-content: space-between; align-items:center; margin-top: 12px;">
+				<div style="margin-left: 20px;">
+					<el-button type="primary" plain @click="openPointSelector">添加</el-button>
+				</div>
+				<div style="color:#606266;">总工作量:<b style="color:#E6A23C;">{{ totalWorkload().toFixed(2) }}</b></div>
+			</div>
+		</el-form>
+
+		<!-- 绩效点选择弹窗(表格勾选,滚动分页) -->
+		<el-dialog v-model="selectDialogVisible" title="服务内容" width="40%">
+			<el-row :gutter="12" style="margin-bottom: 10px;">
+				<el-col :span="6">
+					<el-input v-model="pointQuery.T_name" placeholder="按名称搜索" clearable />
+				</el-col>
+				<el-col :span="4">
+					<el-button type="primary" @click="() => { pointPage.page = 1; fetchPointTable(false) }">查询</el-button>
+				</el-col>
+			</el-row>
+
+			<div ref="pointListWrap" style="max-height: 400px; overflow: auto;" @scroll="onPointScroll">
+			<el-table
+				:data="pointTable"
+				border
+				:loading="pointLoading"
+				row-key="Id"
+				reserve-selection
+				@selection-change="(rows:any[])=>handlePointSelectionChange(rows)"
+				style="width: 100%"
+			>
+				<el-table-column type="selection" width="50" />
+				<el-table-column prop="T_name" label="名称" min-width="160" />
+				<el-table-column label="数量" width="120" align="center">
+					<template #default="{ row }">
+						<span v-if="row.T_points_denominator && row.T_points_denominator !== 1">{{ row.T_points_numerator }}/{{ row.T_points_denominator }}</span>
+						<span v-else>{{ row.T_points_numerator }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column prop="T_remark" label="备注" min-width="200" show-overflow-tooltip />
+			</el-table>
+			</div>
+
+			<!-- 分页条移除,改为滚动加载 -->
+
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="selectDialogVisible = false">取消</el-button>
+					<el-button type="primary" @click="() => {
+						const exists = alreadySelectedIds()
+                        selectedPointRows.forEach((opt: any) => {
+							if (!exists.has(opt.Id)) {
+								pointRows.push({
+									T_performance_points_id: opt.Id,
+                                    T_name: opt.T_name,
+									T_points_numerator: Number(opt.T_points_numerator || 0),
+									T_points_denominator: Number(opt.T_points_denominator || 1),
+									T_quantity: 1,
+									T_type: ruleForm.topType,
+									T_remark: ''
+								})
+							}
+						})
+						selectDialogVisible = false
+					}">确定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+		<template #footer>
+			<span class="dialog-footer">
+				<el-button @click="isEditDialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submitForm">确定</el-button>
+			</span>
+		</template>
+	</el-dialog>
+</div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.performance-page {
+	@include f-direction;
+
+	.search-section {
+		width: 100%;
+
+		.inline-flex {
+			white-space: nowrap;
+		}
+	}
+}
+
+.performance-detail {
+	.detail-section {
+		margin-bottom: 24px;
+
+		h5 {
+			margin-bottom: 16px;
+			color: #303133;
+			font-size: 16px;
+			font-weight: 600;
+			border-bottom: 1px solid #e4e7ed;
+			padding-bottom: 8px;
+		}
+
+		.info-item {
+			margin-bottom: 12px;
+			display: flex;
+			align-items: center;
+
+			.label {
+				color: #606266;
+				margin-right: 8px;
+				min-width: 80px;
+			}
+
+			.value {
+				color: #303133;
+				font-weight: 500;
+			}
+		}
+	}
+
+	.total-score {
+		margin-top: 16px;
+		padding: 16px;
+		background-color: #f5f7fa;
+		border-radius: 4px;
+
+		.score-summary {
+			display: flex;
+			justify-content: space-around;
+			align-items: center;
+
+			.score-item {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+
+				.label {
+					color: #606266;
+					font-size: 12px;
+					margin-bottom: 4px;
+				}
+
+				.value {
+					color: #303133;
+					font-size: 16px;
+					font-weight: 600;
+				}
+			}
+		}
+	}
+}
+
+:deep(.el-drawer__body) {
+	padding: 20px;
+}
+
+:deep(.el-table) {
+	font-size: 13px;
+}
+
+:deep(.el-table th) {
+	background-color: #fafafa;
+}
+</style>

+ 1024 - 0
src/views/salary/Performance/PerformanceMy.vue

@@ -0,0 +1,1024 @@
+<script setup lang="ts">
+import {onMounted, reactive, ref} from 'vue'
+import TableBase from '@/components/TableBase/index.vue'
+import Drawer from '@/components/Drawer/index.vue'
+import type {FormInstance} from 'element-plus'
+import {ElMessage, ElMessageBox} from 'element-plus'
+import {Plus} from '@element-plus/icons-vue'
+import {ColumnProps} from '@/components/TableBase/interface/index'
+import {useTablePublic} from '@/hooks/useTablePublic'
+import {Performance_Detail, Performance_User_List, Performance_Submit_User, Performance_Add, Performance_Edit, Performance_Del, PerformancePoints_List, Performance_Edit_Audit, Performance_User_Excel} from '@/api/performance/index'
+
+const {resetForm, globalStore, searchOnTableList, updateOnTableList} = useTablePublic()
+
+// 表格引用
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+
+// 初始化查询参数
+const initParam = reactive({
+	User_tokey: globalStore.GET_User_tokey,
+	T_date: '',
+	T_audit: '',
+	T_submit: ''
+})
+
+// 搜索表单数据
+const searchForm = ref({
+	T_date: '',
+	T_audit: '',
+	T_submit: ''
+})
+
+// 用户搜索相关
+const userList = ref<any[]>([])
+const userLoading = ref(false)
+const selectedUser = ref<any>(null)
+
+// 参考 VerifyPercentage.vue 的实现
+interface UserListItem {
+	value: string
+	label: string
+}
+
+const allUserList = ref<UserListItem[]>([])
+
+// 审核状态选项
+const auditStatusOptions = [
+	{label: '全部', value: ''},
+	{label: '待提交', value: '1'},
+	{label: '已提交', value: '2'},
+	{label: '已打款', value: '3'}
+]
+
+// 当前查看的绩效详情
+const currentPerformance = ref<any>(null)
+
+// 添加/编辑绩效点表单数据(结构化录入)
+const pointRuleFormRef = ref<FormInstance>()
+const ruleForm = ref({
+  T_date: '',
+  remark: '',
+  topType: 'reporting' as 'reporting' | 'collection'
+})
+
+// 绩效点选项(名称、分值)
+const pointOptions = ref<any[]>([])
+
+// 行明细
+type PointRow = {
+  T_performance_points_id: number | ''
+  T_name: string
+  T_points_numerator: number
+  T_points_denominator: number
+  T_quantity: number
+  T_type: 'reporting' | 'collection' | ''
+  T_remark: string
+}
+const pointRows = ref<PointRow[]>([])
+
+// 计算总工作量:sum(分子/分母*数量)
+const totalWorkload = () => {
+  return pointRows.value.reduce((sum, r) => {
+    const numerator = Number(r.T_points_numerator || 0)
+    const denominator = Number(r.T_points_denominator || 1)
+    const qty = Number(r.T_quantity || 0)
+    if (!denominator) return sum
+    return sum + (numerator / denominator) * qty
+  }, 0)
+}
+
+// 选择绩效点弹窗(表格勾选)
+const selectDialogVisible = ref(false)
+const pointLoading = ref(false)
+const pointQuery = reactive({ T_name: '' })
+const pointPage = reactive({ page: 1, page_z: 20, total: 0 })
+const pointTable = ref<any[]>([])
+const selectedPointRows = ref<any[]>([])
+const pointListWrap = ref<HTMLElement | null>(null)
+
+const alreadySelectedIds = () => new Set(pointRows.value.map(r => Number(r.T_performance_points_id || 0)))
+
+const fetchPointTable = async (append = false) => {
+  try {
+    pointLoading.value = true
+    const res: any = await PerformancePoints_List({
+      page: pointPage.page,
+      page_z: pointPage.page_z,
+      T_name: pointQuery.T_name || ''
+    })
+    if (res && res.Code === 200) {
+      const list = res.Data?.Data || []
+      if (append) {
+        pointTable.value = pointTable.value.concat(list)
+      } else {
+        pointTable.value = list
+      }
+      pointPage.total = res.Data?.Num || 0
+    }
+  } finally {
+    pointLoading.value = false
+  }
+}
+
+const openPointSelector = async () => {
+  selectDialogVisible.value = true
+  pointPage.page = 1
+  await fetchPointTable(false)
+}
+
+const handlePointSelectionChange = (rows: any[]) => {
+  selectedPointRows.value = rows
+}
+
+// 滚动加载更多
+const onPointScroll = (e: Event) => {
+  const el = e.target as HTMLElement
+  if (!el) return
+  const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 10
+  const hasMore = pointTable.value.length < pointPage.total
+  if (nearBottom && hasMore && !pointLoading.value) {
+    pointPage.page += 1
+    fetchPointTable(true)
+  }
+}
+
+// 获取绩效点下拉
+const loadPointOptions = async () => {
+  try {
+    const res: any = await PerformancePoints_List({ page: 1, page_z: 9999 })
+    if (res && res.Code === 200) {
+      pointOptions.value = (res.Data?.Data || []).map((x: any) => ({
+        Id: x.Id,
+        T_name: x.T_name,
+        T_points_numerator: x.T_points_numerator,
+        T_points_denominator: x.T_points_denominator
+      }))
+    }
+  } catch (e) {}
+}
+
+// 行变更:选择名称时回填分子分母
+const onSelectPoint = (row: PointRow) => {
+  const found = pointOptions.value.find((o: any) => o.Id === row.T_performance_points_id)
+  if (found) {
+    row.T_points_numerator = Number(found.T_points_numerator || 0)
+    row.T_points_denominator = Number(found.T_points_denominator || 1)
+  } else {
+    row.T_points_numerator = 0
+    row.T_points_denominator = 1
+  }
+}
+
+// 顶部类型变化时同步(与选择一致:报告/实施)
+const onTopTypeChange = () => {
+  const newType = ruleForm.value.topType
+  pointRows.value = pointRows.value.map(r => ({ ...r, T_type: newType }))
+}
+
+const addRow = () => {
+  pointRows.value.push({
+    T_performance_points_id: '',
+    T_name: '',
+    T_points_numerator: 0,
+    T_points_denominator: 1,
+    T_quantity: 1,
+    T_type: 'reporting',
+    T_remark: ''
+  })
+}
+
+const removeRow = (index: number) => {
+  pointRows.value.splice(index, 1)
+}
+
+// 添加/编辑绩效点的弹窗状态
+const isEditDialogVisible = ref(false)
+const isNew = ref(true)
+const currentId = ref<number | null>(null)
+
+const columns: ColumnProps[] = [
+	{type: 'index', label: '序号', width: 80},
+	{prop: 'T_submit_name', label: '姓名', align: 'center'},
+	{
+		prop: 'T_target_name',
+		label: '工资级别',
+		align: 'center',
+		name: 'T_target_name'
+	},
+	{prop: 'T_perf',label: '绩效工资(元)',align: 'center'},
+	{prop: 'T_date', label: '所属月份', align: 'center'},
+	{prop: 'T_workload', label: '工作量', align: 'center'},
+	{
+		prop: 'T_assess_points',
+		label: '考核工作量',
+		align: 'center',
+		name: 'T_assess_points'
+	},
+	{
+		prop: 'points_total',
+		label: '得分',
+		align: 'center',
+		name: 'points_total'
+	},
+	{
+		prop: 'perf_total',
+		label: '应发绩效',
+		align: 'center',
+		name: 'perf_total'
+	},
+	{
+		prop: 'T_audit',
+		label: '状态',
+		align: 'center',
+		name: 'T_audit'
+	},
+	{prop: 'operation', label: '操作', width: 190, fixed: 'right'}
+]
+
+// 搜索功能
+const searchInfo = () => {
+	initParam.T_date = searchForm.value.T_date
+	initParam.T_audit = searchForm.value.T_audit
+	initParam.T_submit = searchForm.value.T_submit || ''
+	TableRef.value?.searchTable()
+}
+
+
+// 选择用户
+const selectUser = (user: any) => {
+	selectedUser.value = user
+	searchForm.value.T_submit = user.T_uuid
+}
+// 导出功能
+const exportData = async () => {
+	try {
+		ElMessage.info('正在导出,请稍候...')
+		
+		const params = {
+			User_tokey: globalStore.GET_User_tokey,
+			T_date: searchForm.value.T_date || '',
+			T_audit: searchForm.value.T_audit || ''
+		}
+		
+		const response: any = await Performance_User_Excel(params)
+		
+		// 从响应头获取文件名
+		const contentDisposition = response.headers?.['content-disposition'] || response.headers?.['Content-Disposition']
+		
+		// 生成默认文件名,使用后端相同的命名方式:绩效管理_YYYYMMDD_HHMMSS.xlsx
+		const now = new Date()
+		const dateStr = now.getFullYear().toString() + 
+			(now.getMonth() + 1).toString().padStart(2, '0') + 
+			now.getDate().toString().padStart(2, '0')
+		const timeStr = now.getHours().toString().padStart(2, '0') + 
+			now.getMinutes().toString().padStart(2, '0') + 
+			now.getSeconds().toString().padStart(2, '0')
+		let filename = `绩效管理_${dateStr}_${timeStr}.xlsx`
+		
+		
+		// 创建下载链接
+		const blob = new Blob([response.data || response], { 
+			type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
+		})
+		const url = window.URL.createObjectURL(blob)
+		const link = document.createElement('a')
+		link.href = url
+		link.download = filename
+		document.body.appendChild(link)
+		link.click()
+		document.body.removeChild(link)
+		window.URL.revokeObjectURL(url)
+		
+		ElMessage.success('导出成功')
+	} catch (error) {
+		console.error('导出失败:', error)
+		ElMessage.error('导出失败,请稍后重试')
+	}
+}
+
+// 添加绩效
+const addPerformance = () => {
+	openDialog('add')
+}
+
+// 查看详情
+const viewDetail = async (row: any) => {
+	try {
+		// 调用详情接口获取完整数据
+		const response = await Performance_Detail({
+			User_tokey: globalStore.GET_User_tokey,
+			T_id: row.Id
+		})
+
+		if ((response as any).Code === 200 && response.Data) {
+			// 合并列表数据和详情数据
+			const data = response.Data as any
+			currentPerformance.value = {
+				...row,
+				...data.perf,
+				PointList: data.pointList || []
+			}
+			currentPerformance.value.T_submit_name = row.T_submit_name
+			drawerRef.value?.openDrawer()
+		} else {
+			ElMessage.error((response as any).msg || '获取详情失败')
+		}
+	} catch (error) {
+		console.error('获取绩效详情失败:', error)
+		ElMessage.error('获取详情失败,请稍后重试')
+	}
+}
+
+// 计算总得分 - 公式:工作量/考核工作量*100%
+const calculateTotalScore = (row: any) => {
+	if (!row) return 0
+	const workload = parseFloat(row.T_workload) || 0
+	const assessWorkload = parseFloat(row.T_assess_points) || 0
+	if (assessWorkload === 0) return 0
+	return (workload / assessWorkload) * 100
+}
+
+// 根据得分获取颜色
+const getScoreColor = (score: number) => {
+	return score >= 100 ? '#67C23A' : '#F56C6C' // 绿色 : 红色
+}
+
+// 抽屉关闭回调
+const callbackDrawer = (done: () => void) => {
+	done()
+	currentPerformance.value = null
+}
+
+// 打开添加/编辑绩效点弹窗
+const openDialog = (type: 'add' | 'edit', row?: any) => {
+  loadPointOptions()
+  if (type === 'edit' && row) {
+    isNew.value = false
+    currentId.value = row.Id
+    ruleForm.value = {
+      T_date: row.T_date,
+      remark: '',
+      topType: 'reporting'
+    }
+    // 将后端点位映射为行
+    const list = Array.isArray(row.PointList) ? row.PointList : []
+    pointRows.value = list.map((x: any) => ({
+      T_performance_points_id: x.T_performance_points_id || '',
+      T_name: x.PerformancePoints?.T_name || x.T_name || '',
+      T_points_numerator: Number(x.T_points_numerator || 0),
+      T_points_denominator: Number(x.T_points_denominator || 1),
+      T_quantity: Number(x.T_quantity || 1),
+      T_type: (x.T_type as any) || 'reporting',
+      T_remark: x.T_remark || ''
+    }))
+    // 编辑时不自动新增空行
+  } else {
+    isNew.value = true
+    currentId.value = null
+    ruleForm.value = {
+      T_date: '',
+      remark: '',
+      topType: 'reporting'
+    }
+    pointRows.value = []
+    if (pointRuleFormRef.value) {
+      pointRuleFormRef.value.resetFields()
+    }
+  }
+  isEditDialogVisible.value = true
+}
+
+// 计算应发绩效:当得分>=100%时,取绩效工资;否则按比例(得分/100)计算,保留两位小数
+const calculatePerfTotal = (row: any) => {
+	if (!row) return '0.00'
+	const baseAmount = parseFloat(row.T_perf) || 0
+	const scorePercent = calculateTotalScore(row)
+	if (scorePercent >= 100) return baseAmount.toFixed(2)
+	const payable = baseAmount * (scorePercent / 100)
+	return payable.toFixed(2)
+}
+
+
+// 提交绩效点表单
+const submitForm = async () => {
+  if (!pointRuleFormRef.value) return
+  try {
+    await pointRuleFormRef.value.validate()
+    // 组装 Points 数组
+    const points = pointRows.value
+      .filter(r => r.T_performance_points_id)
+      // 去重校验(同月同记录不允许重复)
+      .filter((r, idx, arr) => arr.findIndex(x => x.T_performance_points_id === r.T_performance_points_id) === idx)
+      .map(r => ({
+        T_performance_points_id: r.T_performance_points_id,
+        T_quantity: r.T_quantity,
+        T_points_numerator: r.T_points_numerator,
+        T_points_denominator: r.T_points_denominator,
+        T_type: r.T_type || 'reporting',
+        T_remark: r.T_remark || ''
+      }))
+
+    if (points.length === 0) {
+      ElMessage.error('请至少添加一个绩效点')
+      return
+    }
+
+    // 额外校验表单内是否存在重复点位
+    const ids = points.map(p => p.T_performance_points_id)
+    const uniq = new Set(ids)
+    if (uniq.size !== ids.length) {
+      ElMessage.error('每个月的绩效点不能重复,请检查选择')
+      return
+    }
+
+    const params: any = {
+      T_date: ruleForm.value.T_date,
+      // 若不传 T_workload,后端会自动计算;这里一并传递更直观
+      T_workload: Number(totalWorkload().toFixed(2)),
+      Points: JSON.stringify(points)
+    }
+    if (!isNew.value && currentId.value) {
+      params.T_id = currentId.value
+    }
+
+    const response: any = isNew.value ? await Performance_Add(params) : await Performance_Edit(params)
+    if (response && Number(response.Code) === 200) {
+      ElMessage.success(isNew.value ? '添加成功' : '编辑成功')
+      isEditDialogVisible.value = false
+      updateTableList()
+    } else {
+      ElMessage.error(response?.Msg || (isNew.value ? '添加失败' : '编辑失败'))
+    }
+  } catch (error) {
+    // 验证未通过或异常
+  }
+}
+
+// 删除绩效点
+const deleteItem = async (row: any) => {
+  try {
+    const confirmResult = await ElMessageBox.confirm(
+      `确定要删除${row.T_date}的绩效记录吗?`,
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+    
+    if (confirmResult === 'confirm') {
+      const response = await Performance_Del({ T_id: row.Id })
+      
+      if (response && Number(response.Code) === 200) {
+        ElMessage.success('删除成功')
+        updateTableList() // 更新表格数据
+      } else {
+        ElMessage.error(response?.msg || '删除失败')
+      }
+    }
+  } catch (error) {
+    // 用户取消删除
+  }
+}
+
+// 更新表格数据
+const updateTableList = () => {
+	TableRef.value?.getTableList()
+}
+
+// 提交审核
+const submitAudit = async (row: any) => {
+	try {
+		const confirmResult = await ElMessageBox.confirm(
+			`确定要提交${row.T_date}的绩效记录进行审核吗?`,
+			'提交确认',
+			{
+				confirmButtonText: '确定',
+				cancelButtonText: '取消',
+				type: 'warning'
+			}
+		)
+		
+		if (confirmResult === 'confirm') {
+			const response = await Performance_Edit_Audit({
+				User_tokey: globalStore.GET_User_tokey,
+				T_id: row.Id,
+				T_audit: 2
+			})
+			
+			if (response && Number(response.Code) === 200) {
+				ElMessage.success('提交成功')
+				updateTableList() // 更新表格数据
+			} else {
+				ElMessage.error(response?.msg || '提交失败')
+			}
+		}
+	} catch (error) {
+		// 用户取消提交
+	}
+}
+
+onMounted(async () => {
+	updateTableList()
+})
+</script>
+
+<template>
+	<div>
+	<div class="performance-page">
+		<TableBase
+			ref="TableRef"
+			:columns="columns"
+			:requestApi="Performance_User_List"
+			:initParam="initParam"
+		>
+			<template #table-header>
+				<div class="search-section">
+					<el-row :gutter="20" style="margin-bottom: 0" align="middle">
+						<el-col :span="4">
+							<div style="display: flex; align-items: center;">
+								<span style="white-space: nowrap; margin-right: 8px;">所属月份:</span>
+								<el-date-picker
+									v-model="searchForm.T_date"
+									type="month"
+									placeholder="选择月份"
+									format="YYYY-MM"
+									value-format="YYYY-MM"
+									clearable
+									@change="searchInfo"
+									style="width: 100%;"
+								/>
+							</div>
+						</el-col>
+						<el-col :span="4">
+							<div style="display: flex; align-items: center;">
+								<span style="white-space: nowrap; margin-right: 8px;">状态:</span>
+								<el-select
+									v-model="searchForm.T_audit"
+									placeholder="选择状态"
+									clearable
+									@change="searchInfo"
+									style="width: 100%;"
+								>
+									<el-option
+										v-for="option in auditStatusOptions"
+										:key="option.value"
+										:label="option.label"
+										:value="option.value"
+									/>
+								</el-select>
+							</div>
+						</el-col>
+						<el-col :span="3">
+							<el-button type="primary" @click="searchInfo">查询</el-button>
+						</el-col>
+						<el-col :span="4">
+							<el-button type="primary" @click="addPerformance">添加</el-button>
+							<el-button type="success" @click="exportData">导出</el-button>
+						</el-col>
+					</el-row>
+				</div>
+			</template>
+
+			<!-- 工资级别显示 -->
+			<template #T_target_name="{ row }">
+				<span>{{ row.Target?.T_name }}</span>
+			</template>
+			<!-- 考核工作量显示 -->
+			<template #T_assess_points="{ row }">
+				<span>{{ row.T_assess_points || 0 }}</span>
+			</template>
+
+			<!-- 总得分计算显示 -->
+			<template #points_total="{ row }">
+        <span :style="{ color: getScoreColor(calculateTotalScore(row)), fontWeight: 'bold' }">
+          {{ calculateTotalScore(row).toFixed(1) }}%
+        </span>
+			</template>
+
+			<!-- 应发绩效显示 -->
+			<template #perf_total="{ row }">
+				<span>{{ calculatePerfTotal(row) }}</span>
+			</template>
+
+			<!-- 状态显示 -->
+			<template #T_audit="{ row }">
+				<el-tag
+					:type="Number(row.T_audit) === 1 ? 'info' : Number(row.T_audit) === 2 ? 'warning' : 'success'"
+					size="small"
+				>
+					{{
+						Number(row.T_audit) === 1 ? '待提交' : Number(row.T_audit) === 2 ? '已提交' : Number(row.T_audit) === 3 ? '已打款' : '未知'
+					}}
+				</el-tag>
+			</template>
+
+			<!-- 操作列 -->
+			<template #right="{ row }">
+				<el-button
+					link
+					type="primary"
+					size="small"
+					@click="viewDetail(row)"
+				>
+					详情
+				</el-button>
+				<el-button
+					:disabled="Number(row.T_audit) !== 1"
+					link
+					type="success"
+					size="small"
+					@click="submitAudit(row)"
+				>
+					提交
+				</el-button>
+				<el-button
+				:disabled="Number(row.T_audit) !== 1 && Number(row.T_audit) !== 2"
+					link
+					type="primary"
+					size="small"
+					@click="openDialog('edit', row)"
+				>
+					编辑
+				</el-button>
+				<el-button
+				:disabled="Number(row.T_audit) !== 1 && Number(row.T_audit) !== 2"
+					link
+					type="danger"
+					size="small"
+					@click="deleteItem(row)"
+				>
+					删除
+				</el-button>
+			</template>
+		</TableBase>
+
+		<!-- 绩效详情抽屉 -->
+		<Drawer ref="drawerRef" :handleClose="callbackDrawer" size="60%">
+			<template #header="{ params }">
+				<h4 :id="params.titleId" :class="params.titleClass">
+					绩效详情 - {{ currentPerformance?.T_date }}
+				</h4>
+			</template>
+
+			<div v-if="currentPerformance" class="performance-detail">
+				<!-- 基本信息 -->
+				<div class="detail-section">
+					<h5>基本信息</h5>
+					<el-row :gutter="20">
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">所属月份:</span>
+								<span class="value">{{ currentPerformance.T_date }}</span>
+							</div>
+						</el-col>
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">提交人:</span>
+								<span class="value">{{ currentPerformance.T_submit_name || '未知' }}</span>
+							</div>
+						</el-col>
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">状态:</span>
+								<el-tag
+									:type="currentPerformance.T_audit === 1 ? 'info' : currentPerformance.T_audit === 2 ? 'warning' : 'success'"
+									size="small"
+								>
+									{{
+										currentPerformance.T_audit === 1 ? '待提交' : currentPerformance.T_audit === 2 ? '已提交' : '已打款'
+									}}
+								</el-tag>
+							</div>
+						</el-col>
+					</el-row>
+					<el-row :gutter="20" style="margin-top: 12px;">
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">类型:</span>
+								<span class="value">{{ currentPerformance.T_type === 'reporting' ? '报告' : '实施' }}</span>
+							</div>
+						</el-col>
+	
+						<el-col :span="6">
+							<div class="info-item">
+								<span class="label">统计时间:</span>
+								<span class="value">{{
+										currentPerformance.CreateTime ? new Date(currentPerformance.CreateTime).toLocaleString() : '未知'
+									}}</span>
+							</div>
+						</el-col>
+					</el-row>
+				</div>
+
+				<!-- 绩效点明细 -->
+				<div class="detail-section" marigin-left="16px">
+					<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
+						<h5>明细</h5>
+					</div>
+					<el-table
+						:data="currentPerformance.PointList || []"
+						border
+						size="small"
+						style="width: 100%"
+						empty-text="暂无绩效详情数据"
+					>
+						<el-table-column type="index" label="序号" width="60" align="center"/>
+						<el-table-column prop="PerformancePoints.T_name" align="center" label="名称" min-width="120"/>
+						<el-table-column label="绩效点分值" align="center" width="120">
+							<template #default="{ row }">
+                <span v-if="row.T_points_denominator && row.T_points_denominator !== 1">
+                  {{ row.T_points_numerator }}/{{ row.T_points_denominator }}
+                </span>
+								<span v-else>
+                  {{ row.T_points_numerator || 0 }}
+                </span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="T_quantity" label="数量" align="center" width="80"/>
+						<el-table-column label="工作量" align="center" width="100">
+							<template #default="{ row }">
+                <span style="color: #E6A23C; font-weight: bold;">
+                  {{
+						((row.T_points_numerator || 0) / (row.T_points_denominator || 1) * (row.T_quantity || 1)).toFixed(1)
+					}}
+                </span>
+							</template>
+						</el-table-column>
+						<el-table-column prop="T_remark" label="备注" min-width="150"/>
+			
+					</el-table>
+
+					<!-- 总计 -->
+					<div class="total-score">
+						<div class="score-summary">
+							<div class="score-item">
+								<span class="label">总工作量:</span>
+								<span class="value">{{ currentPerformance.T_workload || 0 }}</span>
+							</div>
+														<div class="score-item">
+								<span class="label">考核工作量:</span>
+								<span class="value">{{ currentPerformance.T_assess_points || 0 }}</span>
+							</div>
+							<div class="score-item">
+								<span class="label">计算总得分:</span>
+								<span class="value"
+									  :style="{ color: getScoreColor(calculateTotalScore(currentPerformance)), fontWeight: 'bold' }">
+                  {{ calculateTotalScore(currentPerformance).toFixed(1) }}%
+                </span>
+							</div>
+							<div class="score-item">
+								<span class="label">绩效工资:</span>
+								<span class="value">{{ currentPerformance.T_perf || 0 }}元</span>
+							</div>
+							<div class="score-item">
+								<span class="label">应发绩效:</span>
+								<span class="value">{{ calculatePerfTotal(currentPerformance) }}元</span>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</Drawer>
+	</div>
+
+	<!-- 添加/编辑绩效弹窗 -->
+	<el-dialog
+		v-model="isEditDialogVisible"
+		:title="isNew ? '添加绩效' : '编辑绩效'"
+		width="50%"
+	>
+		<el-form
+			ref="pointRuleFormRef"
+			:model="ruleForm"
+			label-width="100px"
+		>
+			<el-form-item label="所属月份" prop="T_date" :rules="[{ required: true, message: '请选择所属月份', trigger: 'blur' }]">
+				<el-date-picker
+					v-model="ruleForm.T_date"
+					type="month"
+					placeholder="选择月份"
+					format="YYYY-MM"
+					value-format="YYYY-MM"
+					style="width: 100%;"
+				/>
+			</el-form-item>
+			<el-form-item label="类型">
+				<el-select v-model="ruleForm.topType" style="width: 100%;" @change="onTopTypeChange">
+					<el-option label="报告" value="reporting" />
+					<el-option label="实施" value="collection" />
+				</el-select>
+			</el-form-item>
+
+			<div style="margin: 12px 20px; color: #606266;">明细</div>
+			<el-table :data="pointRows" border size="small" style="width: 98%; margin-left: 20px;">
+				<el-table-column type="index" label="序号" width="60" align="center" />
+				<el-table-column label="名称" min-width="160" align="center">
+					<template #default="{ row }">
+						<span>{{ row.T_name }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="绩效点分值" width="140" align="center">
+					<template #default="{ row }">
+						<span v-if="row.T_points_denominator && row.T_points_denominator !== 1">{{ row.T_points_numerator }}/{{ row.T_points_denominator }}</span>
+						<span v-else>{{ row.T_points_numerator }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="类型" width="120" align="center">
+					<template #default="{ row }">
+						<el-select v-model="row.T_type" style="width: 100%">
+							<el-option label="报告" value="reporting" />
+							<el-option label="实施" value="collection" />
+						</el-select>
+					</template>
+				</el-table-column>
+				<el-table-column label="数量" width="120" align="center">
+					<template #default="{ row }">
+						<el-input 
+							v-model="row.T_quantity" 
+							type="number" 
+							style="width: 100%" 
+							@input="row.T_quantity = Number(row.T_quantity) || 0"
+						/>
+					</template>
+				</el-table-column>
+				<el-table-column label="备注" min-width="160">
+					<template #default="{ row }">
+						<el-input v-model="row.T_remark" placeholder="备注" />
+					</template>
+				</el-table-column>
+				<el-table-column label="操作" width="100" align="center">
+					<template #default="{ $index }">
+						<el-button link type="danger" @click="removeRow($index)">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+
+			<div style="display:flex; justify-content: space-between; align-items:center; margin-top: 12px;">
+				<div style="margin-left: 20px;">
+					<el-button type="primary" plain @click="openPointSelector">添加</el-button>
+				</div>
+				<div style="color:#606266;">总工作量:<b style="color:#E6A23C;">{{ totalWorkload().toFixed(2) }}</b></div>
+			</div>
+		</el-form>
+
+		<!-- 绩效点选择弹窗(表格勾选,滚动分页) -->
+		<el-dialog v-model="selectDialogVisible" title="服务内容" width="40%">
+			<el-row :gutter="12" style="margin-bottom: 10px;">
+				<el-col :span="6">
+					<el-input v-model="pointQuery.T_name" placeholder="按名称搜索" clearable />
+				</el-col>
+				<el-col :span="4">
+					<el-button type="primary" @click="() => { pointPage.page = 1; fetchPointTable(false) }">查询</el-button>
+				</el-col>
+			</el-row>
+
+			<div ref="pointListWrap" style="max-height: 400px; overflow: auto;" @scroll="onPointScroll">
+			<el-table
+				:data="pointTable"
+				border
+				:loading="pointLoading"
+				row-key="Id"
+				reserve-selection
+				@selection-change="(rows:any[])=>handlePointSelectionChange(rows)"
+				style="width: 100%"
+			>
+				<el-table-column type="selection" width="50" />
+				<el-table-column prop="T_name" label="名称" min-width="160" />
+				<el-table-column label="数量" width="120" align="center">
+					<template #default="{ row }">
+						<span v-if="row.T_points_denominator && row.T_points_denominator !== 1">{{ row.T_points_numerator }}/{{ row.T_points_denominator }}</span>
+						<span v-else>{{ row.T_points_numerator }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column prop="T_remark" label="备注" min-width="200" show-overflow-tooltip />
+			</el-table>
+			</div>
+
+			<!-- 分页条移除,改为滚动加载 -->
+
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="selectDialogVisible = false">取消</el-button>
+					<el-button type="primary" @click="() => {
+						const exists = alreadySelectedIds()
+                        selectedPointRows.forEach((opt: any) => {
+							if (!exists.has(opt.Id)) {
+								pointRows.push({
+									T_performance_points_id: opt.Id,
+                                    T_name: opt.T_name,
+									T_points_numerator: Number(opt.T_points_numerator || 0),
+									T_points_denominator: Number(opt.T_points_denominator || 1),
+									T_quantity: 1,
+									T_type: ruleForm.topType,
+									T_remark: ''
+								})
+							}
+						})
+						selectDialogVisible = false
+					}">确定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+		<template #footer>
+			<span class="dialog-footer">
+				<el-button @click="isEditDialogVisible = false">取消</el-button>
+				<el-button type="primary" @click="submitForm">确定</el-button>
+			</span>
+		</template>
+	</el-dialog>
+</div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.performance-page {
+	@include f-direction;
+
+	.search-section {
+		width: 100%;
+
+		.inline-flex {
+			white-space: nowrap;
+		}
+	}
+}
+
+.performance-detail {
+	.detail-section {
+		margin-bottom: 24px;
+
+		h5 {
+			margin-bottom: 16px;
+			color: #303133;
+			font-size: 16px;
+			font-weight: 600;
+			border-bottom: 1px solid #e4e7ed;
+			padding-bottom: 8px;
+		}
+
+		.info-item {
+			margin-bottom: 12px;
+			display: flex;
+			align-items: center;
+
+			.label {
+				color: #606266;
+				margin-right: 8px;
+				min-width: 80px;
+			}
+
+			.value {
+				color: #303133;
+				font-weight: 500;
+			}
+		}
+	}
+
+	.total-score {
+		margin-top: 16px;
+		padding: 16px;
+		background-color: #f5f7fa;
+		border-radius: 4px;
+
+		.score-summary {
+			display: flex;
+			justify-content: space-around;
+			align-items: center;
+
+			.score-item {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+
+				.label {
+					color: #606266;
+					font-size: 12px;
+					margin-bottom: 4px;
+				}
+
+				.value {
+					color: #303133;
+					font-size: 16px;
+					font-weight: 600;
+				}
+			}
+		}
+	}
+}
+
+:deep(.el-drawer__body) {
+	padding: 20px;
+}
+
+:deep(.el-table) {
+	font-size: 13px;
+}
+
+:deep(.el-table th) {
+	background-color: #fafafa;
+}
+</style>

+ 316 - 0
src/views/salary/Performance/Points.vue

@@ -0,0 +1,316 @@
+<script setup lang="ts">
+import { ref, reactive, nextTick } from 'vue'
+import { GlobalStore } from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import TableBase from '@/components/TableBase/index.vue'
+import { Edit, Delete, Plus } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { ColumnProps } from '@/components/TableBase/interface/index'
+import { useTablePublic } from '@/hooks/useTablePublic'
+import {
+  PerformancePoints_List,
+  PerformancePoints_Add,
+  PerformancePoints_Edit,
+  PerformancePoints_Del
+} from '@/api/performance/index'
+
+const isNew = ref(true)
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const { resetForm, globalStore, searchOnTableList, updateOnTableList } = useTablePublic()
+
+// 初始化查询参数
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: ''
+})
+
+// 表单数据
+const form = ref({
+  Id: '',
+  T_name: '',
+  T_points: '',
+  T_remark: ''
+})
+
+// 表单验证规则
+const rules = reactive<FormRules>({
+  T_name: [{ required: true, message: '请输入绩效点名称', trigger: 'blur' }],
+  T_points: [
+    { required: true, message: '请输入绩效点数量', trigger: 'blur' },
+    {
+      pattern: /^(\d+(\.\d+)?|\d+\/\d+)$/,
+      message: '请输入正确的数字或分数格式(如:10 或 3/4)',
+      trigger: 'blur'
+    }
+  ]
+})
+
+// 表格列配置
+const columns: ColumnProps[] = [
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_name', label: '名称', minWidth: 150 },
+  {
+    prop: 'T_points_display',
+    label: '数量',
+    width: 120,
+    align: 'center',
+    name: 'T_points_display'
+  },
+  { prop: 'T_remark', label: '备注', minWidth: 200, ellipsis: true },
+  { prop: 'operation', label: '操作', width: 160, fixed: 'right' }
+]
+
+// 打开新增/编辑抽屉
+const openDrawer = (type: 'add' | 'edit', row?: any) => {
+  isNew.value = type === 'add'
+  if (type === 'edit' && row) {
+    // 根据分母处理绩效点数量的显示
+    let pointsValue = ''
+    if (row.T_points_denominator && row.T_points_denominator !== 1) {
+      pointsValue = `${row.T_points_numerator}/${row.T_points_denominator}`
+    } else {
+      pointsValue = row.T_points_numerator.toString()
+    }
+
+    form.value = {
+      Id: row.Id,
+      T_name: row.T_name,
+      T_points: pointsValue,
+      T_remark: row.T_remark || ''
+    }
+  } else {
+    form.value = {
+      Id: '',
+      T_name: '',
+      T_points: '',
+      T_remark: ''
+    }
+  }
+  drawerRef.value?.openDrawer()
+}
+
+// 抽屉关闭回调
+const callbackDrawer = (done: () => void) => {
+  done()
+  resetForm(ruleFormRef.value)
+}
+
+// 提交表单
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async valid => {
+    if (valid) {
+      try {
+        const params = {
+          User_tokey: globalStore.GET_User_tokey,
+          T_name: form.value.T_name,
+          T_points: form.value.T_points,
+          T_remark: form.value.T_remark
+        }
+
+        let res: any
+        if (isNew.value) {
+          res = await PerformancePoints_Add(params)
+        } else {
+          res = await PerformancePoints_Edit({
+            ...params,
+            T_id: form.value.Id
+          })
+        }
+
+        if (res.Code === 200) {
+          ElMessage.success(isNew.value ? '添加成功!' : '修改成功!')
+          drawerRef.value?.closeDrawer()
+          nextTick(() => {
+            updateTableList()
+          })
+        }
+      } catch (error) {
+        console.error('提交失败:', error)
+      }
+    }
+  })
+}
+
+// 删除绩效点
+const deletePerformancePoint = (row: any) => {
+  ElMessageBox.confirm('您确定要删除这个绩效点吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        const res: any = await PerformancePoints_Del({
+          User_tokey: globalStore.GET_User_tokey,
+          T_id: row.Id
+        })
+        if (res.Code === 200) {
+          ElMessage.success('删除成功!')
+          nextTick(() => {
+            updateTableList()
+          })
+        }
+      } catch (error) {
+        console.error('删除失败:', error)
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+// 搜索功能
+const searchInfo = () => {
+  TableRef.value?.searchTable()
+}
+
+// 更新表格数据
+const updateTableList = () => {
+  TableRef.value?.getTableList()
+}
+</script>
+
+<template>
+  <div class="performance-points">
+    <TableBase ref="TableRef" :columns="columns" :requestApi="PerformancePoints_List" :initParam="initParam">
+      <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0">
+            <el-col :xl="12" :lg="12" :md="12">
+              <span class="inline-flex items-center">名称:</span>
+              <el-input
+                v-model="initParam.T_name"
+                type="text"
+                class="w-50 m-2"
+                placeholder="按名称搜索"
+                @change="searchOnTableList(TableRef)"
+              />
+              <el-button type="primary" @click="searchOnTableList(TableRef)">搜索</el-button>
+            </el-col>
+
+            <el-col :xl="8" :md="8" class="btn">
+              <el-button type="primary" @click="openDrawer('add')">添加</el-button>
+            </el-col>
+          </el-row>
+        </div>
+      </template>
+
+      <template #T_points_display="{ row }">
+        <span v-if="row.T_points_denominator && row.T_points_denominator !== 1">
+          {{ row.T_points_numerator }}/{{ row.T_points_denominator }}
+        </span>
+        <span v-else>
+          {{ row.T_points_numerator }}
+        </span>
+      </template>
+
+      <template #right="{ row }">
+        <el-button link type="primary" size="small" :icon="Edit" @click="openDrawer('edit', row)"> 编辑 </el-button>
+        <el-button link type="danger" size="small" :icon="Delete" @click="deletePerformancePoint(row)">
+          删除
+        </el-button>
+      </template>
+    </TableBase>
+
+    <!-- 新增/编辑抽屉 -->
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">{{ isNew ? '添加' : '编辑' }} - 绩效点管理</h4>
+      </template>
+
+      <el-form ref="ruleFormRef" :model="form" :rules="rules" :label-width="formLabelWidth">
+        <el-form-item label="名称" prop="T_name">
+          <el-input v-model="form.T_name" placeholder="请输入名称" maxlength="50" show-word-limit />
+        </el-form-item>
+
+        <el-form-item label="数量" prop="T_points">
+          <el-input v-model="form.T_points" placeholder="请输入绩效点数量(如:10 或 3/4)" maxlength="20">
+            <template #append>
+              <span>个</span>
+            </template>
+          </el-input>
+          <div class="form-tip">支持整数(如:10)或分数格式(如:3/4)</div>
+        </el-form-item>
+
+        <el-form-item label="备注" prop="T_remark">
+          <el-input
+            v-model="form.T_remark"
+            type="textarea"
+            :rows="3"
+            placeholder="请输入备注信息"
+            maxlength="200"
+            show-word-limit
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <div class="form-buttons">
+            <el-button type="primary" @click="submitForm(ruleFormRef)">
+              {{ isNew ? '添加' : '修改' }}
+            </el-button>
+            <el-button @click="drawerRef?.closeDrawer()">取消</el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+    </Drawer>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.performance-points {
+  @include f-direction;
+  .input-suffix {
+    width: 100%;
+    .inline-flex {
+      white-space: nowrap;
+    }
+    .btn {
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+    }
+    .w-50 {
+      width: 12.5rem;
+    }
+  }
+}
+
+.form-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+  line-height: 1.4;
+}
+
+.form-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
+
+  .el-button {
+    min-width: 80px;
+  }
+}
+
+:deep(.el-drawer__body) {
+  padding: 20px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 20px;
+}
+
+:deep(.el-input-group__append) {
+  background-color: #f5f7fa;
+  border-left: none;
+  color: #909399;
+}
+</style>

+ 459 - 0
src/views/salary/Performance/Target.vue

@@ -0,0 +1,459 @@
+<script setup lang="ts">
+import { ref, reactive, nextTick, computed } from 'vue'
+import { GlobalStore } from '@/stores/index'
+import Drawer from '@/components/Drawer/index.vue'
+import TableBase from '@/components/TableBase/index.vue'
+import { Edit, Delete, Plus } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { ColumnProps } from '@/components/TableBase/interface/index'
+import { useTablePublic } from '@/hooks/useTablePublic'
+import {
+  PerformanceTarget_List,
+  PerformanceTarget_Add,
+  PerformanceTarget_Edit,
+  PerformanceTarget_Del
+} from '@/api/performance/index'
+
+const isNew = ref(true)
+const formLabelWidth = ref('120px')
+const ruleFormRef = ref<FormInstance>()
+const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
+const TableRef = ref<InstanceType<typeof TableBase> | null>(null)
+const { resetForm, globalStore, searchOnTableList, updateOnTableList } = useTablePublic()
+
+// 初始化查询参数
+const initParam = reactive({
+  User_tokey: globalStore.GET_User_tokey,
+  T_name: ''
+})
+
+// 表单数据
+const form = ref({
+  Id: '',
+  T_name: '',
+  T_operator: '≥',
+  T_assess_points: '',
+  T_base: '',
+  T_post: '',
+  T_perf: ''
+})
+
+// 运算符选项
+const operatorOptions = [
+  { label: '≥', value: '≥' },
+  { label: '<', value: '<' }
+]
+
+// 表单验证规则
+const rules = reactive<FormRules>({
+  T_name: [{ required: true, message: '请输入工资级别', trigger: 'blur' }],
+  T_operator: [{ required: true, message: '请选择运算符', trigger: 'blur' }],
+  T_assess_points: [
+    { required: true, message: '请输入月考核工作量', trigger: 'blur' },
+    { pattern: /^\d+$/, message: '请输入正整数', trigger: 'blur' }
+  ],
+  T_base: [
+    { required: true, message: '请输入无责底薪', trigger: 'blur' },
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的金额格式', trigger: 'blur' }
+  ],
+  T_post: [
+    { required: true, message: '请输入补助', trigger: 'blur' },
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的金额格式', trigger: 'blur' }
+  ],
+  T_perf: [
+    { required: true, message: '请输入绩效金额', trigger: 'blur' },
+    { pattern: /^\d+(\.\d+)?$/, message: '请输入正确的金额格式', trigger: 'blur' }
+  ]
+})
+
+// 表格列配置
+const columns: ColumnProps[] = [
+  { type: 'index', label: '序号', width: 80 },
+  { prop: 'T_name', label: '工资级别' },
+  { 
+    prop: 'T_points_display', 
+    label: '月考核工作量(个)', 
+    align: 'center',
+    name: 'T_points_display'
+  },
+  { prop: 'T_base', label: '无责底薪(元)',  align: 'center' },
+  { prop: 'T_post', label: '补助(元)',  align: 'center' },
+  { prop: 'T_perf', label: '绩效金额(元)',  align: 'center' },
+  { 
+    prop: 'total_income', 
+    label: '总收入(元)', 
+    width: 120, 
+    align: 'center',
+    name: 'total_income'
+  },
+  { prop: 'operation', label: '操作', width: 160, fixed: 'right' }
+]
+
+// 打开新增/编辑抽屉
+const openDrawer = (type: 'add' | 'edit', row?: any) => {
+  isNew.value = type === 'add'
+  if (type === 'edit' && row) {
+    form.value = {
+      Id: row.Id,
+      T_name: row.T_name,
+      T_operator: row.T_operator,
+      T_assess_points: (row.T_assess_points || '').toString(),
+      T_base: row.T_base.toString(),
+      T_post: row.T_post.toString(),
+      T_perf: row.T_perf.toString()
+    }
+  } else {
+    form.value = {
+      Id: '',
+      T_name: '',
+      T_operator: '≥',
+      T_assess_points: '',
+      T_base: '',
+      T_post: '',
+      T_perf: ''
+    }
+  }
+  drawerRef.value?.openDrawer()
+}
+
+// 抽屉关闭回调
+const callbackDrawer = (done: () => void) => {
+  done()
+  resetForm(ruleFormRef.value)
+}
+
+// 提交表单
+const submitForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async (valid) => {
+    if (valid) {
+      try {
+        const params = {
+          User_tokey: globalStore.GET_User_tokey,
+          T_name: form.value.T_name,
+          T_operator: form.value.T_operator,
+          T_assess_points: parseInt(form.value.T_assess_points),
+          T_base: parseFloat(form.value.T_base),
+          T_post: parseFloat(form.value.T_post),
+          T_perf: parseFloat(form.value.T_perf)
+        }
+
+        let res: any
+        if (isNew.value) {
+          res = await PerformanceTarget_Add(params)
+        } else {
+          res = await PerformanceTarget_Edit({
+            ...params,
+            T_id: form.value.Id
+          })
+        }
+
+        if (res.Code === 200) {
+          ElMessage.success(isNew.value ? '添加成功!' : '修改成功!')
+          drawerRef.value?.closeDrawer()
+          nextTick(() => {
+            updateTableList()
+          })
+        }
+      } catch (error) {
+        console.error('提交失败:', error)
+      }
+    }
+  })
+}
+
+// 删除绩效指标
+const deletePerformanceTarget = (row: any) => {
+  ElMessageBox.confirm('您确定要删除这个绩效指标吗?', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      try {
+        const res: any = await PerformanceTarget_Del({
+          User_tokey: globalStore.GET_User_tokey,
+          T_id: row.Id
+        })
+        if (res.Code === 200) {
+          ElMessage.success('删除成功!')
+          nextTick(() => {
+            updateTableList()
+          })
+        }
+      } catch (error) {
+        console.error('删除失败:', error)
+      }
+    })
+    .catch(() => {
+      ElMessage.info('已取消删除')
+    })
+}
+
+// 搜索功能
+const searchInfo = () => {
+  TableRef.value?.searchTable()
+}
+
+// 更新表格数据
+const updateTableList = () => {
+  TableRef.value?.getTableList()
+}
+
+// 计算底薪(无责底薪 + 补助)
+const baseSalary = computed(() => {
+  const base = parseFloat(form.value.T_base) || 0
+  const post = parseFloat(form.value.T_post) || 0
+  const result = base + post
+  return result > 0 ? result.toFixed(2) : '0.00'
+})
+
+// 计算总收入(无责底薪 + 补助 + 绩效金额)
+const totalIncome = computed(() => {
+  const base = parseFloat(form.value.T_base) || 0
+  const post = parseFloat(form.value.T_post) || 0
+  const perf = parseFloat(form.value.T_perf) || 0
+  const result = base + post + perf
+  return result > 0 ? result.toFixed(2) : '0.00'
+})
+</script>
+
+<template>
+  <div class="performance-target">
+    <TableBase
+      ref="TableRef"
+      :columns="columns"
+      :requestApi="PerformanceTarget_List"
+      :initParam="initParam"
+    >
+      <template #table-header>
+        <div class="input-suffix">
+          <el-row :gutter="20" style="margin-bottom: 0" align="middle">
+            <el-col :span="6">
+              <div style="display: flex; align-items: center;">
+                <span style="white-space: nowrap; margin-right: 8px;">工资级别:</span>
+                <el-input
+                  v-model="initParam.T_name"
+                  placeholder="按工资级别搜索"
+                  clearable
+                  @change="searchInfo"
+                  @keyup.enter="searchInfo"
+                />
+              </div>
+            </el-col>
+            <el-col :span="3">
+              <el-button type="primary" @click="searchInfo">搜索</el-button>
+            </el-col>
+            <el-col :span="3" :push="12">
+              <el-button type="primary" :icon="Plus" @click="openDrawer('add')">
+                添加
+              </el-button>
+            </el-col>
+          </el-row>
+        </div>
+      </template>
+
+      <template #T_points_display="{ row }">
+        <span>{{ row.T_operator }}{{ row.T_assess_points }}</span>
+      </template>
+
+      <template #total_income="{ row }">
+        <span style="color: #E6A23C; font-weight: bold;">
+          {{ Math.round(parseFloat(row.T_base) + parseFloat(row.T_post) + parseFloat(row.T_perf)) }}
+        </span>
+      </template>
+
+      <template #right="{ row }">
+        <el-button
+          link
+          type="primary"
+          size="small"
+          :icon="Edit"
+          @click="openDrawer('edit', row)"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="danger"
+          size="small"
+          :icon="Delete"
+          @click="deletePerformanceTarget(row)"
+        >
+          删除
+        </el-button>
+      </template>
+    </TableBase>
+
+    <!-- 新增/编辑抽屉 -->
+    <Drawer ref="drawerRef" :handleClose="callbackDrawer">
+      <template #header="{ params }">
+        <h4 :id="params.titleId" :class="params.titleClass">
+          {{ isNew ? '添加' : '编辑' }} - 绩效指标管理
+        </h4>
+      </template>
+      
+      <el-form ref="ruleFormRef" :model="form" :rules="rules" :label-width="formLabelWidth">
+        <el-form-item label="工资级别" prop="T_name">
+          <el-input
+            v-model="form.T_name"
+            placeholder="请输入工资级别"
+            maxlength="50"
+            show-word-limit
+          />
+        </el-form-item>
+        
+        <el-form-item label="月考核工作量" prop="T_assess_points">
+          <el-row :gutter="10">
+            <el-col :span="6">
+              <el-select v-model="form.T_operator" placeholder="请选择运算符">
+                <el-option
+                  v-for="item in operatorOptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="18">
+              <el-input
+                v-model="form.T_assess_points"
+                placeholder="请输入月考核工作量"
+                maxlength="10"
+              >
+                <template #append>
+                  <span>个</span>
+                </template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        
+        <el-form-item label="无责底薪" prop="T_base">
+          <el-input
+            v-model="form.T_base"
+            placeholder="请输入无责底薪"
+            maxlength="20"
+          >
+            <template #append>
+              <span>元</span>
+            </template>
+          </el-input>
+        </el-form-item>
+        
+        <el-form-item label="补助" prop="T_post">
+          <el-input
+            v-model="form.T_post"
+            placeholder="请输入补助金额"
+            maxlength="20"
+          >
+            <template #append>
+              <span>元</span>
+            </template>
+          </el-input>
+        </el-form-item>
+        
+        <el-form-item label="底薪" label-width="120px">
+          <el-input
+            :value="baseSalary"
+            readonly
+            disabled
+            placeholder="自动计算"
+          >
+            <template #append>
+              <span>元</span>
+            </template>
+          </el-input>
+          <div style="font-size: 12px; color: #909399; margin-top: 4px;">
+            底薪 = 无责底薪 + 补助
+          </div>
+        </el-form-item>
+        
+        <el-form-item label="绩效金额" prop="T_perf">
+          <el-input
+            v-model="form.T_perf"
+            placeholder="请输入绩效金额"
+            maxlength="20"
+          >
+            <template #append>
+              <span>元</span>
+            </template>
+          </el-input>
+        </el-form-item>
+        
+        <el-form-item label="总收入" label-width="120px">
+          <el-input
+            :value="totalIncome"
+            readonly
+            disabled
+            placeholder="自动计算"
+          >
+            <template #append>
+              <span>元</span>
+            </template>
+          </el-input>
+          <div style="font-size: 12px; color: #909399; margin-top: 4px;">
+            总收入 = 无责底薪 + 补助 + 绩效金额
+          </div>
+        </el-form-item>
+        
+        <el-form-item>
+          <div class="form-buttons">
+            <el-button type="primary" @click="submitForm(ruleFormRef)">
+              {{ isNew ? '添加' : '修改' }}
+            </el-button>
+            <el-button @click="drawerRef?.closeDrawer()">取消</el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+    </Drawer>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@import '@/styles/var.scss';
+
+.performance-target {
+  @include f-direction;
+  
+  .input-suffix {
+    width: 100%;
+    
+    .inline-flex {
+      white-space: nowrap;
+    }
+    
+    .btn {
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+    }
+  }
+}
+
+.form-buttons {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 20px;
+  
+  .el-button {
+    min-width: 80px;
+  }
+}
+
+:deep(.el-drawer__body) {
+  padding: 20px;
+}
+
+:deep(.el-form-item) {
+  margin-bottom: 20px;
+}
+
+:deep(.el-input-group__append) {
+  background-color: #f5f7fa;
+  border-left: none;
+  color: #909399;
+}
+</style>