【数字后端】- Standard Cell Status

标准单元的放置状态有以下几种情况:unplaced, placed, fixed, cover, soft_fixed。

  • unplaced:place阶段之前,所有的cell都是unplaced

  • placed:place之后,cell会变成place状态

  • fixed:一些固定的单元,工具不可以挪动。一些特定的cell,如ENDCAP、WellTAP以及clock tree上的cell,均为fixed状态

  • cover:工具和手动均不可以挪动,防止误操作

  • soft_fixed:介于placed和fixed之间。在global place中不可以移动,但是在detail place可以被移动或者optimization可以被size

fixed状态的cell:
都是fixed状态
此外,在check place命令,也会分别报出包含和不包含fixed cell的density。如下:
在这里插入图片描述

代码与文字齐飞,逻辑共创意一色!若我的文章让你会心一笑,点个赞我们一起笑谈"IC人生"!qxOWwKbl1tTzkyu.png

1、Digital circuits are made from large assemblies of logic gates, which gate cell is the most simple one(5 分) AINV BNAND CNOR DDFF 2、以下逻辑功能符号错误的是(5 分) A与:& B或:| C非:^ D异或:^ 3、关于后端数字设计流程的描述错误的是(5 分) APhysical Verification主要包含DRC、LVS检查等 BPost-layout Verification是用Place&Route产生的网表做晶体管级仿真 CSTA Timing是对布局布线后的结果做时序分析 DPlace&Route 是对综合后的门级网表做布局布线的进一步处理 4、关于前端数字设计流程迭代过程的描述错误的是(5 分) A如果RTL Verification有错误,需要返回到RTL design并再次Verification B如果Logic Synthesis结果有violation,需要迭代几次Synthesis或者返回RTL Design C如果Gate-level Verification有错误,可能返回Logic Synthesis D如果Gate-level Verification有错误,不会返回到RTL Design 5、关于后端数字设计流程迭代过程的描述错误的是(5 分) A如果Place&Route结果有violation,可以尝试多次迭代 B如果STA Timing结果有violation,可以尝试多次迭代 C如果Post-layout Verification有错误,可能需要返回Place&Route D如果Place&Route结果无误,则进入STA Timing流程 6、以下硬件描述形式,是门级描述常用的方法的是(5 分) Aalways块 Bif条件语句 Cassign赋值语句 D模块例化 7、A classical verification testbench structure does not include(5 分) ADUT BStimulus CMonitors DScoreboard 8、Following testbench components will collect the simulation result except(5 分) AChecker BStimulus CMonitors DScoreboard 9、The description of testbench components are correct except(5 分) AMonitor: capture output/status BChecker: compare input with output and status to analysis whether DUT function pass CReference Model: golden models of some functional blocks for verification DScoreboard: store some key flags to control the stimulus 10、Following UVM components are under uvm_agent except(5 分) Auvm_monitor Buvm_driver Cuvm_cofig Duvm_sequencer 11、Following descriptions of NCsim commands are correct except(5 分) Ancverilog: Compiles the Verilog source files Bncvlog: Compiles the Verilog source files Cncelab: Elaborates the design and generates a simulation snapshot Dncsim: Simulates the snapshot 12、The input files of Logic Synthesis do not include(5 分) AVerilog file of RTL design BConstraint file CStandard delay file DTiming library file 13、Logic Synthesis flow does not include(5 分) ASetting Design Environment BSetting Design Constraint CCompile Design DSpecification 14、The wrong description of physical aware Synthesis is(5 分) ADCG has more physical library and layer, congestion related setting BDCG can more accurately consider the path median delay, and optimize the timing with more accurate path CDCG usually extracts physical constraint information from DEF files after ICC is floorplan DDCG use “dc_shell –dcg” to launch 15、The Place&Route includes following activities except(5 分) AFunctionality check BDesign Preparation CDesign Planning DPlacement 16、Following steps are included in Place&Route except(5 分) AClock Tree Synthesis BRoute CFormal equivalence check DChip Finishing 17、The input files of Static Timing Analysis do not include(5 分) ADesign data BGDS file CParasitic data file DTiming library file 18、Analysis modes of STA do not include(5 分) ASingle mode BBC_WC mode CPPA mode DOCV mode 19、The elements to construct different scenarios in MCMM do not include(5 分) ADifferent clock tree methods BPVT corners CDifferent functional modes with different constraint DParasitic views 20、The wrong description of post-layout verification is(5分) AUse post-layout netlist from APR tool as one of inputs BUse SDF file generated by STA tool to annotate timing delay info CCan use parasitic file SPEF for timing delay calculation DUse “–sdf” option to active SDF file in simulation
09-20
<template> <el-main> <!-- 查询条件 --> <el-form :model="searchModel" ref="searchForm" label-width="80px" :inline="true" size="small" > <el-form-item> <el-input v-model="searchModel.name" placeholder="请输入姓名" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-input v-model="searchModel.category" placeholder="请输入事项类别" style="width: 220px" ></el-input> </el-form-item> <!-- 审核状态下拉框查询条件 --> <el-form-item label="审核状态"> <el-select v-model="searchModel.auditStatus" placeholder="全部" style="width: 180px" @change="search()" > <el-option label="全部" value="" /> <el-option label="待审核" value="0" /> <el-option label="已初审" value="1" /> <el-option label="初审驳回" value="2" /> <el-option label="已终审" value="3" /> <el-option label="终审驳回" value="4" /> </el-select> </el-form-item> <el-form-item> <div style="display: flex; align-items: center; gap: 10px"> <!-- 查询/重置/新增按钮组 --> <div> <el-button type="primary" icon="el-icon-search" @click="search()" >查询</el-button > <el-button icon="el-icon-refresh-right" @click="resetValue()" >重置</el-button > <el-button type="success" icon="el-icon-plus" @click="openAddWindow()" >积分申请</el-button > </div> <!-- 导出/导入按钮组 --> <div style="display: flex; gap: 10px; margin-left: 10px"> <el-button type="primary" icon="el-icon-download" @click="exportExcel()" >导出Excel</el-button > <el-upload :action="importExcelUrl" :headers="uploadHeaders" :show-file-list="false" :on-success="importExcel" :before-upload="beforeUpload" > <el-button type="success" icon="el-icon-upload2" >导入Excel</el-button > </el-upload> </div> </div> </el-form-item> </el-form> <!-- 数据表格 --> <el-table :data="scoreDetailList" :height="tableHeight" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" show-summary :summary-method="getSummaries" > <!-- 原有列保持不变 --> <el-table-column prop="id" label="序号" width="80" align="center" fixed="left" ></el-table-column> <el-table-column prop="recordDate" label="日期" width="120" header-align="center" align="center" fixed="left" ></el-table-column> <el-table-column prop="name" label="姓名" width="100" header-align="center" align="center" fixed="left" ></el-table-column> <!-- 新增图像列 --> <el-table-column prop="avatar" label="图像" width="120" align="center" header-align="center" > <template slot-scope="scope"> <el-image v-if="scope.row.avatar" style="width: 50px; height: 50px; border-radius: 10%" :src="getAvatarUrl(scope.row.avatar)" :preview-src-list="[getAvatarUrl(scope.row.avatar)]" @error="() => handleImageError(scope.row)" > <div slot="error" class="image-error" /> </el-image> </template> </el-table-column> <!-- 剩余列保持不变 --> <el-table-column prop="category" label="事项类别" width="180" header-align="center" ></el-table-column> <el-table-column prop="score" label="奖惩积分" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="originalScore" label="原始分值" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="executor" label="执行扣分人" width="100" header-align="center" align="center" ></el-table-column> <el-table-column prop="errorType" label="错误类型" width="180" header-align="center" ></el-table-column> <el-table-column prop="mistakeCount" label="犯错次数" width="80" header-align="center" align="center" ></el-table-column> <el-table-column prop="redEnvelope" label="红包" width="80" header-align="center" align="center" ></el-table-column> <el-table-column prop="details" label="具体事情" width="250" header-align="center" ></el-table-column> <el-table-column prop="creator" label="制单人" width="120" header-align="center" ></el-table-column> <el-table-column prop="createDate" label="制单日期" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="remarks" label="备注" width="200" header-align="center" ></el-table-column> <el-table-column prop="auditStatus" label="审核状态" width="100" align="center" header-align="center" > <template slot-scope="scope"> {{ formatAuditStatus(scope.row.auditStatus) }} </template> </el-table-column> <el-table-column prop="firstAuditOpinion" label="初审意见" width="200" header-align="center" ></el-table-column> <el-table-column prop="finalAuditOpinion" label="终审意见" width="200" header-align="center" ></el-table-column> <!-- 操作列 --> <el-table-column label="操作" align="center" width="400" fixed="right"> <template slot-scope="scope"> <!-- 编辑按钮:仅待审核、初审驳回、终审驳回时可见 --> <el-button icon="el-icon-edit" type="primary" size="small" @click="handleEdit(scope.row)" :disabled="scope.row.auditStatus === 3" v-if="[0, 2, 4].includes(scope.row.auditStatus)" >编辑</el-button > <!-- 删除按钮:仅待审核、终审驳回时可见 --> <el-button icon="el-icon-delete" type="danger" size="small" @click="handleDelete(scope.row)" :disabled=" scope.row.auditStatus === 1 || scope.row.auditStatus === 3 " v-if="[0, 4].includes(scope.row.auditStatus)" >删除</el-button > <!-- 初审按钮:仅待审核、初审驳回时可见,已初审状态显示为浅蓝色 --> <el-button icon="el-icon-edit" :type="scope.row.auditStatus === 1 ? 'info' : 'primary'" size="small" @click="firstAudit(scope.row)" v-if=" hasPermission('api:scoreDetail:audit:save') && [0, 1, 2].includes(scope.row.auditStatus) " >初审</el-button > <!-- 终审按钮:仅初审通过、终审驳回时可见,已终审状态显示为浅蓝色 --> <el-button icon="el-icon-edit" :type="scope.row.auditStatus === 3 ? 'info' : 'success'" size="small" @click="finalAudit(scope.row)" v-if=" hasPermission('api:scoreDetail:audit:save') && [1, 3, 4].includes(scope.row.auditStatus) " >终审</el-button > <!-- 行内上传图像按钮 --> <el-upload :action="uploadFileUrl" :show-file-list="false" :headers="uploadHeaders" :before-upload="(file) => beforeAvatarUpload(file, scope.row)" :on-success=" (response, file, fileList) => handleRowAvatarUpload(scope.row, response) " :on-error=" (error, file, fileList) => handleAvatarUploadError(scope.row, error) " style="display: inline-flex; align-items: center" > <el-button type="primary" size="small" icon="el-icon-upload" >上传图像</el-button > </el-upload> </template> </el-table-column> </el-table> <!-- 分页工具栏 --> <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageNum" :page-sizes="[10, 20, 30, 40, 50]" :page-size="10" layout="total, sizes, prev, pager, next, jumper" :total="total" ></el-pagination> <!-- 添加修改分数详情窗口 --> <el-dialog :title="scoreDetailDialog.title" :visible.sync="scoreDetailDialog.visible" width="80%" > <el-form :model="scoreDetail" ref="scoreDetailForm" :rules="rules" label-width="80px" :inline="false" size="small" > <!-- 第1行:增加奖惩类型单选按钮 --> <el-row> <el-col :span="24"> <el-form-item label="奖惩类型"> <el-radio-group v-model="scoreType" @change="handleScoreTypeChange" > <el-radio label="reward">奖分</el-radio> <el-radio label="punish">扣分</el-radio> </el-radio-group> </el-form-item> </el-col> </el-row> <!-- 第2行 --> <el-row> <el-col :span="6"> <el-form-item label="日期" prop="recordDate"> <el-date-picker v-model="scoreDetail.recordDate" type="date" placeholder="选择日期" style="width: 180px" ></el-date-picker> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="姓名" prop="name"> <el-input v-model="scoreDetail.name" style="width: 150px" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="事项类别" prop="category"> <el-input v-model="scoreDetail.category" style="width: 180px" ></el-input> <el-button type="success" size="small" @click="openStandardDialog()" style="margin-left: 5px" > 积分标准 </el-button> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="奖惩积分" prop="score"> <el-input v-model.number="scoreDetail.score" style="width: 180px" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第3行 --> <el-row> <!-- 左侧12列区域 - 跨两行显示积分标准 --> <el-col :span="12"> <el-form-item label="积分标准" prop="standard"> <!-- 使用文本域并设置合适的高度,使其视觉上占据两行空间 --> <el-input v-model="scoreDetail.standard" type="textarea" :rows="5" style="width: 100%;" placeholder="请输入积分标准" ></el-input > </el-form-item> </el-col> <!-- 仅扣分时显示 --> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="原始分值" prop="originalScore"> <el-input v-model.number="scoreDetail.originalScore" style="width: 180px" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="犯错次数" prop="mistakeCount"> <el-input v-model.number="scoreDetail.mistakeCount" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="红包" prop="redEnvelope"> <el-input v-model.number="scoreDetail.redEnvelope" style="width: 100%" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第4行 --> <el-row> <el-col :span="12"> </el-col> <!-- 仅扣分时显示 --> <el-col :span="8" v-if="scoreType === 'punish'"> <el-form-item label="执行扣分人" prop="executor"> <el-input v-model="scoreDetail.executor" style="width: 200px" @click.native="openPersonSelectDialog" :readonly="true" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="错误类型" prop="errorType"> <el-input v-model="scoreDetail.errorType" style="width: 180px" @click.native="openErrorTypeDialog" :readonly="true" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第5行 --> <el-row> <el-col :span="12"> <el-form-item label="具体事情" prop="details"> <el-input v-model="scoreDetail.details" type="textarea" :rows="4" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="12"> <!-- 图像容器:整体居中 --> <div style=" display: flex; flex-direction: column; align-items: center; height: 100%; " > <!-- 上传按钮:在图像上方居中 --> <el-form-item label="图像" prop="avatar" style="width: 100%; text-align: center; margin-bottom: 15px" > <el-upload :action="uploadFileUrl" :show-file-list="false" :headers="uploadHeaders" :before-upload="beforeAvatarUpload" :on-success="handleFormAvatarUpload" :on-error="handleAvatarUploadError" style="display: inline-block" > <el-button type="primary" size="small" icon="el-icon-upload" >上传图像</el-button > </el-upload> </el-form-item> <!-- 图像预览区:居中显示 --> <div style=" flex: 1; display: flex; align-items: center; justify-content: center; width: 100%; " > <el-image v-if="scoreDetail.avatar" style="width: 120px; height: 120px; border-radius: 10%" :src="getAvatarUrl(scoreDetail.avatar)" :preview-src-list="[getAvatarUrl(scoreDetail.avatar)]" @error="handleFormImageError" > <div slot="error" class="image-error" style=" width: 120px; height: 120px; display: flex; align-items: center; justify-content: center; " /> </el-image> <!-- 无图像时的占位符:居中显示 --> <div v-else style=" width: 120px; height: 120px; border-radius: 10%; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #ccc; font-size: 14px; " > 暂无图像 </div> </div> </div> </el-col> </el-row> <!-- 第6行 --> <el-row> <el-col :span="6"> <el-form-item label="制单人" prop="creator"> <el-input v-model="scoreDetail.creator" style="width: 180px" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="制单日期" prop="createDate"> <el-date-picker v-model="scoreDetail.createDate" type="date" placeholder="选择日期" style="width: 180px" ></el-date-picker> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="备注" prop="remarks"> <el-input v-model="scoreDetail.remarks" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="审核状态" prop="auditStatus"> <!-- 显示汉字状态而非数字 --> <el-input v-model="currentAuditStatusText" style="width: 100px" disabled ></el-input> </el-form-item> </el-col> </el-row> <!-- 审核区域 --> <div v-if="['初审', '终审'].includes(scoreDetailDialog.title)"> <el-row> <el-col :span="8"> <el-form-item label="审核意见" prop="auditOpinion" :rules="getAuditOpinionRules()" > <el-input v-model="scoreDetail.auditOpinion" type="textarea" :rows="2" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="审核人员"> <el-input v-model="currentAuditUser" disabled style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="审核时间"> <el-input v-model="currentAuditTime" disabled style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="8"> <el-form-item> <el-button type="success" @click="handleAuditApprove" >审核通过</el-button > <el-button @click="handleAuditCancel">取消审核</el-button> <el-button type="danger" @click="handleAuditReject" >审核驳回</el-button > </el-form-item> </el-col> </el-row> </div> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="scoreDetailDialog.visible = false">取消</el-button> <el-button type="primary" @click="onConfirm" v-if=" ['新增分数详情', '编辑分数详情'].includes(scoreDetailDialog.title) " >确定</el-button > </div> </el-dialog> <!-- 积分标准选择窗口 --> <el-dialog :title="standardSelectDialog.title" :visible.sync="standardSelectDialog.visible" width="60%" > <!-- 保持不变 --> <el-form :model="standardSearchModel" ref="standardSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="事项类别"> <el-input v-model="standardSearchModel.category" placeholder="请输入事项类别" style="width: 220px" ></el-input> </el-form-item> <el-form-item label="积分标准"> <el-input v-model="standardSearchModel.standard" placeholder="请输入积分标准" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchStandards()" >查询</el-button > </el-form-item> </el-form> <el-table :data="standardList" :height="standardTableHeight" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" fixed="left" ></el-table-column> <el-table-column prop="category" label="事项类别" width="200" header-align="center" ></el-table-column> <el-table-column prop="standard" label="事项标准" header-align="center" ></el-table-column> <el-table-column prop="score" label="分值" width="120" header-align="center" ></el-table-column> <el-table-column prop="remarks" label="备注" header-align="center" ></el-table-column> <!-- 操作列 --> <el-table-column label="操作" align="center" width="120" fixed="right"> <template slot-scope="scope"> <el-button icon="el-icon-check" type="success" size="small" @click="selectStandard(scope.row)" >选择</el-button > </template> </el-table-column> </el-table> <!-- 积分标准选择窗口分页工具栏 --> <el-pagination v-if="standardTotal > 0" background @size-change="handleStandardSizeChange" @current-change="handleStandardCurrentChange" :current-page="standardPageNum" :page-sizes="[10, 20, 30, 40, 50]" :page-size="standardPageSize" layout="total, sizes, prev, pager, next, jumper" :total="standardTotal" ></el-pagination> <div slot="footer" class="dialog-footer"> <el-button @click="standardSelectDialog.visible = false" >取消</el-button > </div> </el-dialog> <!-- 新增:选择执行扣分人对话框 --> <el-dialog :title="personSelectDialog.title" :visible.sync="personSelectDialog.visible" width="20%" > <el-form :model="personSearchModel" ref="personSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="人员姓名"> <el-input v-model="personSearchModel.name" placeholder="请输入人员姓名" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchPersons()" >查询</el-button > </el-form-item> </el-form> <el-table :data="personList" height="400" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" ></el-table-column> <el-table-column prop="name" label="姓名" width="120" header-align="center" > <template slot-scope="scope"> <el-button type="text" @click="selectPerson(scope.row)"> {{ scope.row.name }} </el-button> </template> </el-table-column> <!-- 操作列 --> </el-table> <div slot="footer" class="dialog-footer"> <el-button @click="personSelectDialog.visible = false">取消</el-button> </div> </el-dialog> <!-- 新增:错误类型选择对话框 --> <el-dialog :title="errorTypeDialog.title" :visible.sync="errorTypeDialog.visible" width="30%" > <el-form :model="errorTypeSearchModel" ref="errorTypeSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="错误类型"> <el-input v-model="errorTypeSearchModel.name" placeholder="请输入错误类型" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchErrorTypes()" >查询</el-button > </el-form-item> </el-form> <el-table :data="errorTypeList" height="400" border stripe style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" ></el-table-column> <el-table-column prop="name" label="错误类型" header-align="center"> <!-- 修改:点击文本即可选择 --> <template slot-scope="scope"> <el-button type="text" @click="selectErrorType(scope.row)"> {{ scope.row.name }} </el-button> </template> </el-table-column> </el-table> <div slot="footer" class="dialog-footer"> <el-button @click="errorTypeDialog.visible = false">取消</el-button> </div> </el-dialog> </el-main> </template> <script> import { getScoreDetails, addScoreDetail, updateScoreDetail, deleteScoreDetail, getErrorCount, getScoreErrorTypes, } from "@/api/company/hr/scoreDetail"; import { getScoreStandards } from "@/api/company/hr/scoreStandard"; import { getScorePersons } from "@/api/company/hr/scorePerson"; import { getToken, getUsername } from "@/utils/auth"; import axios from "axios"; export default { name: "scoreDetailList", data() { return { // 新增:奖惩类型,默认奖分 scoreType: "reward", // 错误类型对话框相关数据 errorTypeDialog: { title: "选择错误类型", visible: false, }, errorTypeSearchModel: { errorType: "", pageNum: 1, pageSize: 10, }, errorTypeList: [], errorTypePageNum: 1, errorTypePageSize: 10, errorTypeTotal: 0, // 当前审核记录在列表中的索引(用于自动跳转) currentAuditIndex: -1, // 审核状态映射(数字转汉字) auditStatusMap: { 0: "待审核", 1: "已初审", 2: "初审驳回", 3: "已终审", 4: "终审驳回", }, // 查询条件 searchModel: { name: "", category: "", auditStatus: "", // 审核状态:空-全部,0-待审核,1-已初审,2-初审驳回,3-已终审,4-终审驳回 pageNum: 1, pageSize: 10, }, // 表格数据 scoreDetailList: [], tableHeight: 0, pageNum: 1, pageSize: 10, total: 0, // 分数详情表单 scoreDetail: { id: "", recordDate: new Date(), name: this.$store.getters.name, // 当前登录用户 category: "", score: "", originalScore: "", executor: "", errorType: "", mistakeCount: "", redEnvelope: "", details: "", creator: "", createDate: new Date(), remarks: "", auditStatus: 0, auditOpinion: "", firstAuditUser: "", firstAuditTime: "", finalAuditUser: "", finalAuditTime: "", avatar: "", // 图像字段 }, // 积分标准选择对话框数据 standardSelectDialog: { title: "选择积分标准", visible: false, }, standardSearchModel: { category: "", standard: "", pageNum: 1, pageSize: 10, }, standardList: [], standardTableHeight: 0, standardPageNum: 1, standardPageSize: 10, standardTotal: 0, // 表单验证规则 rules: { name: [{ required: true, trigger: "blur", message: "请输入姓名" }], recordDate: [ { required: true, trigger: "blur", message: "请选择日期" }, ], category: [ { required: true, trigger: "blur", message: "请输入事项类别" }, ], score: [{ required: true, trigger: "blur", message: "请输入奖惩积分" }], }, // 对话框 scoreDetailDialog: { title: "", visible: false, }, // 导入导出相关 uploadHeaders: { token: "" }, importExcelUrl: process.env.VUE_APP_BASE_API + "api/scoreDetail/import/easyExcel", // 图像上传相关配置 uploadFileUrl: process.env.VUE_APP_BASE_API + "files/upload", // 图像上传接口 uploadAvatarFileUrl: process.env.VUE_APP_BASE_API + "files/", // 图像显示基础路径 // 审核相关数据 currentAuditType: "", // 'first' 或 'final' currentAuditUser: "", currentAuditTime: "", currentAuditAction: "", // 'approve' 或 'reject' // 审核状态控制 auditStatusControl: { firstAudit: { enabled: true, // 始终启用初审 status: 0, // 0:待审核, 1:已初审, 2:初审驳回 }, finalAudit: { enabled: true, // 始终启用终审 status: 0, // 0:待终审, 1:已终审, 2:终审驳回 }, }, // 新增:选择执行扣分人对话框数据 personSelectDialog: { title: "选择执行扣分人", visible: false, }, personSearchModel: { name: "", pageNum: 1, pageSize: 10, }, personList: [], personTotal: 0, personPageNum: 1, personPageSize: 10, }; }, computed: { currentToken() { return getToken(); }, currentUsername() { return this.$store.getters.name; }, // 计算属性:当前审核状态文本 currentAuditStatusText() { const statusMap = { 0: "待审核", 1: "已初审", 2: "初审驳回", 3: "已终审", 4: "终审驳回", }; // 处理新增时的默认状态 return this.scoreDetail.id ? statusMap[this.scoreDetail.auditStatus] || "未知状态" : "待审核"; }, }, watch: { currentToken(newVal) { this.uploadHeaders.token = newVal; }, // 监听对话框标题变化,更新审核相关数据 "scoreDetailDialog.title"(newVal) { if (newVal === "初审") { this.currentAuditType = "first"; this.currentAuditUser = this.scoreDetail.firstAuditUser || this.currentUsername; this.currentAuditTime = this.scoreDetail.firstAuditTime; this.scoreDetail.auditOpinion = this.scoreDetail.firstAuditOpinion || ""; // 更新审核状态控制 this.auditStatusControl.firstAudit.status = this.scoreDetail.auditStatus; } else if (newVal === "终审") { this.currentAuditType = "final"; this.currentAuditUser = this.scoreDetail.finalAuditUser || this.currentUsername; this.currentAuditTime = this.scoreDetail.finalAuditTime; this.scoreDetail.auditOpinion = this.scoreDetail.finalAuditOpinion || ""; // 更新审核状态控制 this.auditStatusControl.finalAudit.status = this.scoreDetail.auditStatus; } }, }, methods: { // 新增:表格合计方法 getSummaries(param) { const { columns, data } = param; const sums = []; columns.forEach((column, index) => { if (index === 0) { // 第一列显示"合计" sums[index] = '合计'; return; } // 只对奖惩积分列进行合计 if (column.property === 'score') { const values = data.map(item => Number(item.score)); if (!values.every(value => isNaN(value))) { const sum = values.reduce((prev, curr) => { const value = Number(curr); return isNaN(value) ? prev : prev + value; }, 0); // 格式化合计值(保留两位小数,千分位分隔) sums[index] = sum.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } else { sums[index] = ''; } } else { // 其他列不显示合计 sums[index] = ''; } }); return sums; }, // 新增:处理奖惩类型变更 handleScoreTypeChange(val) { // 当切换为奖分时清空扣分相关字段 if (val === "reward") { this.scoreDetail.originalScore = ""; this.scoreDetail.mistakeCount = ""; this.scoreDetail.redEnvelope = ""; this.scoreDetail.executor = ""; this.scoreDetail.errorType = ""; } }, // 打开错误类型对话框 openErrorTypeDialog() { this.errorTypeSearchModel = { errorType: "", pageNum: 1, pageSize: 10, }; this.searchErrorTypes(); this.errorTypeDialog.visible = true; }, // 查询错误类型列表 async searchErrorTypes() { try { const res = await getScoreErrorTypes({ ...this.errorTypeSearchModel, pageNum: this.errorTypePageNum, pageSize: this.errorTypePageSize, }); if (res.code === 200) { this.errorTypeList = res.data.rows || []; this.errorTypeTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询错误类型失败"); } } catch (error) { this.$message.error("查询错误类型请求异常"); } }, // 选择错误类型 - 简化逻辑 async selectErrorType(row) { // 直接使用row.name作为错误类型值 this.scoreDetail.errorType = row.name; this.errorTypeDialog.visible = false; // 强制触发视图更新 this.$forceUpdate(); // 自动更新犯错次数 await this.updateMistakeCount(); }, // 更新犯错次数 async updateMistakeCount() { if ( this.scoreDetail.recordDate && this.scoreDetail.name && this.scoreDetail.errorType ) { try { // 从日期中提取年份 const year = new Date(this.scoreDetail.recordDate).getFullYear(); // 调用API获取错误次数 const res = await getErrorCount({ name: this.scoreDetail.name, year: year, errorType: this.scoreDetail.errorType, }); if (res.code === 200) { // 使用$set确保响应式更新 const newCount = (res.data || 0) + 1; this.$set(this.scoreDetail, "mistakeCount", newCount); // 再次强制更新,确保视图同步 this.$nextTick(() => { this.$forceUpdate(); }); } else { this.$message.error(res.message || "获取错误次数失败"); } } catch (error) { console.error("获取错误次数请求异常:", error); this.$message.error( `获取错误次数失败: ${error.message || "网络错误"}` ); } } else { this.$message.warning("请先填写日期、姓名错误类型"); } }, // 日期格式化辅助函数 formatDate(date) { const d = new Date(date); let month = "" + (d.getMonth() + 1); let day = "" + d.getDate(); const year = d.getFullYear(); if (month.length < 2) month = "0" + month; if (day.length < 2) day = "0" + day; return [year, month, day].join("-"); }, // 新增:打开选择执行扣分人对话框 openPersonSelectDialog() { this.personSearchModel = { name: "", pageNum: 1, pageSize: 10, }; this.searchPersons(); this.personSelectDialog.visible = true; }, // 新增:查询执行扣分人列表 async searchPersons() { try { const res = await getScorePersons({ ...this.personSearchModel, pageNum: this.personPageNum, pageSize: this.personPageSize, }); if (res.code === 200) { this.personList = res.data.rows || []; this.personTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询人员列表失败"); } } catch (error) { this.$message.error("查询人员列表请求异常"); } }, // 新增:选择执行扣分人 selectPerson(row) { this.scoreDetail.executor = row.name; this.personSelectDialog.visible = false; }, // 获取审核意见的验证规则 getAuditOpinionRules() { return this.currentAuditAction === "reject" ? { required: true, message: "请输入驳回意见", trigger: "blur" } : []; }, // 打开初审窗口 firstAudit(row) { this.scoreDetailDialog.title = "初审"; this.scoreDetailDialog.visible = true; this.scoreDetail = { ...row }; // 记录当前索引 this.currentAuditIndex = this.scoreDetailList.findIndex( (item) => item.id === row.id ); // 初始化当前审核人 this.currentAuditUser = this.currentUsername; this.currentAuditType = "first"; // 更新审核状态控制 this.auditStatusControl.firstAudit.status = this.scoreDetail.auditStatus; }, // 打开终审窗口 finalAudit(row) { // 允许对任何状态记录进行终审操作 this.scoreDetailDialog.title = "终审"; this.scoreDetailDialog.visible = true; this.scoreDetail = { ...row }; this.currentAuditIndex = this.scoreDetailList.findIndex( (item) => item.id === row.id ); this.currentAuditUser = this.currentUsername; this.currentAuditType = "final"; // 更新审核状态控制 this.auditStatusControl.finalAudit.status = this.scoreDetail.auditStatus; }, // 审核通过 async handleAuditApprove() { this.currentAuditAction = "approve"; // 设置当前操作为通过 this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; const now = new Date(); const auditTime = now.toISOString().slice(0, 19).replace("T", " "); if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 1; this.scoreDetail.firstAuditUser = this.currentUsername; this.scoreDetail.firstAuditTime = auditTime; this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.firstAudit.status = 1; // 更新状态为已初审 } else { this.scoreDetail.auditStatus = 3; this.scoreDetail.finalAuditUser = this.currentUsername; this.scoreDetail.finalAuditTime = auditTime; this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.finalAudit.status = 1; // 更新状态为已终审 } await this.saveAuditInfo(); this.$message.success(`${this.scoreDetailDialog.title}通过`); this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1); }); }, // 取消审核 async handleAuditCancel() { if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 0; this.scoreDetail.firstAuditOpinion = ""; this.scoreDetail.firstAuditTime = null; this.auditStatusControl.firstAudit.status = 0; // 更新状态为待审核 } else { // 终审取消,回到初审通过状态 this.scoreDetail.auditStatus = 1; this.scoreDetail.finalAuditOpinion = ""; this.scoreDetail.finalAuditTime = null; this.auditStatusControl.finalAudit.status = 0; // 更新状态为待终审 } await this.saveAuditInfo(); this.$message.success(`已取消${this.scoreDetailDialog.title}`); this.scoreDetailDialog.visible = false; }, // 审核驳回 async handleAuditReject() { this.currentAuditAction = "reject"; // 设置当前操作为驳回 this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; const now = new Date(); const auditTime = now.toISOString().slice(0, 19).replace("T", " "); if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 2; this.scoreDetail.firstAuditUser = this.currentUsername; this.scoreDetail.firstAuditTime = auditTime; this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.firstAudit.status = 2; // 更新状态为初审驳回 } else { this.scoreDetail.auditStatus = 4; this.scoreDetail.finalAuditUser = this.currentUsername; this.scoreDetail.finalAuditTime = auditTime; this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.finalAudit.status = 2; // 更新状态为终审驳回 } await this.saveAuditInfo(); this.$message.success(`${this.scoreDetailDialog.title}驳回`); this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1); }); }, // 保存审核信息到后端 async saveAuditInfo() { try { const res = await updateScoreDetail(this.scoreDetail); if (res.code === 200) { this.search(); // 刷新列表 } else { this.$message.error(res.message); } } catch (error) { this.$message.error("审核操作失败"); } }, // 自动跳转到下一个符合条件的记录 jumpToNextAuditRecord(targetStatus) { const nextIndex = this.scoreDetailList.findIndex( (item, index) => index > this.currentAuditIndex && item.auditStatus === targetStatus ); if (nextIndex > -1) { const nextRecord = this.scoreDetailList[nextIndex]; this.scoreDetailDialog.visible = false; // 根据目标状态决定打开初审还是终审 targetStatus === 0 ? this.firstAudit(nextRecord) : this.finalAudit(nextRecord); } else { this.scoreDetailDialog.visible = false; this.$message.info("已无符合条件的下一条记录"); } }, // 表格列格式化审核状态 formatAuditStatus(status) { return this.auditStatusMap[status] || "未知状态"; }, // 初始化组件时获取用户名 initUserInfo() { this.scoreDetail.name = this.currentUsername; this.scoreDetail.creator = this.currentUsername; }, // 分数详情列表查询 async search() { try { const res = await getScoreDetails(this.searchModel); if (res.code === 200) { this.scoreDetailList = res.data.rows || []; this.total = res.data.total || 0; } else { this.$message.error(res.message || "查询失败"); } } catch (error) { this.$message.error("请求异常"); } }, // 重置查询条件 resetValue() { this.searchModel = { name: "", category: "", auditStatus: "", // 重置为全部 pageNum: 1, pageSize: 10, }; this.search(); }, // 分页事件处理 handleSizeChange(size) { this.searchModel.pageSize = size; this.search(); }, handleCurrentChange(page) { this.searchModel.pageNum = page; this.search(); }, // 打开新增窗口(优化:确保表单完全重置) openAddWindow() { // 先销毁表单验证状态,再重置字段 if (this.$refs.scoreDetailForm) { this.$refs.scoreDetailForm.clearValidate(); // 清除所有验证状态 this.$refs.scoreDetailForm.resetFields(); // 重置字段值 } // 初始化新增表单数据 this.scoreType = "reward"; // 默认奖分 this.scoreDetail = { recordDate: new Date(), createDate: new Date(), name: this.currentUsername, creator: this.currentUsername, auditStatus: 0, category: "", score: "", avatar: "", }; this.scoreDetailDialog.title = "新增分数详情"; this.scoreDetailDialog.visible = true; }, // 编辑分数详情 handleEdit(row) { this.scoreDetail = { ...row }; // 设置奖惩类型 this.scoreType = row.originalScore || row.mistakeCount || row.redEnvelope ? "punish" : "reward"; // 日期格式转换 if (this.scoreDetail.recordDate) { this.scoreDetail.recordDate = new Date(this.scoreDetail.recordDate); } if (this.scoreDetail.createDate) { this.scoreDetail.createDate = new Date(this.scoreDetail.createDate); } this.scoreDetailDialog.title = "编辑分数详情"; this.scoreDetailDialog.visible = true; }, // 打开积分标准选择对话框 openStandardDialog() { this.standardSearchModel = { category: "", standard: "", pageNum: 1, pageSize: 10, }; this.searchStandards(); this.standardSelectDialog.visible = true; }, // 查询积分标准 async searchStandards() { try { const res = await getScoreStandards({ ...this.standardSearchModel, pageNum: this.standardPageNum, pageSize: this.standardPageSize, }); if (res.code === 200) { this.standardList = res.data.rows || []; this.standardTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询积分标准失败"); } } catch (error) { this.$message.error("查询积分标准请求异常"); } }, // 积分标准分页大小变更 handleStandardSizeChange(size) { this.standardPageSize = size; this.standardSearchModel.pageSize = size; this.searchStandards(); }, // 积分标准分页页码变更 handleStandardCurrentChange(page) { this.standardPageNum = page; this.standardSearchModel.pageNum = page; this.searchStandards(); }, // 选择积分标准 selectStandard(row) { // 给事项类别、积分标准、奖惩积分赋值 this.scoreDetail.category = row.category; this.scoreDetail.standard = row.standard; this.scoreDetail.score = row.score; // 手动触发表单验证 if (this.$refs.scoreDetailForm) { this.$refs.scoreDetailForm.validateField(["category", "score"]); } // 关闭选择窗口 this.standardSelectDialog.visible = false; }, // 保存分数详情 async onConfirm() { this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; try { const res = this.scoreDetail.id ? await updateScoreDetail(this.scoreDetail) : await addScoreDetail(this.scoreDetail); if (res.code === 200) { this.$message.success(res.message); this.scoreDetailDialog.visible = false; this.search(); } else { this.$message.error(res.message); } } catch (error) { this.$message.error("操作失败"); } }); }, // 删除分数详情 async handleDelete(row) { try { await this.$confirm("确定删除该分数详情?", "提示", { type: "warning", }); const res = await deleteScoreDetail(row.id); if (res.code === 200) { this.$message.success(res.message); this.search(); } else { this.$message.error(res.message); } } catch (error) { // 取消删除 } }, // 导出Excel async exportExcel() { try { const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_API, headers: { token: getToken() }, responseType: "blob", }); const response = await instance.get( "/api/scoreDetail/export/easyExcel", { params: { name: this.searchModel.name, }, } ); const fileName = response.headers["content-disposition"] ? decodeURIComponent( response.headers["content-disposition"].split("filename=")[1] ).replace(/"/g, "") : "scoreDetails_export.xls"; const link = document.createElement("a"); link.href = URL.createObjectURL(new Blob([response.data])); link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); } catch (error) { this.handleExportError(error); } }, // 导入Excel importExcel(response) { if (response.code === 200) { this.search(); this.$message.success("导入成功"); } else { this.$message.error(response.message || "导入失败"); } }, // 导入文件校验 beforeUpload(file) { const allowedTypes = [ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel", ]; if (!allowedTypes.includes(file.type)) { this.$message.error("仅支持 .xls 或 .xlsx 格式文件"); return false; } return true; }, // 导出错误处理 handleExportError(error) { if (error.response?.status === 401) { this.$message.error("登录已过期,请重新登录"); this.$store.dispatch("user/logout"); this.$router.push("/login"); } else if (error.response?.data instanceof Blob) { const reader = new FileReader(); reader.onload = () => { try { const errorData = JSON.parse(reader.result); this.$message.error(errorData.message || "导出失败"); } catch { this.$message.error("导出文件解析失败"); } }; reader.readAsText(error.response.data); } else { this.$message.error(error.message || "导出失败"); } }, // 图像上传相关方法 // 获取图像完整URL getAvatarUrl(avatar) { if (!avatar) return ""; const timestamp = Date.now(); return `${this.uploadAvatarFileUrl}${encodeURIComponent(avatar)}?token=${ this.currentToken }&t=${timestamp}`; }, // 处理图片加载错误 handleImageError(row) { console.error("图片加载失败:", row.avatar); // 简单的错误处理 this.$set(row, "avatar", ""); }, // 表单图片加载错误处理 handleFormImageError() { console.error("表单图片加载失败"); this.scoreDetail.avatar = ""; }, // 图像上传前验证 beforeAvatarUpload(file, row) { const isImage = file.type.startsWith("image/"); const isLt5M = file.size / 1024 / 1024 < 5; if (!isImage) { this.$message.error("只能上传图片文件!"); } if (!isLt5M) { this.$message.error("图像图片大小不能超过5MB!"); } return isImage && isLt5M; }, // 处理表单中的图像上传成功 handleFormAvatarUpload(response) { if (response.code === 200) { this.scoreDetail.avatar = response.data; this.$message.success("图像上传成功"); } else { this.$message.error(response.message || "图像上传失败"); } }, // 处理行内图像上传成功 handleRowAvatarUpload(row, response) { if (response.code === 200) { // 更新当前行的图像信息 this.$set(row, "avatar", response.data); this.$message.success("图像上传成功"); // 同步更新到后端 const updateData = { id: row.id, avatar: response.data, }; updateScoreDetail(updateData) .then((res) => { if (res.code !== 200) { this.$message.warning("图像信息同步失败"); } }) .catch(() => { this.$message.warning("图像信息同步失败"); }); } else { this.$message.error(response.message || "图像上传失败"); } }, // 处理图像上传失败 handleAvatarUploadError(row, error) { console.error("图像上传失败:", error); this.$message.error("图像上传失败,请重试"); }, }, created() { this.search(); }, mounted() { this.uploadHeaders.token = getToken(); this.initUserInfo(); this.$nextTick(() => { this.tableHeight = window.innerHeight - 220; this.standardTableHeight = window.innerHeight - 300; }); }, }; </script> <style> /* 全局单元格样式 */ .el-table-ellipsis .cell { white-space: nowrap !important; /* 强制不换行 */ overflow: hidden !important; text-overflow: ellipsis !important; } /* 禁用操作列的省略号效果 */ .el-table-ellipsis .no-ellipsis .cell { white-space: normal !important; /* 恢复默认换行 */ overflow: visible !important; text-overflow: clip !important; } /* 可选:表头对齐方式 */ .el-table-ellipsis th > .cell { display: block; /* 解决表头与内容列宽度轻微错位问题 */ } .dialog-footer { display: flex; justify-content: flex-end; gap: 10px; } /* 图片错误占位样式 */ .image-error { width: 100%; height: 100%; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #ccc; } .image-error::after { content: "图片错误"; font-size: 12px; } /* 新增:合计行样式 */ .el-table .el-table__footer .cell { font-weight: bold; color: #409EFF; } </style> 报错: vue.runtime.esm.js:620 [Vue warn]: Property or method "showSettings1" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. found in ---> <Layout> at src/layout/index.vue <App> at src/App.vue <Root> 6 vue.runtime.esm.js:620 [Vue warn]: Property or method "formatNumber" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. found in ---> <ScoreTotal> at src/views/company/hr/scoreTotal.vue <AppMain> at src/layout/components/AppMain.vue <Layout> at src/layout/index.vue <App> at src/App.vue <Root> 合计数没有展示出来,优化代码
08-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LogicYarn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值