You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1749 lines
48 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <div class="mod-config exp-apply-page">
  3. <!-- 查询条件表单 -->
  4. <el-form :inline="true" label-position="top" class="query-form">
  5. <el-form-item label="申请单号">
  6. <el-input v-model="queryHeaderData.applyNo" placeholder="请输入申请单号" clearable style="width: 150px"></el-input>
  7. </el-form-item>
  8. <el-form-item label="事业部">
  9. <el-select v-model="queryHeaderData.buNo" placeholder="请选择" clearable style="width: 120px">
  10. <el-option label="全部" value=""></el-option>
  11. <el-option
  12. v-for="i in buList"
  13. :key="i.buNo"
  14. :label="i.buDesc"
  15. :value="i.buNo">
  16. </el-option>
  17. </el-select>
  18. </el-form-item>
  19. <el-form-item label="试验类型">
  20. <el-select v-model="queryHeaderData.experimentType" placeholder="请选择" clearable style="width: 120px">
  21. <el-option label="全部" value=""></el-option>
  22. <el-option label="High Risk" value="High Risk"></el-option>
  23. <el-option label="Low Risk" value="Low Risk"></el-option>
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="试验名称">
  27. <el-input v-model="queryHeaderData.title" placeholder="支持模糊查询" clearable style="width: 150px"></el-input>
  28. </el-form-item>
  29. <el-form-item label="项目编号">
  30. <el-input v-model="queryHeaderData.projectNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input>
  31. </el-form-item>
  32. <el-form-item label="产品型号">
  33. <el-input v-model="queryHeaderData.productType" placeholder="支持模糊查询" clearable style="width: 150px"></el-input>
  34. </el-form-item>
  35. <el-form-item label="状态">
  36. <el-select v-model="queryHeaderData.status" placeholder="请选择" clearable style="width: 120px">
  37. <el-option label="全部" value=""></el-option>
  38. <el-option label="草稿" value="草稿"></el-option>
  39. <el-option label="已下达" value="已下达"></el-option>
  40. <el-option label="已批准" value="已批准"></el-option>
  41. <el-option label="生产中" value="生产中"></el-option>
  42. <el-option label="样品确认" value="样品确认"></el-option>
  43. <el-option label="已完成" value="已完成"></el-option>
  44. </el-select>
  45. </el-form-item>
  46. <el-form-item label="创建日期">
  47. <el-date-picker
  48. v-model="queryHeaderData.createStartDate"
  49. type="date"
  50. format="yyyy-MM-dd"
  51. value-format="yyyy-MM-dd"
  52. placeholder="开始日期"
  53. style="width: 140px">
  54. </el-date-picker>
  55. </el-form-item>
  56. <el-form-item label="至">
  57. <el-date-picker
  58. v-model="queryHeaderData.createEndDate"
  59. type="date"
  60. format="yyyy-MM-dd"
  61. value-format="yyyy-MM-dd"
  62. placeholder="结束日期"
  63. style="width: 140px">
  64. </el-date-picker>
  65. </el-form-item>
  66. <el-form-item label=" " style="margin-top: -11px">
  67. <el-button @click="getDataList('Y')" type="primary" plain class="search-btn">查询</el-button>
  68. <el-button @click="resetQuery()" plain class="reset-btn">重置</el-button>
  69. <el-button @click="openCreateDialog()" type="success" plain class="add-btn">新增申请单</el-button>
  70. </el-form-item>
  71. </el-form>
  72. <!-- 数据表格 -->
  73. <el-table
  74. ref="dataTable"
  75. @row-click="handleRowClick"
  76. highlight-current-row
  77. :data="dataList"
  78. v-loading="dataListLoading"
  79. border
  80. class="data-table"
  81. style="width: 100%;"
  82. :height="tableHeight">
  83. <el-table-column
  84. label="操作"
  85. width="150"
  86. align="center"
  87. header-align="center">
  88. <template slot-scope="scope">
  89. <a
  90. @click="editApply(scope.row)">修改</a>
  91. <a
  92. v-if="scope.row.status === '草稿' || scope.row.status === '已驳回'"
  93. @click="submitApply(scope.row)">{{ scope.row.status === '已驳回' ? '重新下达' : '下达' }}</a>
  94. <a
  95. v-if="scope.row.status === '草稿'"
  96. @click="deleteApply(scope.row)">删除</a>
  97. <a
  98. v-if="scope.row.status !== '草稿' && scope.row.status !== '已完成' && scope.row.status !== '已驳回'"
  99. @click="withdrawApply(scope.row)">撤回</a>
  100. <a
  101. v-if="showSampleConfirmBtn(scope.row)"
  102. @click="openSampleConfirm(scope.row)">样品确认</a>
  103. </template>
  104. </el-table-column>
  105. <el-table-column
  106. prop="buDesc"
  107. label="事业部"
  108. width="70"
  109. align="center"
  110. header-align="center">
  111. </el-table-column>
  112. <el-table-column
  113. prop="applyNo"
  114. label="申请单号"
  115. width="100"
  116. align="center"
  117. header-align="center"
  118. show-overflow-tooltip>
  119. </el-table-column>
  120. <el-table-column
  121. prop="status"
  122. label="状态"
  123. width="80"
  124. align="center"
  125. header-align="center">
  126. </el-table-column>
  127. <!-- <el-table-column
  128. prop="currentStep"
  129. label="当前步骤"
  130. width="90"
  131. align="center"
  132. header-align="center">
  133. </el-table-column>-->
  134. <el-table-column
  135. prop="experimentType"
  136. label="试验类型"
  137. width="100"
  138. align="center" show-overflow-tooltip
  139. header-align="center">
  140. </el-table-column>
  141. <el-table-column
  142. prop="title"
  143. label="试验名称"
  144. min-width="120"
  145. align="left"
  146. header-align="center"
  147. show-overflow-tooltip>
  148. </el-table-column>
  149. <el-table-column
  150. prop="projectNo"
  151. label="项目编号"
  152. width="100"
  153. align="center"
  154. header-align="center"
  155. show-overflow-tooltip>
  156. </el-table-column>
  157. <el-table-column
  158. prop="productType"
  159. label="产品型号"
  160. min-width="220"
  161. align="center"
  162. header-align="center"
  163. show-overflow-tooltip>
  164. </el-table-column>
  165. <el-table-column
  166. prop="expectedFinishDate"
  167. label="期望完成日期"
  168. width="120"
  169. align="center"
  170. header-align="center">
  171. </el-table-column>
  172. <el-table-column
  173. prop="actualFinishDate"
  174. label="最终完成日期"
  175. width="120"
  176. align="center"
  177. header-align="center">
  178. </el-table-column>
  179. <el-table-column
  180. prop="finalQuantity"
  181. label="入库数量"
  182. width="80"
  183. align="center"
  184. header-align="center">
  185. </el-table-column>
  186. <el-table-column
  187. prop="creatorName"
  188. label="创建人"
  189. width="80"
  190. align="center"
  191. header-align="center">
  192. </el-table-column>
  193. <el-table-column
  194. prop="createTime"
  195. label="创建时间"
  196. min-width="100"
  197. align="center" show-overflow-tooltip
  198. header-align="center">
  199. </el-table-column>
  200. </el-table>
  201. <!-- 分页组件 -->
  202. <el-pagination
  203. @size-change="sizeChangeHandle"
  204. @current-change="currentChangeHandle"
  205. :current-page="pageIndex"
  206. :page-sizes="[20, 50, 100]"
  207. :page-size="pageSize"
  208. :total="totalPage"
  209. layout="total, sizes, prev, pager, next, jumper"
  210. style="margin-top: 10px; text-align: right;">
  211. </el-pagination>
  212. <!-- 下方Tab区域 -->
  213. <el-tabs class="customer-tab"
  214. v-model="activeName"
  215. type="border-card"
  216. style="margin-top: 10px; min-height: 280px;"
  217. @tab-click="handleTabClick">
  218. <!-- Tab 1: 项目详情 -->
  219. <!-- <el-tab-pane :label="getProjectDetailLabel()" name="projectDetail">
  220. <exp-project-detail
  221. v-if="currentRow.applyNo"
  222. ref="projectDetail"
  223. :apply-no="currentRow.applyNo"
  224. :status="currentRow.status"
  225. :height="detailHeight"
  226. @refresh="handleDetailRefresh">
  227. </exp-project-detail>
  228. <div v-else class="empty-tip">
  229. 请在上方表格中选择一条申请单记录
  230. </div>
  231. </el-tab-pane>-->
  232. <el-tab-pane label="附件上传" name="attachment">
  233. <erf-attachment-manager
  234. v-if="currentRow.applyNo"
  235. ref="attachmentManager"
  236. :apply-no="currentRow.applyNo"
  237. :disabled="false"
  238. :height="detailHeight">
  239. </erf-attachment-manager>
  240. <div v-else class="empty-tip">
  241. 请在上方表格中选择一条申请单记录
  242. </div>
  243. </el-tab-pane>
  244. <!-- Tab 2: 三方确认 -->
  245. <el-tab-pane label="三方确认" v-if="currentRow.experimentType==='High Risk'" name="triConfirm">
  246. <exp-tri-confirm
  247. v-if="currentRow.applyNo&&currentRow.experimentType==='High Risk'"
  248. ref="triConfirm"
  249. :apply-no="currentRow.applyNo"
  250. :experiment-type="currentRow.experimentType"
  251. :height="detailHeight">
  252. </exp-tri-confirm>
  253. <div v-else class="empty-tip">
  254. 请在上方表格中选择一条申请单记录
  255. </div>
  256. </el-tab-pane>
  257. <!-- Tab 3: 附件上传 -->
  258. <!-- Tab 4: 审批状态和日志 -->
  259. <el-tab-pane label="审批状态和日志" name="approvalStatus">
  260. <div v-if="currentRow.applyNo" :style="{height: detailHeight + 'px', overflowY: 'auto', padding: '0px 10px'}">
  261. <!-- 两栏布局审批流程 | 审批日志 -->
  262. <div class="two-column-layout">
  263. <!-- 左栏审批流程 -->
  264. <div class="stages-column">
  265. <div class="column-header">
  266. <i class="el-icon-s-order"></i>
  267. <span>审批流程</span>
  268. <span class="progress-badge">{{ flowStatus.progressPercent || 0 }}%</span>
  269. </div>
  270. <div class="stages-list">
  271. <div
  272. v-for="(stage, index) in flowStatus.stages"
  273. :key="index"
  274. class="stage-item"
  275. :class="'stage-' + stage.status">
  276. <div class="stage-icon">
  277. <i :class="getStageIcon(stage.status)"></i>
  278. </div>
  279. <div class="stage-content">
  280. <div class="stage-name">{{ stage.nodeName }}</div>
  281. <div class="stage-meta">
  282. <el-tag :type="getStageTagType(stage.status)" size="mini" effect="plain">
  283. {{ getStageStatusText(stage.status) }}
  284. </el-tag>
  285. <span v-if="stage.completeTime" class="stage-time">{{ stage.completeTime }}</span>
  286. </div>
  287. </div>
  288. </div>
  289. </div>
  290. </div>
  291. <!-- 右栏审批日志表格 -->
  292. <div class="logs-column">
  293. <div class="column-header">
  294. <i class="el-icon-tickets"></i>
  295. <span>审批日志</span>
  296. <span class="logs-count">{{ flowStatus.approvalLogs ? flowStatus.approvalLogs.length : 0 }}</span>
  297. </div>
  298. <div class="logs-table-wrapper">
  299. <el-table
  300. :data="flowStatus.approvalLogs"
  301. size="small"
  302. class="approval-logs-table"
  303. style="width: 100%"
  304. height="34vh">
  305. <el-table-column prop="logTime" label="时间" width="150" align="center">
  306. <template slot-scope="scope">
  307. <span style="font-size: 12px">{{ formatDateTime(scope.row.logTime) }}</span>
  308. </template>
  309. </el-table-column>
  310. <el-table-column prop="action" label="操作" width="90" align="center">
  311. <template slot-scope="scope">
  312. <el-tag :type="getActionTagType(scope.row.action)" size="mini">
  313. {{ scope.row.action }}
  314. </el-tag>
  315. </template>
  316. </el-table-column>
  317. <el-table-column prop="nodeCode" label="节点" width="150" align="center">
  318. <template slot-scope="scope">
  319. <span style="font-size: 12px">{{ scope.row.nodeCode }}</span>
  320. </template>
  321. </el-table-column>
  322. <el-table-column prop="operatorName" label="操作人" width="100" align="center">
  323. <template slot-scope="scope">
  324. <span style="font-size: 12px">{{ scope.row.operatorName }}</span>
  325. </template>
  326. </el-table-column>
  327. <el-table-column prop="comment" label="备注" min-width="180" show-overflow-tooltip>
  328. <template slot-scope="scope">
  329. <span style="font-size: 12px; color: #606266">{{ scope.row.comment || '-' }}</span>
  330. </template>
  331. </el-table-column>
  332. </el-table>
  333. </div>
  334. </div>
  335. </div>
  336. </div>
  337. <div v-else class="empty-tip">
  338. <i class="el-icon-document" style="font-size: 40px; color: #C0C4CC; margin-bottom: 10px"></i>
  339. <p style="font-size: 13px">请选择申请单查看审批状态</p>
  340. </div>
  341. </el-tab-pane>
  342. </el-tabs>
  343. <!-- 新增/编辑弹窗 -->
  344. <el-dialog
  345. :title="dialogTitle"
  346. :visible.sync="dialogVisible"
  347. width="950px"
  348. :close-on-click-modal="false"
  349. v-drag>
  350. <exp-apply-form
  351. v-if="dialogVisible"
  352. ref="applyForm"
  353. :apply-data="currentApply"
  354. :readonly="dialogReadonly">
  355. </exp-apply-form>
  356. <el-footer style="height: 40px; margin-top: 40px; text-align: center">
  357. <el-button type="primary" @click="saveApply" v-if="!dialogReadonly" :loading="saveLoading">
  358. {{ saveLoading ? '保存中...' : '保存' }}
  359. </el-button>
  360. <el-button type="primary" @click="dialogVisible = false">关闭</el-button>
  361. </el-footer>
  362. </el-dialog>
  363. <!-- 样品确认弹窗 -->
  364. <el-dialog
  365. title="样品确认"
  366. :visible.sync="sampleConfirmVisible"
  367. width="320px"
  368. :close-on-click-modal="false">
  369. <div style="margin-top: 1px;margin-left: 10px; color: #909399; font-size: 12px">
  370. 不输入或输入0表示样品报废大于0表示正常入库
  371. </div>
  372. <el-form :model="sampleConfirmData" label-width="100px" size="small">
  373. <el-form-item label="申请单号">
  374. <el-tag type="primary">{{ sampleConfirmData.applyNo }}</el-tag>
  375. </el-form-item>
  376. <el-form-item label="样品数量" required>
  377. <el-input
  378. v-model="sampleConfirmData.sampleQuantity"
  379. :min="0"
  380. :precision="0"
  381. placeholder="请输入样品数量"
  382. style="width: 80%">
  383. </el-input>
  384. </el-form-item>
  385. <el-form-item label="完成日期" required>
  386. <el-date-picker
  387. v-model="sampleConfirmData.finalFinishDate"
  388. type="date"
  389. format="yyyy-MM-dd"
  390. value-format="yyyy-MM-dd"
  391. placeholder="请选择最终完成日期"
  392. style="width: 80%">
  393. </el-date-picker>
  394. </el-form-item>
  395. <el-form-item label="样品状态">
  396. <el-tag :type="getSampleStatusTagType()">
  397. {{ getSampleStatusText() }}
  398. </el-tag>
  399. </el-form-item>
  400. </el-form>
  401. <div slot="footer" class="dialog-footer">
  402. <el-button type="primary" @click="confirmSampleSubmit" :loading="sampleConfirmLoading">
  403. {{ sampleConfirmLoading ? '确认中...' : '确认' }}
  404. </el-button>
  405. <el-button @click="sampleConfirmVisible = false">关闭</el-button>
  406. </div>
  407. </el-dialog>
  408. <!-- 下达确认弹窗 - 选择审批人 -->
  409. <el-dialog
  410. title="确认审批人信息"
  411. :visible.sync="submitDialogVisible"
  412. width="500px"
  413. :close-on-click-modal="false">
  414. <el-form :model="submitData" label-width="80px" size="small">
  415. <!-- 技术经理 -->
  416. <el-form-item label="技术经理">
  417. <el-tag type="success" size="medium">
  418. {{ submitData.techManagerName }}
  419. </el-tag>
  420. <span style="margin-left: 10px; color: #909399; font-size: 12px">
  421. (根据发起人角色自动分配)
  422. </span>
  423. </el-form-item>
  424. <!-- 生产经理 -->
  425. <el-form-item label="生产经理" required>
  426. <el-select class="manager-select"
  427. v-model="submitData.prodManagerIds"
  428. multiple
  429. placeholder="请选择生产经理"
  430. style="width: 100%">
  431. <el-option
  432. v-for="manager in prodManagerList"
  433. :key="manager.userId"
  434. :label="manager.userDisplay || manager.username"
  435. :value="manager.userId">
  436. </el-option>
  437. </el-select>
  438. <div style="margin-top: 1px; color: #909399; font-size: 12px">
  439. 可多选所有选中的生产经理都必须审批通过后才会流转到计划员排产
  440. </div>
  441. </el-form-item>
  442. <!-- 质量经理 -->
  443. <el-form-item label="质量经理" required>
  444. <el-select class="manager-select"
  445. v-model="submitData.qualityManagerIds"
  446. multiple
  447. placeholder="请选择质量经理"
  448. style="width: 100%">
  449. <el-option
  450. v-for="manager in qualityManagerList"
  451. :key="manager.userId"
  452. :label="manager.userDisplay || manager.username"
  453. :value="manager.userId">
  454. </el-option>
  455. </el-select>
  456. <div style="margin-top: 1px; color: #909399; font-size: 12px">
  457. 可多选所有选中的质量经理都必须审批通过后才会流转到计划员排产
  458. </div>
  459. </el-form-item>
  460. </el-form>
  461. <div slot="footer" class="dialog-footer">
  462. <el-button @click="submitDialogVisible = false">取消</el-button>
  463. <el-button type="primary" @click="confirmSubmit" :loading="submitLoading">
  464. {{ submitLoading ? '下达中...' : '确认下达' }}
  465. </el-button>
  466. </div>
  467. </el-dialog>
  468. </div>
  469. </template>
  470. <script>
  471. import { searchExpApplyList, submitExpApply, deleteExpApply, withdrawExpApply, getSubmitApprovers, getFlowStatus, getTriConfirmList, confirmSample } from '@/api/erf/erf'
  472. import { getBuList } from '@/api/factory/site'
  473. import ExpApplyForm from './components/expApplyForm.vue'
  474. import ExpProjectDetail from './components/expProjectDetail.vue'
  475. import ExpTriConfirm from './components/expTriConfirm.vue'
  476. import ErfAttachmentManager from './components/erfAttachmentManager.vue'
  477. export default {
  478. name: 'ExpApplyList',
  479. components: {
  480. ExpApplyForm,
  481. ExpProjectDetail,
  482. ExpTriConfirm,
  483. ErfAttachmentManager
  484. },
  485. data() {
  486. return {
  487. buList: [],
  488. // 查询条件
  489. queryHeaderData: {
  490. applyNo: '',
  491. buNo: '',
  492. experimentType: '',
  493. status: '',
  494. createStartDate: '',
  495. createEndDate: '',
  496. page: 1,
  497. limit: 20
  498. },
  499. // 数据列表
  500. dataList: [],
  501. // 分页参数
  502. pageIndex: 1,
  503. pageSize: 20,
  504. totalPage: 0,
  505. dataListLoading: false,
  506. // 弹窗相关
  507. dialogVisible: false,
  508. dialogTitle: '新增申请单',
  509. dialogReadonly: false,
  510. currentApply: {},
  511. saveLoading: false,
  512. // 当前选中行
  513. currentRow: {},
  514. // Tab页签
  515. activeName: 'attachment',
  516. // 表格高度
  517. tableHeight: (window.innerHeight - 260)/2,
  518. detailHeight: '35vh',
  519. // 下达确认弹窗
  520. submitDialogVisible: false,
  521. submitLoading: false,
  522. submitData: {
  523. applyNo: '',
  524. buNo: '',
  525. techManagerId: null,
  526. techManagerName: '',
  527. prodManagerIds: [],
  528. qualityManagerIds: []
  529. },
  530. prodManagerList: [], // 生产经理候选列表
  531. qualityManagerList: [], // 质量经理候选列表
  532. // 流程状态数据
  533. flowStatus: {
  534. applyNo: '',
  535. currentStatus: '',
  536. currentStep: '',
  537. progressPercent: 0,
  538. stages: [],
  539. approvalLogs: []
  540. },
  541. // 样品确认弹窗
  542. sampleConfirmVisible: false,
  543. sampleConfirmLoading: false,
  544. sampleConfirmData: {
  545. applyNo: '',
  546. sampleQuantity: null,
  547. finalFinishDate: ''
  548. }
  549. }
  550. },
  551. activated() {
  552. this.loadBuList()
  553. this.getDataList()
  554. },
  555. methods: {
  556. /**
  557. * 加载事业部列表
  558. */
  559. loadBuList() {
  560. const tempData = { site: this.$store.state.user.site }
  561. getBuList(tempData).then(({data}) => {
  562. if (data.code === 0) {
  563. this.buList = data.row1
  564. }
  565. })
  566. },
  567. /**
  568. * 获取申请单列表
  569. */
  570. getDataList(flag) {
  571. if (flag === 'Y') {
  572. this.pageIndex = 1
  573. }
  574. this.queryHeaderData.page = this.pageIndex
  575. this.queryHeaderData.limit = this.pageSize
  576. this.dataListLoading = true
  577. searchExpApplyList(this.queryHeaderData).then(({data}) => {
  578. this.dataListLoading = false
  579. if (data && data.code === 0) {
  580. this.dataList = data.page.list || []
  581. this.totalPage = data.page.totalCount || 0
  582. // 如果有数据,处理行选中
  583. if (this.dataList.length > 0) {
  584. // 尝试找到之前选中的行
  585. if (this.currentRow.applyNo) {
  586. const selectedRow = this.dataList.find(item => item.applyNo === this.currentRow.applyNo)
  587. if (selectedRow) {
  588. // 如果找到之前选中的行,重新高亮
  589. this.$nextTick(() => {
  590. this.handleRowClick(selectedRow)
  591. })
  592. } else {
  593. // 如果没找到,选中第一行
  594. this.handleRowClick(this.dataList[0])
  595. }
  596. } else {
  597. // 如果之前没有选中任何行,默认选中第一行
  598. this.handleRowClick(this.dataList[0])
  599. }
  600. } else {
  601. // 没有数据,清空当前行
  602. this.currentRow = {}
  603. }
  604. } else {
  605. this.dataList = []
  606. this.totalPage = 0
  607. this.currentRow = {}
  608. this.$message.error(data.msg || '查询失败')
  609. }
  610. }).catch(error => {
  611. this.dataListLoading = false
  612. this.$message.error('查询异常')
  613. })
  614. },
  615. /**
  616. * 重置查询条件
  617. */
  618. resetQuery() {
  619. this.queryHeaderData = {
  620. applyNo: '',
  621. buNo: '',
  622. experimentType: '',
  623. status: '',
  624. createStartDate: '',
  625. createEndDate: '',
  626. page: 1,
  627. limit: 20
  628. }
  629. this.getDataList('Y')
  630. },
  631. /**
  632. * 打开新增对话框
  633. */
  634. openCreateDialog() {
  635. this.dialogTitle = '新增申请单'
  636. this.dialogReadonly = false
  637. this.currentApply = {}
  638. this.dialogVisible = true
  639. },
  640. /**
  641. * 查看详情
  642. */
  643. viewDetail(row) {
  644. this.dialogTitle = '查看申请单'
  645. this.dialogReadonly = true
  646. this.currentApply = { ...row }
  647. this.dialogVisible = true
  648. },
  649. /**
  650. * 编辑申请单
  651. */
  652. editApply(row) {
  653. this.dialogTitle = '修改申请单'
  654. this.dialogReadonly = false
  655. this.currentApply = { ...row }
  656. this.dialogVisible = true
  657. },
  658. /**
  659. * 保存申请单
  660. */
  661. saveApply() {
  662. const formData = this.$refs.applyForm.getFormData()
  663. if (!formData) {
  664. return
  665. }
  666. this.saveLoading = true
  667. this.$refs.applyForm.save().then(() => {
  668. this.saveLoading = false
  669. this.$message.success('保存成功')
  670. this.dialogVisible = false
  671. this.getDataList()
  672. }).catch(error => {
  673. this.saveLoading = false
  674. })
  675. },
  676. /**
  677. * 下达申请单 - 打开审批人确认弹窗
  678. */
  679. submitApply(row) {
  680. // ✅ 如果是High Risk,先验证工序
  681. if (row.experimentType === 'High Risk') {
  682. console.log('🔍 检测到High Risk申请单,开始验证工序...')
  683. // 通过API查询工序列表进行验证(不依赖子组件是否已加载)
  684. this.validateHighRiskProcessByApi(row.applyNo).then(isValid => {
  685. if (!isValid) {
  686. console.log('❌ High Risk验证失败,终止下达')
  687. this.$alert('High Risk申请单必须至少有一条完整的工序(包含车间、质量、技术三个负责人)才能下达', '操作提示', {
  688. confirmButtonText: '确定',
  689. type: 'warning'
  690. })
  691. return
  692. }
  693. console.log('✅ High Risk验证通过,继续下达流程')
  694. // 验证通过后继续下达流程
  695. this.proceedSubmit(row)
  696. }).catch(error => {
  697. console.error('❌ 验证工序异常:', error)
  698. this.$message.error('验证工序失败,请重试')
  699. })
  700. } else {
  701. // 非High Risk直接下达
  702. console.log('📝 Low Risk申请单,直接下达')
  703. this.proceedSubmit(row)
  704. }
  705. },
  706. /**
  707. * 通过API验证High Risk工序
  708. *
  709. * @param {String} applyNo 申请单号
  710. * @return {Promise<Boolean>} 验证结果
  711. */
  712. validateHighRiskProcessByApi(applyNo) {
  713. return new Promise((resolve, reject) => {
  714. // 调用API查询工序列表
  715. getTriConfirmList({ applyNo: applyNo }).then(({data}) => {
  716. if (data && data.code === 0) {
  717. const processList = data.list || []
  718. console.log('📊 查询到工序数量:', processList.length)
  719. // 检查是否至少有一条完整的工序
  720. const completeProcesses = processList.filter(process => {
  721. const isComplete = process.prodApproverName &&
  722. process.qaApproverName &&
  723. process.techApproverName
  724. if (isComplete) {
  725. console.log('✅ 完整工序:', process.processStep, {
  726. 车间: process.prodApproverName,
  727. 质量: process.qaApproverName,
  728. 技术: process.techApproverName
  729. })
  730. }
  731. return isComplete
  732. })
  733. console.log(`📈 完整工序数量: ${completeProcesses.length}/${processList.length}`)
  734. // 至少需要一条完整的工序
  735. resolve(completeProcesses.length > 0)
  736. } else {
  737. reject(new Error(data.msg || '查询工序列表失败'))
  738. }
  739. }).catch(error => {
  740. reject(error)
  741. })
  742. })
  743. },
  744. /**
  745. * 执行下达流程
  746. */
  747. proceedSubmit(row) {
  748. // 一次性获取所有审批人信息
  749. getSubmitApprovers({
  750. userId: this.$store.state.user.id,
  751. buNo: row.buNo
  752. }).then(({data}) => {
  753. if (data && data.code === 0) {
  754. // 设置生产经理和质量经理列表
  755. this.prodManagerList = data.prodManagers || []
  756. this.qualityManagerList = data.qualityManagers || []
  757. // 根据事业部设置默认生产经理(使用user_display模糊匹配)
  758. const defaultProdManagerIds = []
  759. if (row.buDesc === 'RFID') {
  760. // RFID默认Tony
  761. const tony = this.prodManagerList.find(m =>
  762. m.userDisplay && m.userDisplay.toLowerCase().includes('tony')
  763. )
  764. if (tony) defaultProdManagerIds.push(tony.userId)
  765. } else if (row.buDesc === 'RF') {
  766. // RF默认Charles
  767. const charles = this.prodManagerList.find(m =>
  768. m.userDisplay && m.userDisplay.toLowerCase().includes('charles')
  769. )
  770. if (charles) defaultProdManagerIds.push(charles.userId)
  771. }
  772. // 根据事业部设置默认质量经理(使用user_display模糊匹配)
  773. const defaultQualityManagerIds = []
  774. if (row.buDesc === 'RFID') {
  775. // RFID默认Victor
  776. const victor = this.qualityManagerList.find(m =>
  777. m.userDisplay && m.userDisplay.toLowerCase().includes('victor')
  778. )
  779. if (victor) defaultQualityManagerIds.push(victor.userId)
  780. } else if (row.buDesc === 'RF') {
  781. // RF默认尹君
  782. const yin = this.qualityManagerList.find(m =>
  783. m.userDisplay && (m.userDisplay.includes('尹君') || m.userDisplay.toLowerCase().includes('yin'))
  784. )
  785. if (yin) defaultQualityManagerIds.push(yin.userId)
  786. }
  787. // 设置表单数据
  788. this.submitData = {
  789. applyNo: row.applyNo,
  790. buNo: row.buNo,
  791. techManagerId: data.techManager.managerId,
  792. techManagerName: data.techManager.managerName,
  793. prodManagerIds: defaultProdManagerIds,
  794. qualityManagerIds: defaultQualityManagerIds
  795. }
  796. // 显示弹窗
  797. this.submitDialogVisible = true
  798. } else {
  799. this.$message.error(data.msg || '获取审批人信息失败')
  800. }
  801. }).catch(() => {
  802. this.$message.error('获取审批人信息异常')
  803. })
  804. },
  805. /**
  806. * 确认下达
  807. */
  808. confirmSubmit() {
  809. // 验证必填项
  810. if (!this.submitData.techManagerId) {
  811. this.$message.warning('技术经理未分配')
  812. return
  813. }
  814. if (!this.submitData.prodManagerIds || this.submitData.prodManagerIds.length === 0) {
  815. this.$message.warning('请至少选择一位生产经理')
  816. return
  817. }
  818. if (!this.submitData.qualityManagerIds || this.submitData.qualityManagerIds.length === 0) {
  819. this.$message.warning('请至少选择一位质量经理')
  820. return
  821. }
  822. this.submitLoading = true
  823. // 调用下达API
  824. submitExpApply({
  825. applyNo: this.submitData.applyNo,
  826. techManagerId: this.submitData.techManagerId,
  827. prodManagerIds: this.submitData.prodManagerIds,
  828. qualityManagerIds: this.submitData.qualityManagerIds
  829. }).then(({data}) => {
  830. this.submitLoading = false
  831. if (data && data.code === 0) {
  832. this.$message.success('下达成功,已通知相关审批人')
  833. this.submitDialogVisible = false
  834. this.getDataList()
  835. // 触发全局审批通知检查(通知审批人)
  836. this.triggerApprovalNotification()
  837. } else {
  838. this.$message.error(data.msg || '下达失败')
  839. }
  840. }).catch(() => {
  841. this.submitLoading = false
  842. })
  843. },
  844. /**
  845. * 触发全局审批通知检查
  846. * 在申请单下达成功后调用立即通知审批人
  847. */
  848. triggerApprovalNotification() {
  849. try {
  850. // 通过 $root 访问 App.vue 的方法
  851. if (this.$root && this.$root.$children && this.$root.$children[0]) {
  852. const app = this.$root.$children[0]
  853. if (app.checkApprovalNotifications) {
  854. // 延迟1秒后检查,确保后端数据已更新
  855. setTimeout(() => {
  856. //app.checkApprovalNotifications()
  857. console.log('[审批通知] 已触发手动检查')
  858. }, 1000)
  859. }
  860. }
  861. } catch (error) {
  862. console.log('[审批通知] 触发通知检查失败:', error.message)
  863. }
  864. },
  865. /**
  866. * 删除申请单
  867. */
  868. deleteApply(row) {
  869. this.$confirm('确定删除该申请单?', '操作提示', {
  870. confirmButtonText: '确定',
  871. cancelButtonText: '取消',
  872. type: 'warning'
  873. }).then(() => {
  874. deleteExpApply({ applyNo: row.applyNo }).then(({data}) => {
  875. if (data && data.code === 0) {
  876. this.$message.success('删除成功')
  877. this.getDataList()
  878. } else {
  879. this.$message.error(data.msg || '删除失败')
  880. }
  881. })
  882. })
  883. },
  884. /**
  885. * 撤回申请单
  886. */
  887. withdrawApply(row) {
  888. this.$confirm('确定撤回该申请单?撤回后将变为草稿状态', '操作提示', {
  889. confirmButtonText: '确定',
  890. cancelButtonText: '取消',
  891. type: 'warning'
  892. }).then(() => {
  893. withdrawExpApply({
  894. applyNo: row.applyNo,
  895. currentUserId: this.$store.state.user.id
  896. }).then(({data}) => {
  897. if (data && data.code === 0) {
  898. this.$message.success('撤回成功')
  899. this.getDataList()
  900. } else {
  901. this.$message.error(data.msg || '撤回失败')
  902. }
  903. })
  904. })
  905. },
  906. /**
  907. * 获取状态类型
  908. */
  909. getStatusType(status) {
  910. const types = {
  911. '草稿': 'info',
  912. '已下达': 'warning',
  913. '已批准': 'success',
  914. '生产中': '',
  915. '已完成': 'success',
  916. '已取消': 'danger',
  917. '已驳回': 'danger'
  918. }
  919. return types[status] || 'info'
  920. },
  921. /**
  922. * 获取状态文本直接返回中文状态
  923. */
  924. getStatusText(status) {
  925. return status || ''
  926. },
  927. /**
  928. * 分页大小改变
  929. */
  930. sizeChangeHandle(val) {
  931. this.pageSize = val
  932. this.pageIndex = 1
  933. this.getDataList()
  934. },
  935. /**
  936. * 当前页改变
  937. */
  938. currentChangeHandle(val) {
  939. this.pageIndex = val
  940. this.getDataList()
  941. },
  942. /**
  943. * 行点击事件
  944. */
  945. handleRowClick(row) {
  946. this.currentRow = JSON.parse(JSON.stringify(row))
  947. // 设置表格当前行高亮
  948. this.$nextTick(() => {
  949. this.$refs.dataTable.setCurrentRow(row)
  950. })
  951. // 根据当前tab刷新对应的数据
  952. if (this.activeName === 'approvalStatus') {
  953. // 刷新审批状态和日志
  954. this.loadFlowStatus()
  955. } else if (this.activeName === 'triConfirm') {
  956. // 刷新三方确认数据
  957. this.$nextTick(() => {
  958. if (this.$refs.triConfirm && this.$refs.triConfirm.loadProcessList) {
  959. console.log('🔄 行切换,刷新三方确认数据')
  960. this.$refs.triConfirm.loadProcessList()
  961. }
  962. })
  963. } else {
  964. // 否则自动切换到项目详情tab
  965. this.activeName = 'attachment'
  966. }
  967. },
  968. /**
  969. * Tab切换事件
  970. */
  971. handleTabClick(tab) {
  972. // Tab切换时可以做一些特殊处理
  973. console.log('当前Tab:', tab.name)
  974. // 切换到审批状态tab时加载流程状态
  975. if (tab.name === 'approvalStatus' && this.currentRow.applyNo) {
  976. this.loadFlowStatus()
  977. }
  978. // 切换到三方确认tab时刷新数据
  979. if (tab.name === 'triConfirm' && this.currentRow.applyNo) {
  980. this.$nextTick(() => {
  981. if (this.$refs.triConfirm && this.$refs.triConfirm.loadProcessList) {
  982. console.log('🔄 Tab切换,刷新三方确认数据')
  983. this.$refs.triConfirm.loadProcessList()
  984. }
  985. })
  986. }
  987. },
  988. /**
  989. * 项目详情刷新事件
  990. */
  991. handleDetailRefresh() {
  992. // 刷新当前行数据
  993. this.getDataList()
  994. },
  995. /**
  996. * 获取项目详情Tab标签
  997. */
  998. getProjectDetailLabel() {
  999. if (this.currentRow.applyNo) {
  1000. return `项目详情 [${this.currentRow.applyNo}]`
  1001. }
  1002. return '项目详情'
  1003. },
  1004. /**
  1005. * 加载流程状态
  1006. */
  1007. loadFlowStatus() {
  1008. if (!this.currentRow.applyNo) {
  1009. return
  1010. }
  1011. getFlowStatus({ applyNo: this.currentRow.applyNo }).then(({data}) => {
  1012. if (data && data.code === 0) {
  1013. this.flowStatus = data.data || {
  1014. stages: [],
  1015. approvalLogs: []
  1016. }
  1017. }
  1018. })
  1019. },
  1020. /**
  1021. * 获取阶段颜色
  1022. */
  1023. getStageColor(status) {
  1024. const colors = {
  1025. 'pending': '#C0C4CC',
  1026. 'current': '#409EFF',
  1027. 'completed': '#67C23A',
  1028. 'rejected': '#F56C6C'
  1029. }
  1030. return colors[status] || '#C0C4CC'
  1031. },
  1032. /**
  1033. * 获取阶段图标
  1034. */
  1035. getStageIcon(status) {
  1036. const icons = {
  1037. 'pending': 'el-icon-time',
  1038. 'current': 'el-icon-loading',
  1039. 'completed': 'el-icon-success',
  1040. 'rejected': 'el-icon-error'
  1041. }
  1042. return icons[status] || 'el-icon-time'
  1043. },
  1044. /**
  1045. * 获取阶段标签类型
  1046. */
  1047. getStageTagType(status) {
  1048. const types = {
  1049. 'pending': 'info',
  1050. 'current': '',
  1051. 'completed': 'success',
  1052. 'rejected': 'danger'
  1053. }
  1054. return types[status] || 'info'
  1055. },
  1056. /**
  1057. * 获取阶段状态文本
  1058. */
  1059. getStageStatusText(status) {
  1060. const texts = {
  1061. 'pending': '未开始',
  1062. 'current': '进行中',
  1063. 'completed': '已完成',
  1064. 'rejected': '已驳回'
  1065. }
  1066. return texts[status] || '未知'
  1067. },
  1068. /**
  1069. * 获取操作类型标签
  1070. */
  1071. getActionTagType(action) {
  1072. const types = {
  1073. '下达': 'primary',
  1074. '批准': 'success',
  1075. '确认': 'success',
  1076. '驳回': 'danger',
  1077. '撤回': 'warning',
  1078. '提醒': 'info',
  1079. '已排产': 'success',
  1080. '录入最终结果': 'success'
  1081. }
  1082. return types[action] || 'info'
  1083. },
  1084. /**
  1085. * 格式化日期时间
  1086. */
  1087. formatDateTime(dateTime) {
  1088. if (!dateTime) {
  1089. return ''
  1090. }
  1091. if (typeof dateTime === 'string') {
  1092. return dateTime
  1093. }
  1094. const date = new Date(dateTime)
  1095. const year = date.getFullYear()
  1096. const month = String(date.getMonth() + 1).padStart(2, '0')
  1097. const day = String(date.getDate()).padStart(2, '0')
  1098. const hours = String(date.getHours()).padStart(2, '0')
  1099. const minutes = String(date.getMinutes()).padStart(2, '0')
  1100. const seconds = String(date.getSeconds()).padStart(2, '0')
  1101. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  1102. },
  1103. /**
  1104. * 判断是否显示样品确认按钮
  1105. * Low Risk: 计划员排产完成后(currentStep="已完成"且status="生产中")显示
  1106. * High Risk: 三方确认阶段(currentStep="三方确认"且status="生产中")显示
  1107. */
  1108. showSampleConfirmBtn(row) {
  1109. if (row.experimentType === 'Low Risk') {
  1110. return row.currentStep === '样品确认'
  1111. } else if (row.experimentType === 'High Risk') {
  1112. return row.currentStep === '样品确认'
  1113. }
  1114. return false
  1115. },
  1116. /**
  1117. * 打开样品确认对话框
  1118. */
  1119. openSampleConfirm(row) {
  1120. this.sampleConfirmData = {
  1121. applyNo: row.applyNo,
  1122. sampleQuantity: null,
  1123. finalFinishDate: ''
  1124. }
  1125. this.sampleConfirmVisible = true
  1126. },
  1127. /**
  1128. * 获取样品状态标签类型
  1129. */
  1130. getSampleStatusTagType() {
  1131. if (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0) {
  1132. return 'danger'
  1133. }
  1134. return 'success'
  1135. },
  1136. /**
  1137. * 获取样品状态文本
  1138. */
  1139. getSampleStatusText() {
  1140. if (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0) {
  1141. return '报废'
  1142. }
  1143. return '正常入库'
  1144. },
  1145. /**
  1146. * 提交样品确认
  1147. */
  1148. confirmSampleSubmit() {
  1149. // 验证最终完成日期必填
  1150. if (!this.sampleConfirmData.finalFinishDate) {
  1151. this.$message.warning('请选择最终完成日期')
  1152. return
  1153. }
  1154. // 确定样品状态
  1155. const finalStatus = (!this.sampleConfirmData.sampleQuantity || this.sampleConfirmData.sampleQuantity === 0)
  1156. ? '报废'
  1157. : '正常入库'
  1158. const finalQuantity = this.sampleConfirmData.sampleQuantity || 0
  1159. this.sampleConfirmLoading = true
  1160. confirmSample({
  1161. applyNo: this.sampleConfirmData.applyNo,
  1162. finalQuantity: finalQuantity,
  1163. finalStatus: finalStatus,
  1164. actualFinishDate: this.sampleConfirmData.finalFinishDate
  1165. }).then(({data}) => {
  1166. this.sampleConfirmLoading = false
  1167. if (data && data.code === 0) {
  1168. this.$message.success('样品确认成功')
  1169. this.sampleConfirmVisible = false
  1170. this.getDataList()
  1171. } else {
  1172. this.$message.error(data.msg || '样品确认失败')
  1173. }
  1174. }).catch(() => {
  1175. this.sampleConfirmLoading = false
  1176. this.$message.error('样品确认异常')
  1177. })
  1178. }
  1179. }
  1180. }
  1181. </script>
  1182. <style scoped>
  1183. .mod-config {
  1184. }
  1185. .el-form {
  1186. margin-bottom: 10px;
  1187. }
  1188. .dialog-footer {
  1189. text-align: center;
  1190. }
  1191. .empty-tip {
  1192. padding: 40px;
  1193. text-align: center;
  1194. color: #999;
  1195. font-size: 14px;
  1196. }
  1197. /* ==================== 页面专属样式 - 不影响全局 ==================== */
  1198. /* 查询表单样式 */
  1199. .exp-apply-page .query-form {
  1200. background-color: #FFFFFF;
  1201. padding: 15px 15px 5px 15px;
  1202. border-radius: 4px;
  1203. }
  1204. .exp-apply-page .query-form >>> .el-form-item__label {
  1205. color: #333333;
  1206. font-size: 13px;
  1207. padding-bottom: 5px;
  1208. }
  1209. .exp-apply-page .query-form >>> .el-input__inner {
  1210. height: 32px;
  1211. line-height: 32px;
  1212. border-radius: 4px;
  1213. border: 1px solid #DCDFE6;
  1214. font-size: 13px;
  1215. }
  1216. .exp-apply-page .query-form >>> .el-input__inner::placeholder {
  1217. color: #C0C4CC;
  1218. font-size: 13px;
  1219. }
  1220. .exp-apply-page .query-form >>> .el-select .el-input__inner {
  1221. border-radius: 4px;
  1222. }
  1223. .exp-apply-page .query-form >>> .el-date-editor .el-input__inner {
  1224. border-radius: 4px;
  1225. }
  1226. /* 按钮样式 - 扁平化风格 */
  1227. .exp-apply-page >>> .el-button {
  1228. height: 32px;
  1229. padding: 0 15px;
  1230. font-size: 13px;
  1231. border-radius: 4px;
  1232. }
  1233. /* 查询按钮 - 蓝色扁平 */
  1234. .exp-apply-page .search-btn {
  1235. background-color: #ECF5FF;
  1236. border-color: #B3D8FF;
  1237. color: #409EFF;
  1238. }
  1239. .exp-apply-page .search-btn:hover {
  1240. background-color: #409EFF;
  1241. border-color: #409EFF;
  1242. color: #FFFFFF;
  1243. }
  1244. /* 重置按钮 - 灰色扁平 */
  1245. .exp-apply-page .reset-btn {
  1246. background-color: #F5F7FA;
  1247. border-color: #D3D4D6;
  1248. color: #606266;
  1249. }
  1250. .exp-apply-page .reset-btn:hover {
  1251. background-color: #909399;
  1252. border-color: #909399;
  1253. color: #FFFFFF;
  1254. }
  1255. /* 新增按钮 - 绿色扁平 */
  1256. .exp-apply-page .add-btn {
  1257. background-color: #F0F9FF;
  1258. border-color: #C0E6C7;
  1259. color: #67C23A;
  1260. }
  1261. .exp-apply-page .add-btn:hover {
  1262. background-color: #67C23A;
  1263. border-color: #67C23A;
  1264. color: #FFFFFF;
  1265. }
  1266. /* 数据表格样式 */
  1267. .exp-apply-page .data-table {
  1268. background-color: #FFFFFF;
  1269. border-radius: 4px;
  1270. }
  1271. .exp-apply-page .data-table >>> .el-table__header-wrapper th {
  1272. background-color: #F5F7FA !important;
  1273. color: #333333;
  1274. font-weight: 600;
  1275. font-size: 16px;
  1276. border-color: #EBEEF5;
  1277. padding: 10px 0;
  1278. height: auto;
  1279. }
  1280. /* 固定列表头也使用相同背景色 */
  1281. .exp-apply-page .data-table >>> .el-table__fixed-header-wrapper th {
  1282. background-color: #F5F7FA !important;
  1283. color: #333333;
  1284. font-weight: 600;
  1285. font-size: 16px;
  1286. border-color: #EBEEF5;
  1287. padding: 12px 0;
  1288. height: auto;
  1289. }
  1290. .exp-apply-page .data-table >>> .el-table__header-wrapper .cell {
  1291. text-align: center;
  1292. padding: 0 10px;
  1293. //line-height: 2;
  1294. overflow: hidden;
  1295. text-overflow: ellipsis;
  1296. white-space: nowrap;
  1297. font-size: 13px !important;
  1298. }
  1299. .exp-apply-page .data-table >>> .el-table__fixed-header-wrapper .cell {
  1300. text-align: center;
  1301. padding: 0 10px;
  1302. //line-height: 2;
  1303. overflow: hidden;
  1304. text-overflow: ellipsis;
  1305. white-space: nowrap;
  1306. }
  1307. .exp-apply-page .data-table >>> .el-table__body-wrapper td {
  1308. border-color: #EBEEF5;
  1309. padding: 12px 0;
  1310. font-size: 16px;
  1311. color: #606266;
  1312. height: auto;
  1313. }
  1314. /* 固定列数据行也使用相同样式 */
  1315. .exp-apply-page .data-table >>> .el-table__fixed-body-wrapper td {
  1316. border-color: #EBEEF5;
  1317. padding: 12px 0;
  1318. font-size: 16px;
  1319. color: #606266;
  1320. height: auto;
  1321. }
  1322. .exp-apply-page .data-table >>> .el-table__body-wrapper .cell {
  1323. padding: 0 10px;
  1324. //line-height: 2;
  1325. overflow: hidden;
  1326. text-overflow: ellipsis;
  1327. white-space: nowrap;
  1328. font-size: 13px !important;
  1329. }
  1330. .exp-apply-page .data-table >>> .el-table__fixed-body-wrapper .cell {
  1331. padding: 0 10px;
  1332. //line-height: 2;
  1333. overflow: hidden;
  1334. text-overflow: ellipsis;
  1335. white-space: nowrap;
  1336. }
  1337. /* 操作列链接样式 - 统一蓝色样式 */
  1338. .exp-apply-page .data-table >>> .el-table__body-wrapper a {
  1339. text-decoration: none;
  1340. cursor: pointer;
  1341. font-size: 13px;
  1342. color: #409EFF !important;
  1343. white-space: nowrap;
  1344. }
  1345. .exp-apply-page .data-table >>> .el-table__body-wrapper a:hover {
  1346. color: #66B1FF !important;
  1347. text-decoration: underline;
  1348. }
  1349. .exp-apply-page .data-table >>> .el-table__body tr:hover > td {
  1350. background-color: #F5F7FA !important;
  1351. }
  1352. .exp-apply-page .data-table >>> .el-table__body tr.current-row > td {
  1353. background-color: #ECF5FF !important;
  1354. }
  1355. /* Tab样式优化 - 两栏布局 */
  1356. /* 两栏布局容器 */
  1357. .two-column-layout {
  1358. display: flex;
  1359. gap: 10px;
  1360. height: 100%;
  1361. }
  1362. /* 统一的列头样式 */
  1363. .column-header {
  1364. display: flex;
  1365. align-items: center;
  1366. gap: 6px;
  1367. font-size: 13px;
  1368. font-weight: 600;
  1369. color: #606266;
  1370. padding: 8px 12px;
  1371. background: #f5f7fa;
  1372. border-bottom: 2px solid #409EFF;
  1373. }
  1374. .column-header i {
  1375. color: #409EFF;
  1376. font-size: 14px;
  1377. }
  1378. .progress-badge {
  1379. margin-left: auto;
  1380. font-size: 14px;
  1381. color: #409EFF;
  1382. font-weight: bold;
  1383. }
  1384. /* ==================== 左栏:审批流程 ==================== */
  1385. .stages-column {
  1386. flex: 0 0 380px;
  1387. background: white;
  1388. border: 1px solid #e4e7ed;
  1389. border-radius: 6px;
  1390. overflow: hidden;
  1391. display: flex;
  1392. flex-direction: column;
  1393. }
  1394. .stages-list {
  1395. padding: 8px;
  1396. overflow-y: auto;
  1397. flex: 1;
  1398. }
  1399. .stage-item {
  1400. display: flex;
  1401. align-items: flex-start;
  1402. gap: 8px;
  1403. padding: 8px;
  1404. margin-bottom: 6px;
  1405. border-radius: 6px;
  1406. background: #fafafa;
  1407. transition: all 0.2s;
  1408. }
  1409. .stage-item:last-child {
  1410. margin-bottom: 0;
  1411. }
  1412. .stage-item:hover {
  1413. background: #f0f2f5;
  1414. transform: translateX(2px);
  1415. }
  1416. .stage-icon {
  1417. width: 28px;
  1418. height: 28px;
  1419. border-radius: 50%;
  1420. display: flex;
  1421. align-items: center;
  1422. justify-content: center;
  1423. font-size: 14px;
  1424. flex-shrink: 0;
  1425. margin-top: 2px;
  1426. }
  1427. .stage-pending .stage-icon {
  1428. background: #e8e8e8;
  1429. color: #909399;
  1430. }
  1431. .stage-current .stage-icon {
  1432. background: #409EFF;
  1433. color: white;
  1434. animation: pulse-stage 2s infinite;
  1435. }
  1436. @keyframes pulse-stage {
  1437. 0%, 100% {
  1438. box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.3);
  1439. }
  1440. 50% {
  1441. box-shadow: 0 0 0 5px rgba(64, 158, 255, 0.1);
  1442. }
  1443. }
  1444. .stage-completed .stage-icon {
  1445. background: #67C23A;
  1446. color: white;
  1447. }
  1448. .stage-rejected .stage-icon {
  1449. background: #F56C6C;
  1450. color: white;
  1451. }
  1452. .stage-content {
  1453. flex: 1;
  1454. min-width: 0;
  1455. }
  1456. .stage-name {
  1457. font-size: 13px;
  1458. font-weight: 600;
  1459. color: #303133;
  1460. margin-bottom: 5px;
  1461. }
  1462. .stage-meta {
  1463. display: flex;
  1464. align-items: center;
  1465. gap: 6px;
  1466. }
  1467. .stage-time {
  1468. font-size: 11px;
  1469. color: #909399;
  1470. }
  1471. .stage-current {
  1472. background: #ecf5ff !important;
  1473. border: 1px solid #b3d8ff;
  1474. }
  1475. .stage-completed {
  1476. background: #f0f9ff !important;
  1477. }
  1478. .stage-rejected {
  1479. background: #fef0f0 !important;
  1480. border: 1px solid #fbc4c4;
  1481. }
  1482. /* ==================== 右栏:审批日志(表格) ==================== */
  1483. .logs-column {
  1484. flex: 1;
  1485. background: white;
  1486. border: 1px solid #e4e7ed;
  1487. border-radius: 6px;
  1488. overflow: hidden;
  1489. display: flex;
  1490. flex-direction: column;
  1491. }
  1492. .logs-column .column-header {
  1493. display: flex;
  1494. justify-content: space-between;
  1495. align-items: center;
  1496. flex-shrink: 0;
  1497. }
  1498. .logs-count {
  1499. font-size: 11px;
  1500. color: #909399;
  1501. font-weight: normal;
  1502. margin-left: auto;
  1503. }
  1504. .logs-table-wrapper {
  1505. flex: 1;
  1506. overflow: hidden;
  1507. }
  1508. /* ==================== 审批日志表格专用样式 ==================== */
  1509. /* 表头样式 - 深灰色背景,白色文字 */
  1510. .approval-logs-table >>> .el-table__header-wrapper th,
  1511. .approval-logs-table >>> .el-table__header-wrapper .el-table__cell {
  1512. background-color: #F5F7FA !important;
  1513. color: #606266 !important;
  1514. font-size: 12px;
  1515. font-weight: 600;
  1516. }
  1517. /* 数据行样式 - 更高的行高防止遮挡 */
  1518. .approval-logs-table >>> .el-table__body-wrapper .el-table__row {
  1519. height: 40px !important;
  1520. }
  1521. .approval-logs-table >>> .el-table__body-wrapper td,
  1522. .approval-logs-table >>> .el-table__body-wrapper .el-table__cell {
  1523. height: 40px !important;
  1524. padding: 0 !important;
  1525. }
  1526. /* 关键修复:cell容器要有足够的padding和overflow可见 */
  1527. .approval-logs-table >>> .el-table__body-wrapper .cell {
  1528. padding: 1px 12px !important;
  1529. line-height: 24px !important;
  1530. overflow: visible !important;
  1531. height: auto !important;
  1532. }
  1533. /* 标签样式 - 确保不被遮挡 */
  1534. .approval-logs-table >>> .el-tag {
  1535. vertical-align: middle !important;
  1536. display: inline-block !important;
  1537. margin: 2px 0 !important;
  1538. }
  1539. /* 悬停效果 */
  1540. .approval-logs-table >>> .el-table__body tr:hover > td {
  1541. background-color: #f5f7fa !important;
  1542. }
  1543. /* 空状态 */
  1544. .empty-tip {
  1545. display: flex;
  1546. flex-direction: column;
  1547. align-items: center;
  1548. justify-content: center;
  1549. padding: 50px 20px;
  1550. color: #909399;
  1551. }
  1552. .empty-tip p {
  1553. margin: 0;
  1554. }
  1555. /deep/ .manager-select .el-input__inner {
  1556. height: 30px !important;
  1557. line-height: 30px !important;
  1558. }
  1559. </style>