|
@@ -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>
|