EditNode.vue 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083
  1. <template>
  2. <div>
  3. <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="800px"
  4. :before-close="dialogBeforeClose">
  5. <div class="status-btn-box fadeInDown animated">
  6. <el-link type="primary" @click="detailsDialogVisible = true">查看处理详情</el-link>
  7. </div>
  8. <div class="dialog-content" v-loading="loading">
  9. <div class="dialog-content-left scroll-bar"
  10. style="padding-bottom: 50px; min-height: 500px; overflow-y: auto;">
  11. <!-- 附加属性 -->
  12. <div class="append-properties" v-if="dialogData.expand && dialogData.expand.length > 0">
  13. <div class="tips">
  14. 自定义属性(只有通过excel上传发布的指标才有)
  15. </div>
  16. <table>
  17. <tr>
  18. <td v-for="item in dialogData.expand" :key="item.p">
  19. <el-tooltip class="item" effect="dark" placement="top">
  20. <div v-html="item.k" slot="content" style="max-width: 300px;"></div>
  21. <div class="oneLine">{{ item.k }}</div>
  22. </el-tooltip>
  23. </td>
  24. </tr>
  25. <tr>
  26. <td v-for="item in dialogData.expand" :key="item.p">
  27. <el-tooltip class="item" effect="dark" placement="top">
  28. <div v-html="item.v" slot="content" style="max-width: 300px;"></div>
  29. <div class="oneLine">{{ item.v }}</div>
  30. </el-tooltip>
  31. </td>
  32. </tr>
  33. </table>
  34. </div>
  35. <!-- 附加属性 -->
  36. <!-- 考核基本信息 -->
  37. <div class="base-info-box">
  38. <div class="label">
  39. <i class="el-icon-user"></i>
  40. 被考核人
  41. </div>
  42. <div class="value">
  43. {{ dialogData.task && dialogData.task.assigneeName || '--' }}
  44. </div>
  45. </div>
  46. <div class="base-info-box">
  47. <div class="label">
  48. <i class="el-icon-eleme"></i>
  49. 考核名称
  50. </div>
  51. <div class="value">
  52. {{ dialogData.reviewTitle || '--' }}
  53. </div>
  54. </div>
  55. <div class="base-info-box">
  56. <div class="label">
  57. <i class="el-icon-eleme"></i>
  58. 考核状态
  59. </div>
  60. <div class="value">
  61. <div class="status-btn" :class="reviewStatus == 0 ? 'green-color' : 'gray-color'">
  62. {{ reviewStatus == 0 ? '进行中' : '已结束' }}
  63. </div>
  64. </div>
  65. </div>
  66. <div class="base-info-box">
  67. <div class="label">
  68. <i class="el-icon-date"></i>
  69. 周期类型
  70. </div>
  71. <div class="value">
  72. <el-tag size="small">{{ dialogData.cycleType | formatCycleType }}</el-tag>
  73. </div>
  74. </div>
  75. <div class="base-info-box">
  76. <div class="label">
  77. <i class="el-icon-time"></i>
  78. 考核周期
  79. </div>
  80. <div class="value">
  81. {{ dialogData.startTime | formatDate }} 至 {{ dialogData.endTime | formatDate }}
  82. </div>
  83. </div>
  84. <div class="base-info-box">
  85. <div class="label">
  86. <i class="el-icon-thumb"></i>
  87. 指标
  88. </div>
  89. <div class="value">
  90. {{ dialogData.title || '--' }}
  91. </div>
  92. </div>
  93. <div class="base-info-box">
  94. <div class="label">
  95. <i class="el-icon-aim"></i>
  96. 目标
  97. </div>
  98. <div class="value">
  99. {{ dialogData.target || '--' }}
  100. </div>
  101. </div>
  102. <div class="base-info-box">
  103. <div class="label">
  104. <i class="el-icon-s-check"></i>
  105. 单位
  106. </div>
  107. <div class="value">
  108. {{ dialogData.unit || '--' }}
  109. </div>
  110. </div>
  111. <div class="base-info-box">
  112. <div class="label">
  113. <i class="el-icon-pie-chart"></i>
  114. 权重(%)
  115. </div>
  116. <div class="value">
  117. {{ dialogData.weight || '--' }}
  118. </div>
  119. </div>
  120. <div class="base-info-box">
  121. <div class="label">
  122. <i class="el-icon-s-flag"></i>
  123. 结果值
  124. </div>
  125. <div class="value">
  126. {{ dialogData.result || '--' }}
  127. </div>
  128. </div>
  129. <div class="base-info-box">
  130. <div class="label">
  131. <i class="el-icon-document-checked"></i>
  132. 最终评分
  133. </div>
  134. <div class="value">
  135. {{ dialogData.score || '--' }}
  136. </div>
  137. </div>
  138. <div class="base-info-box">
  139. <div class="label">
  140. <i class="el-icon-document"></i>
  141. 规则
  142. </div>
  143. <div v-if="dialogData.content" class="value text-style" v-html="dialogData.content">
  144. </div>
  145. <div v-else>
  146. --
  147. </div>
  148. </div>
  149. <!-- 考核基本信息 -->
  150. </div>
  151. <!-- 录入信息 -->
  152. <div class="dialog-content-right">
  153. <div class="data-box" v-if="activeName == 1">
  154. <el-input v-model="formData && formData.title" placeholder="指标标题" style="width: 300px;"
  155. clearable @focus="handleFocus" @blur="handleEdit('title')" :disabled="!isEdit || loading">
  156. <template slot="prepend">指标</template>
  157. </el-input>
  158. </div>
  159. <div class="data-box" v-if="activeName == 1" style="height: 120px;">
  160. <el-input type="textarea" v-model="formData && formData.content" placeholder="规则说明"
  161. style="width: 300px;" rows="4" cols="4" clearable @focus="handleFocus"
  162. @blur="handleEdit('content')" :disabled="!isEdit || loading">
  163. <template slot="prepend">规则</template>
  164. </el-input>
  165. </div>
  166. <div class="data-box" v-if="activeName == 1">
  167. <el-input placeholder="目标值(数字)" @input="handleInputTarget" v-model="formData && formData.target"
  168. style="width: 300px;" :disabled="!isEdit || loading" clearable @focus="handleFocus"
  169. @blur="handleEdit('target')">
  170. <template slot="prepend">目标值</template>
  171. </el-input>
  172. </div>
  173. <!-- 单位编辑 -->
  174. <div class="data-box" v-if="activeName == 1">
  175. <el-input placeholder="单位" v-model="formData && formData.unit" style="width: 300px;" clearable
  176. @focus="handleFocus" @blur="handleEdit('unit')" :disabled="!isEdit || loading">
  177. <template slot="prepend">单位</template>
  178. </el-input>
  179. </div>
  180. <div class="data-box" v-if="activeName == 2">
  181. <el-input placeholder="结果值" v-model="formData && formData.result" style="width: 300px;"
  182. @focus="handleFocus" @blur="handleEdit('result')" clearable :disabled="loading">
  183. <template slot="prepend">结果值</template>
  184. </el-input>
  185. </div>
  186. <!-- 自评 -->
  187. <div class="data-box" v-if="activeName == 3">
  188. <el-input :controls="false" placeholder="评分(数字)" @input="handleInputScore"
  189. v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
  190. @focus="handleFocus" @blur="handleEdit('scoreSelf')" :disabled="loading">
  191. <template slot="prepend">评分</template>
  192. </el-input>
  193. </div>
  194. <!-- 互评 -->
  195. <div class="data-box" v-if="activeName == 4">
  196. <el-input placeholder="评分(数字)" @input="handleInputScore"
  197. v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
  198. @focus="handleFocus" @blur="handleEdit('scoreEachOther')" :disabled="loading">
  199. <template slot="prepend">评分</template>
  200. </el-input>
  201. </div>
  202. <!-- 评分 -->
  203. <div class="data-box" v-if="activeName == 5">
  204. <div v-if="dialogData && dialogData.scoreExpression" class="flex-box-ce"
  205. style="margin-bottom: 10px; ">
  206. <div class="flex-box-ce">系统评分: {{ dialogData.scoreExpression }} </div>
  207. <el-link type="primary" :disabled="loading" style="margin-left: 20px;"
  208. @click="handleScore()">立即评分</el-link>
  209. </div>
  210. <el-input placeholder="评分(数字)" @input="handleInputScore"
  211. v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
  212. @focus="handleFocus" @blur="handleEdit('score')" :disabled="loading">
  213. <template slot="prepend">评分</template>
  214. </el-input>
  215. </div>
  216. <!-- 审批 -->
  217. <div class="data-box" v-if="activeName == 6">
  218. <el-input disabled placeholder="最终分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
  219. v-model="formData && formData.score" style="width: 300px;" clearable>
  220. <template slot="prepend">最终分</template>
  221. </el-input>
  222. </div>
  223. <div class="data-box" style="height: 160px;">
  224. <el-input type="textarea" v-model="comment" placeholder="请输入意见" style="width: 300px;" clearable
  225. rows="5" cols="3" @focus="handleFocus" @blur="confirmCommentDialog(1)"
  226. :disabled="loading"></el-input>
  227. </div>
  228. <uploadOss v-if="activeName > 1 && activeName <= 6" :key="Date.now()" class="avatar-uploader"
  229. :headers="$xtoken" :show-file-list="true" :multiple="true" :limit="5" :accept="acceptFile"
  230. :file-list="fileList" :action="$action" :on-preview="onFilePreView" :on-success="handleSuccess"
  231. :on-remove="handleRemove" :before-upload="beforeFilesUpload">
  232. <el-button class="primaryBtn" icon="el-icon-paperclip" plain size="mini">上传附件</el-button>
  233. <!-- <div slot="tip" class="el-upload__tip">(支持上传xlsx,xls,doc,docx,pdf,txt,png,jpeg,jpg,gif, 大小不能超过5M)</div> -->
  234. </uploadOss>
  235. <el-button v-if="activeName > 1 && activeName <= 6" class="primaryBtn" icon="el-icon-check"
  236. type="primary" size="mini" @click="comfirmUploadFiles()"
  237. style="margin-top: 10px;">提交附件</el-button>
  238. </div>
  239. <!-- 录入信息 -->
  240. </div>
  241. <div slot="footer">
  242. <el-button v-if="isCreator || getRole1 || isTransfer" @click="showEmployeeSelector = true">转 交</el-button>
  243. <el-button v-if="activeName == 6" type="danger" @click="reset" :disabled="isInputFocused">驳 回</el-button>
  244. <el-button type="primary" @click="confirmCommentDialog(2)" :disabled="isInputFocused">提 交</el-button>
  245. </div>
  246. </el-dialog>
  247. <!-- 输入意见 -->
  248. <el-dialog title="意见" :visible.sync="commentDialog" width="500px" :before-close="beforCommentDialogClose">
  249. <div>
  250. <el-input type="textarea" v-model="comment" placeholder="请输入意见" rows="5" cols="5"></el-input>
  251. </div>
  252. <div slot="footer">
  253. <el-button @click="commentDialog = false">取 消</el-button>
  254. <el-button type="primary" @click="confirmCommentDialog">确 定</el-button>
  255. </div>
  256. </el-dialog>
  257. <!-- 各节点处理详情 -->
  258. <DetailsDialog v-model="detailsDialogVisible" :dialog-data="dialogData" />
  259. <!-- 图片查看 -->
  260. <el-dialog title="图片查看" :visible.sync="isShowImg" width="50%">
  261. <div class="flex-box-ce flex-center-center" style="width: 100%; height: 100%;">
  262. <div style="width: 500px;">
  263. <img :src="imgUrl" style="width: 100%;" />
  264. </div>
  265. </div>
  266. <span slot="footer" class="dialog-footer">
  267. <el-button @click="closePreview">关闭</el-button>
  268. </span>
  269. </el-dialog>
  270. <!-- 图片查看 -->
  271. <!-- 员工选择组件 -->
  272. <EmployeeSelector :multi="false" :is_filtration_creator="false" :selected="employeeSelectedObj"
  273. :isChecKedAll="false" :visible.sync="showEmployeeSelector" @confirm="onEmployeeSelected" />
  274. </div>
  275. </template>
  276. <script>
  277. import moment from 'moment';
  278. import { mapGetters } from 'vuex';
  279. import cloneDeep from 'lodash.clonedeep';
  280. import DetailsDialog from "./EditNodeDialog.vue"
  281. import uploadOss from '@/components/upload';
  282. import { _debounce } from '@/utils/auth';
  283. import EmployeeSelector from '@/components/EmployeeSelector';
  284. export default {
  285. components: {
  286. DetailsDialog,
  287. uploadOss,
  288. EmployeeSelector
  289. },
  290. model: {
  291. prop: 'dialogVisible',
  292. event: 'close-dialog'
  293. },
  294. props: {
  295. activeName: {
  296. type: String,
  297. default: "1"
  298. },
  299. nodeType: {
  300. type: String,
  301. default: ""
  302. },
  303. dialogTitle: {
  304. type: String,
  305. default: ""
  306. },
  307. dialogVisible: {
  308. type: Boolean,
  309. default: false
  310. },
  311. dialogData: {
  312. type: Object,
  313. default: () => {}
  314. }
  315. },
  316. watch: {
  317. dialogData(v) {
  318. if (this.dialogData) {
  319. this.loading = false;
  320. this.formData = cloneDeep(this.dialogData);
  321. this.comment = this.formData.task.comment || '';
  322. }
  323. }
  324. },
  325. filters: {
  326. formatCycleType(val) {
  327. if (val == 0) return '未定义'
  328. if (val == 1) return '年度'
  329. if (val == 2) return '半年'
  330. if (val == 3) return '季度'
  331. if (val == 4) return '月度'
  332. else return '--'
  333. },
  334. formatDate(val) {
  335. if (val) return moment(val).format('YYYY-MM-DD')
  336. else return "--"
  337. }
  338. },
  339. data() {
  340. return {
  341. isCreator: this.$supremeAuthority('creator'), // 创始人,总经理
  342. getRole1: this.$getRole(1), // 绩效主管理员,子管理员
  343. loading: false,
  344. isInputFocused: false,
  345. commentDialog: false,
  346. currentInput: null, // 当前操作的 el-input 的标识
  347. comment: "",
  348. score: 0,
  349. reject: false,
  350. formData: null, // 用于提交的数据,指标,目标,规则,
  351. baseInfo: null, // 用于展示的数据,指标,目标,规则,权重,标题
  352. detailsDialogVisible: false,
  353. acceptFile: '.jpg,.jpeg,.png,.gif,.bmp,.pdf,.JPG,.JPEG,.PBG,.GIF,.BMP,.PDF',
  354. fileList: [], // 附件列表
  355. uploadFileList: [], // 上传成功的fileList
  356. isShowImg: false, // 图片预览弹框
  357. imgUrl: '', // 预览图片地址
  358. isUploadFile: true,
  359. showEmployeeSelector: false,
  360. employeeSelectedObj: {
  361. employee: [],
  362. dept: []
  363. },
  364. }
  365. },
  366. computed: {
  367. ...mapGetters(['user_info']),
  368. // 确认目标是否可以编辑
  369. isEdit() {
  370. let children = [], employeeIds = [];
  371. let user_id = this.user_info.id.toString() // 当前用户ID
  372. const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
  373. if (nodes && nodes.length > 0)
  374. children = nodes.find(node => node.type === 'targetConfirms').children
  375. children.forEach(child => {
  376. // 是否允许编辑
  377. if (child.allows.includes('edit')) {
  378. if (['leader', 'deptLeader', 'post', 'user'].includes(child.assigneeType)) {
  379. if (child.tasks && child.tasks.length > 0) {
  380. child.tasks.forEach(task => {
  381. employeeIds.push(task.assignee)
  382. })
  383. }
  384. }
  385. else {
  386. employeeIds = [user_id]
  387. }
  388. }
  389. })
  390. return employeeIds && employeeIds.length > 0 && employeeIds.includes(user_id) ? true : false
  391. },
  392. // 判断当前任务是否可以转交
  393. isTransfer() {
  394. let children = [], employeeIds = [], currentNode = null;
  395. let user_id = this.user_info.id.toString() // 当前用户ID
  396. const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
  397. if (nodes && nodes.length > 0) {
  398. currentNode = nodes.find(node => node.type === this.nodeType)
  399. // 有子节点
  400. if (currentNode && currentNode.children && currentNode.children.length > 0) {
  401. children = currentNode.children
  402. children.forEach(child => {
  403. // 是否允许转交
  404. if (child.allows.includes('transfer')) {
  405. if (['leader', 'deptLeader', 'post', 'user'].includes(child.assigneeType)) {
  406. if (child.tasks && child.tasks.length > 0) {
  407. child.tasks.forEach(task => {
  408. employeeIds.push(task.assignee)
  409. })
  410. }
  411. }
  412. else {
  413. employeeIds = [user_id]
  414. }
  415. }
  416. })
  417. // 无子节点
  418. } else {
  419. if (currentNode.allows.includes('transfer')) {
  420. if (['leader', 'deptLeader', 'post', 'user'].includes(currentNode.assigneeType)) {
  421. if (currentNode.tasks && currentNode.tasks.length > 0) {
  422. currentNode.tasks.forEach(task => {
  423. employeeIds.push(task.assignee)
  424. })
  425. }
  426. }
  427. else {
  428. employeeIds = [user_id]
  429. }
  430. }
  431. }
  432. }
  433. return employeeIds && employeeIds.length > 0 && employeeIds.includes(user_id) ? true : false
  434. },
  435. // 整个审批的状态 0,进行中 1,已完成
  436. reviewStatus() {
  437. return this.dialogData && this.dialogData.reviewStatus
  438. },
  439. // 自评节点
  440. scoreSelf() {
  441. // 通过解构赋予默认值
  442. const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
  443. if (nodes.length > 0) {
  444. return this.dialogData.flow.nodes.find(node => node.type == 'scoreSelf')
  445. }
  446. },
  447. // 互评节点
  448. scoreEachOther() {
  449. // 通过解构赋予默认值
  450. const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
  451. if (nodes.length > 0) {
  452. return this.dialogData.flow.nodes.find(node => node.type == 'scoreEachOther')
  453. }
  454. },
  455. // 评分节点
  456. scoreNode() {
  457. // 通过解构赋予默认值
  458. const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
  459. if (nodes.length > 0) {
  460. return this.dialogData.flow.nodes.find(node => node.type == 'score')
  461. }
  462. },
  463. },
  464. mounted() {
  465. if (this.dialogData) {
  466. this.formData = cloneDeep(this.dialogData);
  467. this.comment = this.formData && this.formData.task && this.formData.task.comment ? this.formData.task.comment : ''
  468. }
  469. // 监听全局 focus 和 blur 事件
  470. document.addEventListener('focusin', this.handleGlobalFocus);
  471. document.addEventListener('focusout', this.handleGlobalBlur);
  472. },
  473. beforeDestroy() {
  474. // 移除全局事件监听器
  475. document.removeEventListener('focusin', this.handleGlobalFocus);
  476. document.removeEventListener('focusout', this.handleGlobalBlur);
  477. },
  478. methods: {
  479. closeEmployeeSelector() {
  480. this.showEmployeeSelector = false;
  481. },
  482. onEmployeeSelected(params) {
  483. console.log(params);
  484. this.employeeSelectedObj = params;
  485. this.$confirm('确定将当前任务转交给《' + params.employee[0].name + '》吗?', {
  486. confirmButtonText: '确定',
  487. cancelButtonText: '取消',
  488. type: 'warning'
  489. }).then(() => {
  490. let url = `/performance/review/job/forward/${this.user_info.site_id}/${this.dialogData.reviewIndicatorId}`
  491. let { taskId } = this.dialogData.task
  492. let data = {
  493. taskId,
  494. employeeId: params.employee[0].id
  495. }
  496. this.loading = true;
  497. this.$http.post(url, data).then(res => {
  498. let { data, code, message } = res
  499. if (code == 1) {
  500. this.$message.success('操作成功,已将当前任务转交给《' + params.employee[0].name + '》');
  501. this.$emit('handleEditSuccess', data)
  502. this.closeEmployeeSelector();
  503. }
  504. else this.$message.error(message || '操作失败');
  505. this.loading = false;
  506. })
  507. }).catch(() => { });
  508. },
  509. handleInputTarget(value) {
  510. // 使用正则表达式限制输入
  511. const regex = /^([0-9])*$/; // 匹配非负整数
  512. // 如果输入值不符合正则表达式,则恢复为之前的值
  513. if (!regex.test(value)) {
  514. value = ''; // 重置输入框的值
  515. this.formData.task.score = 0
  516. } else {
  517. // 如果输入值符合正则表达式,更新绑定的值
  518. this.formData.target = Number(value);
  519. }
  520. },
  521. handleInputScore(value) {
  522. // 使用正则表达式限制输入
  523. const regex = /^([0-9])*$/; // 匹配非负整数
  524. // 如果输入值不符合正则表达式,则恢复为之前的值
  525. if (!regex.test(value)) {
  526. value = ''; // 重置输入框的值
  527. if (this.formData && this.formData.task && this.formData.task.score)
  528. this.formData.task.score = 0
  529. } else {
  530. // 如果输入值符合正则表达式,更新绑定的值
  531. if (this.formData && this.formData.task && this.formData.task.score)
  532. this.formData.task.score = Number(value);
  533. }
  534. },
  535. // 录入系统评分
  536. handleScore() {
  537. this.formData.task.score = this.dialogData.scoreExpression
  538. },
  539. dialogBeforeClose() {
  540. this.formData = null
  541. this.imgUrl = "";
  542. this.isShowImg = false;
  543. this.comment = '';
  544. this.$emit('close-dialog', false)
  545. },
  546. beforCommentDialogClose() {
  547. this.commentDialog = false;
  548. },
  549. handleFocus() {
  550. // 当 el-input 获得焦点时,设置标志变量为 true
  551. this.isInputFocused = true;
  552. },
  553. handleBlur() {
  554. // 当 el-input 失去焦点时,设置标志变量为 false
  555. this.isInputFocused = false;
  556. },
  557. handleGlobalFocus(event) {
  558. // 检查触发事件的元素是否是 input
  559. if (event.target.tagName.toLowerCase() === 'input') {
  560. this.isInputFocused = true;
  561. console.log('全局:有 input 获得焦点');
  562. }
  563. },
  564. handleGlobalBlur(event) {
  565. // 检查触发事件的元素是否是 input
  566. if (event.target.tagName.toLowerCase() === 'input') {
  567. this.isInputFocused = false;
  568. console.log('全局:input 失去焦点');
  569. }
  570. },
  571. // 编辑节点
  572. handleEdit: _debounce(function(type) {
  573. this.loading = true;
  574. let { reviewIndicatorId } = this.dialogData
  575. let { taskId, nodeType } = this.dialogData.task
  576. let data = { taskId }
  577. if (type === 'scoreSelf' || type === 'scoreEachOther' || type === 'score') {
  578. data['score'] = this.formData.task['score'];
  579. }
  580. else data[type] = this.formData[type];
  581. let url;
  582. switch (nodeType) {
  583. // 确认目标
  584. case 'targetConfirm':
  585. url = `/performance/review/target/confirm/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
  586. break;
  587. // 录入结果值
  588. case 'resultInput':
  589. url = `/performance/review/result/Input/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
  590. break;
  591. // 自评
  592. case 'scoreSelf':
  593. url = `/performance/review/score/self/score/${this.user_info.site_id}/${reviewIndicatorId}`
  594. break;
  595. // 互评
  596. case 'scoreEachOther':
  597. url = `/performance/review/score/each/other/score/${this.user_info.site_id}/${reviewIndicatorId}`
  598. break;
  599. // 评分
  600. case 'score':
  601. url = `/performance/review/score/score/${this.user_info.site_id}/${reviewIndicatorId}`
  602. break;
  603. default:
  604. break;
  605. }
  606. if (!url) return
  607. this.$http.post(url, data).then(res => {
  608. if (res.code == 1) {
  609. let { data } = res
  610. this.$emit('handleEditSuccess', data)
  611. }
  612. else this.$message.error(res.message || '操作失败');
  613. this.loading = false;
  614. this.handleBlur();
  615. })
  616. }, 100),
  617. confirmCommentDialog(type) {
  618. // 阻止事件冒泡,避免触发全局点击事件
  619. // event.stopPropagation();
  620. if(this.loading) return
  621. let { reviewIndicatorId } = this.dialogData;
  622. let { taskId, nodeType } = this.dialogData.task
  623. let url;
  624. switch (nodeType) {
  625. case 'targetConfirm':
  626. url = `/performance/review/target/confirm/complete/${this.user_info.site_id}/${reviewIndicatorId}`
  627. break;
  628. case 'resultInput':
  629. url = `/performance/review/result/Input/${this.user_info.site_id}/${reviewIndicatorId}`
  630. break;
  631. // 自评
  632. case 'scoreSelf':
  633. url = `/performance/review/score/self/${this.user_info.site_id}/${reviewIndicatorId}`
  634. break;
  635. // 互评
  636. case 'scoreEachOther':
  637. url = `/performance/review/score/each/other/${this.user_info.site_id}/${reviewIndicatorId}`
  638. break;
  639. // 评分
  640. case 'score':
  641. url = `/performance/review/score/${this.user_info.site_id}/${reviewIndicatorId}`
  642. break;
  643. case 'review':
  644. if (this.reject) url = `/performance/review/review/refuse/${this.user_info.site_id}/${reviewIndicatorId}`
  645. else url = `/performance/review/review/pass/${this.user_info.site_id}/${reviewIndicatorId}`
  646. break;
  647. default:
  648. break;
  649. }
  650. let data = {
  651. taskId,
  652. comment: this.comment,
  653. complete: type == 1 ? false : true // false 暂存,true 提交
  654. }
  655. // 提交时,提醒用户要提交附件
  656. if (type == 2) {
  657. if (!this.isUploadFile) {
  658. return this.$message.warning("上传了附件,记得提交哦")
  659. }
  660. }
  661. this.$http.post(url, data).then(res => {
  662. if (type == 1) {
  663. if (res.code == 1) { }
  664. else this.$message.error(res.message || '操作失败');
  665. } else {
  666. if (res.code == 1) {
  667. this.$message.success("操作成功");
  668. this.$emit("handleSuccess")
  669. this.$emit('close-dialog', false);
  670. this.formData = null;
  671. this.commentDialog = false;
  672. this.comment = "";
  673. this.reject = false; // 驳回标识
  674. this.uploadFileList = [];
  675. this.fileList = [];
  676. } else {
  677. this.$message.error(res.message || '操作失败');
  678. }
  679. }
  680. this.handleBlur();
  681. })
  682. },
  683. onFilePreView(file) {
  684. let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
  685. let lastIndex = file.url && file.url.lastIndexOf("/") || -1
  686. let suffix; //文件后缀名
  687. if (lastIndex > 0) {
  688. suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
  689. if (imgFiles.includes(suffix)) {
  690. this.imgUrl = file.url;
  691. this.isShowImg = true;
  692. } else {
  693. window.open(file.url, '_blank');
  694. }
  695. }
  696. },
  697. handleSuccess: _debounce(function (response, file, fileList) {
  698. this.uploadFileList = fileList.map(item => {
  699. return item.url;
  700. });
  701. this.isUploadFile = false;
  702. }),
  703. handleRemove(file, fileList) {
  704. setTimeout(() => {
  705. this.fileList = fileList;
  706. if (!(this.fileList && this.fileList.length > 0)) {
  707. this.isUploadFile = true;
  708. }
  709. }, 500);
  710. },
  711. beforeFilesUpload(file) {
  712. const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
  713. const isLt2M = file.size / 1024 / 1024 < 5;
  714. let len = file.name.split('.').length - 1;
  715. const $ext_name = file.name.split('.')[len];
  716. let isFile = $ext_list.indexOf($ext_name) != -1;
  717. if (!isLt2M) {
  718. this.$message.error('文件大小不能超过 5MB!');
  719. }
  720. if (!isFile) {
  721. this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
  722. }
  723. return isFile && isLt2M;
  724. },
  725. comfirmUploadFiles() {
  726. this.isUploadFile = false;
  727. let { reviewIndicatorId } = this.dialogData
  728. let { taskId, nodeType } = this.dialogData.task
  729. let url;
  730. switch (nodeType) {
  731. // 结果值附件录入
  732. case 'resultInput':
  733. url = `/performance/review/result/Input/result/file/${this.user_info.site_id}/${reviewIndicatorId}`
  734. break;
  735. // 自评附件录入
  736. case 'scoreSelf':
  737. url = `/performance/review/score/self/files/${this.user_info.site_id}/${reviewIndicatorId}`
  738. break;
  739. // 互评附件录入
  740. case 'scoreEachOther':
  741. url = `/performance/review/score/each/other/files/${this.user_info.site_id}/${reviewIndicatorId}`
  742. break;
  743. // 评分附件录入
  744. case 'score':
  745. url = `/performance/review/score/files/${this.user_info.site_id}/${reviewIndicatorId}`
  746. break;
  747. // 审批附件录入
  748. case 'review':
  749. url = `/performance/review/review/files/${this.user_info.site_id}/${reviewIndicatorId}`
  750. break;
  751. default:
  752. break;
  753. }
  754. let data = {
  755. taskId: taskId,
  756. files: this.uploadFileList
  757. }
  758. this.$http.post(url, data).then(res => {
  759. this.isUploadFile = true;
  760. let { code } = res
  761. if (code == 1) return this.$message.success("上传附件成功")
  762. else return this.$message.success("上传附件失败")
  763. })
  764. },
  765. closePreview() {
  766. this.imgUrl = "";
  767. this.isShowImg = false;
  768. },
  769. // 驳回
  770. reset() {
  771. this.reject = true;
  772. this.commentDialog = true;
  773. },
  774. // 提交
  775. confirm() {
  776. this.commentDialog = true;
  777. },
  778. }
  779. }
  780. </script>
  781. <style lang="scss">
  782. .dialog-content {
  783. .el-descriptions-item__label {
  784. width: 120px;
  785. }
  786. }
  787. .oneLine {
  788. overflow: hidden;
  789. white-space: nowrap;
  790. text-overflow: ellipsis;
  791. }
  792. </style>
  793. <style scoped="scoped" lang="scss">
  794. .status-box {
  795. padding: 2px;
  796. display: inline-block;
  797. border: 1px solid;
  798. box-sizing: border-box;
  799. border-radius: 4px;
  800. }
  801. .green-color {
  802. border-color: #67C23A;
  803. color: #67C23A;
  804. }
  805. .gray-color {
  806. border-color: #f56c6c;
  807. color: #f56c6c;
  808. }
  809. .status-btn-box {
  810. width: 120px;
  811. height: 40px;
  812. z-index: 10;
  813. position: absolute;
  814. top: 20px;
  815. left: 20px;
  816. .status-btn {
  817. width: 100%;
  818. height: 100%;
  819. background: transparent;
  820. border: 2px dashed;
  821. text-align: center;
  822. font-size: 20px;
  823. line-height: 40px;
  824. }
  825. }
  826. .dialog-content {
  827. width: 100%;
  828. height: 450px;
  829. display: flex;
  830. .append-properties {
  831. width: 100%;
  832. padding: 10px;
  833. box-sizing: border-box;
  834. border-radius: 6px;
  835. background-color: #f7f7f7;
  836. overflow-x: auto;
  837. margin-bottom: 5px;
  838. /* 设置滚动条的宽度和背景色 */
  839. &::-webkit-scrollbar {
  840. width: 6px;
  841. height: 6px;
  842. background-color: #f9f9f9;
  843. }
  844. /* 设置滚动条滑块的样式 */
  845. &::-webkit-scrollbar-thumb {
  846. border-radius: 6px;
  847. background-color: #c1c1c1;
  848. }
  849. /* 设置滚动条滑块hover样式 */
  850. &::-webkit-scrollbar-thumb:hover {
  851. background-color: #a8a8a8;
  852. }
  853. /* 设置滚动条轨道的样式 */
  854. &::-webkit-scrollbar-track {
  855. box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
  856. border-radius: 6px;
  857. background: #ededed;
  858. }
  859. .tips {
  860. height: 30px;
  861. display: flex;
  862. align-items: center;
  863. justify-content: center;
  864. color: #999;
  865. margin: 0 auto 10px auto;
  866. }
  867. table {
  868. width: 100%;
  869. table-layout: fixed;
  870. border-collapse: collapse;
  871. /* 合并表格边框 */
  872. border: 1px solid #ccc;
  873. /* 设置表格边框样式和颜色 */
  874. margin: 0 auto 10px auto;
  875. /* 设置表格外边距 */
  876. background-color: #f8f8f8;
  877. /* 设置表格背景颜色 */
  878. color: #000;
  879. /* 设置表格文字颜色 */
  880. text-align: center;
  881. /* 设置表格文字居中 */
  882. line-height: 40px;
  883. border-radius: 6px;
  884. /* 设置表格行高 */
  885. tr:nth-child(1) {
  886. background-color: #f2f2f2;
  887. /* 偶数行背景色 */
  888. }
  889. tr:nth-child(even) {
  890. background-color: #fff;
  891. /* 偶数行背景色 */
  892. }
  893. tr:nth-child(odd) {
  894. background-color: #f2f2f2;
  895. /* 奇数行背景色 */
  896. }
  897. td {
  898. width: 100px;
  899. overflow: hidden;
  900. white-space: nowrap;
  901. text-overflow: ellipsis;
  902. }
  903. }
  904. }
  905. .dialog-content-left, .dialog-content-right {
  906. width: 50%;
  907. height: 100%;
  908. padding: 0 10px;
  909. box-sizing: border-box;
  910. }
  911. .dialog-content-left {
  912. border-right: 1px solid #f1f1f1;
  913. }
  914. .dialog-content-right {
  915. width: 50%;
  916. }
  917. }
  918. .base-info-box {
  919. width: 100%;
  920. display: flex;
  921. margin-bottom: 20px;
  922. .label {
  923. display: flex;
  924. align-items: center;
  925. color: #606266;
  926. width: 120px;
  927. i {
  928. margin-right: 5px;
  929. }
  930. }
  931. .value {
  932. flex: 1;
  933. }
  934. .text-style {
  935. padding: 5px;
  936. border-radius: 4px;
  937. background: #f7f7f7;
  938. white-space: pre-line;
  939. box-sizing: border-box;
  940. }
  941. }
  942. .data-box {
  943. width: 500px;
  944. height: 60px;
  945. margin-bottom: 10px;
  946. .label {
  947. width: 160px;
  948. i {
  949. font-size: 16px;
  950. font-weight: 500;
  951. margin-right: 5px;
  952. color: #409EFF;
  953. }
  954. }
  955. .value {
  956. display: flex;
  957. align-items: center;
  958. }
  959. }
  960. </style>