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.

1016 lines
23 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. <template>
  2. <div class="tri-confirm-container">
  3. <!-- 页面标题和统计 -->
  4. <div class="page-header">
  5. <div class="header-left">
  6. <h2 class="page-title">
  7. <i class="el-icon-s-check"></i> 三方确认
  8. </h2>
  9. <p class="page-subtitle">High Risk 生产质量技术三方确认管理</p>
  10. </div>
  11. <div class="header-right">
  12. <div class="stat-cards">
  13. <div class="stat-card stat-total">
  14. <div class="stat-icon"><i class="el-icon-document"></i></div>
  15. <div class="stat-content">
  16. <div class="stat-value">{{ dataList.length }}</div>
  17. <div class="stat-label">待确认工序</div>
  18. </div>
  19. </div>
  20. <div class="stat-card stat-can-confirm">
  21. <div class="stat-icon"><i class="el-icon-success"></i></div>
  22. <div class="stat-content">
  23. <div class="stat-value">{{ canConfirmCount }}</div>
  24. <div class="stat-label">可立即确认</div>
  25. </div>
  26. </div>
  27. <div class="stat-card stat-waiting">
  28. <div class="stat-icon"><i class="el-icon-time"></i></div>
  29. <div class="stat-content">
  30. <div class="stat-value">{{ waitingCount }}</div>
  31. <div class="stat-label">等待上道工序</div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. <!-- 查询条件表单 -->
  38. <div class="search-section">
  39. <el-collapse class="no-arrow" v-model="searchExpanded">
  40. <el-collapse-item name="1">
  41. <template slot="title">
  42. <i class="el-icon-search"></i>
  43. <span style="margin-left: 8px; font-weight: 500;">筛选条件</span>
  44. </template>
  45. <el-form :inline="true" label-position="top" class="search-form">
  46. <el-form-item label="申请单号">
  47. <el-input v-model="queryData.applyNo" placeholder="支持模糊查询" clearable style="width: 150px"></el-input>
  48. </el-form-item>
  49. <el-form-item label="试验类型">
  50. <el-select v-model="queryData.experimentType" placeholder="请选择" clearable style="width: 120px">
  51. <el-option label="全部" value=""></el-option>
  52. <el-option label="High Risk" value="High Risk"></el-option>
  53. <el-option label="Low Risk" value="Low Risk"></el-option>
  54. </el-select>
  55. </el-form-item>
  56. <el-form-item label="试验名称">
  57. <el-input v-model="queryData.title" placeholder="支持模糊查询" clearable style="width: 150px"></el-input>
  58. </el-form-item>
  59. <el-form-item label=" ">
  60. <el-button @click="handleQuery" type="primary" icon="el-icon-search">查询</el-button>
  61. <el-button @click="resetQuery" type="default" icon="el-icon-refresh-left">重置</el-button>
  62. </el-form-item>
  63. </el-form>
  64. </el-collapse-item>
  65. </el-collapse>
  66. </div>
  67. <!-- 卡片列表 -->
  68. <div class="cards-container" v-loading="dataListLoading">
  69. <transition-group name="card-list" tag="div" class="cards-grid">
  70. <div
  71. v-for="item in filteredDataList"
  72. :key="item.applyNo + '-' + item.processStep"
  73. class="confirm-card"
  74. :class="{'high-risk': item.experimentType === 'High Risk', 'disabled': !item.canConfirm}">
  75. <!-- 卡片头部 -->
  76. <div class="card-header">
  77. <div class="card-title-area">
  78. <el-tag
  79. :type="item.experimentType === 'High Risk' ? 'danger' : 'success'"
  80. size="middle"
  81. effect="dark"
  82. class="risk-tag">
  83. <i :class="item.experimentType === 'High Risk' ? 'el-icon-warning' : 'el-icon-success'"></i>
  84. {{ item.experimentType }}
  85. </el-tag>
  86. <span class="apply-no">{{ item.applyNo }}</span>
  87. </div>
  88. <el-tag
  89. :type="getRoleTagType(item.currentRole)"
  90. size="middle"
  91. effect="plain"
  92. class="role-tag">
  93. {{ getRoleName(item.currentRole) }}
  94. </el-tag>
  95. </div>
  96. <!-- 卡片主体内容 -->
  97. <div class="card-body">
  98. <h3 class="experiment-title">
  99. <i class="el-icon-document"></i>
  100. {{ item.title }}
  101. </h3>
  102. <div class="process-info">
  103. <div class="process-badge">
  104. <i class="el-icon-s-operation"></i>
  105. <span class="process-label">工序 {{ item.processSeq }}</span>
  106. </div>
  107. <div class="process-name">{{ item.processStep }}</div>
  108. </div>
  109. <div class="card-details">
  110. <div class="detail-row">
  111. <span class="detail-label">
  112. <i class="el-icon-box"></i>
  113. 项目编号
  114. </span>
  115. <span class="detail-value">{{ item.projectNo || '-' }}</span>
  116. </div>
  117. <div class="detail-row">
  118. <span class="detail-label">
  119. <i class="el-icon-goods"></i>
  120. 产品型号
  121. </span>
  122. <span class="detail-value" :title="item.productType">{{ item.productType || '-' }}</span>
  123. </div>
  124. <div class="detail-row">
  125. <span class="detail-label">
  126. <i class="el-icon-user"></i>
  127. 申请人
  128. </span>
  129. <span class="detail-value">{{ item.creatorName }}</span>
  130. </div>
  131. </div>
  132. <!-- 三方确认状态 -->
  133. <div class="tri-status">
  134. <div class="status-item" :class="{'confirmed': item.prodConfirmed}">
  135. <i :class="item.prodConfirmed ? 'el-icon-circle-check' : 'el-icon-remove-outline'"></i>
  136. <span>生产</span>
  137. </div>
  138. <div class="status-item" :class="{'confirmed': item.qaConfirmed}">
  139. <i :class="item.qaConfirmed ? 'el-icon-circle-check' : 'el-icon-remove-outline'"></i>
  140. <span>质量</span>
  141. </div>
  142. <div class="status-item" :class="{'confirmed': item.techConfirmed}">
  143. <i :class="item.techConfirmed ? 'el-icon-circle-check' : 'el-icon-remove-outline'"></i>
  144. <span>技术</span>
  145. </div>
  146. </div>
  147. </div>
  148. <!-- 卡片底部 -->
  149. <div class="card-footer">
  150. <div class="status-tip" v-if="!item.canConfirm">
  151. <i class="el-icon-time"></i>
  152. 等待第{{ item.processSeq - 1 }}道工序完成
  153. </div>
  154. <div class="status-tip ready" v-else>
  155. <i class="el-icon-success"></i>
  156. 可以确认
  157. </div>
  158. <div class="action-buttons">
  159. <el-button
  160. type="success"
  161. size="small"
  162. plain
  163. icon="el-icon-check"
  164. class="confirm-btn"
  165. :disabled="!item.canConfirm"
  166. @click="openConfirmDialog(item)">
  167. 确认
  168. </el-button>
  169. </div>
  170. </div>
  171. </div>
  172. </transition-group>
  173. <!-- 空状态 -->
  174. <div v-if="!dataListLoading && filteredDataList.length === 0" class="empty-state">
  175. <i class="el-icon-document-checked empty-icon"></i>
  176. <p class="empty-text">暂无待确认工序</p>
  177. <p class="empty-subtext">您已完成所有三方确认任务</p>
  178. </div>
  179. </div>
  180. <!-- 确认弹窗 -->
  181. <el-dialog
  182. :title="'三方确认 - ' + getRoleName(confirmData.roleType)"
  183. :visible.sync="confirmDialogVisible"
  184. width="700px"
  185. :close-on-click-modal="false"
  186. v-drag>
  187. <!-- 申请单和工序信息 -->
  188. <div class="confirm-info">
  189. <el-descriptions :column="2" border size="small">
  190. <el-descriptions-item label="申请单号">{{ confirmData.applyNo }}</el-descriptions-item>
  191. <el-descriptions-item label="试验类型">
  192. <el-tag :type="confirmData.experimentType === 'High Risk' ? 'danger' : 'success'" size="small">
  193. {{ confirmData.experimentType }}
  194. </el-tag>
  195. </el-descriptions-item>
  196. <el-descriptions-item label="试验名称" :span="2">{{ confirmData.title }}</el-descriptions-item>
  197. <el-descriptions-item label="工序顺序"> {{ confirmData.processSeq }} </el-descriptions-item>
  198. <el-descriptions-item label="工序名称">{{ confirmData.processStep }}</el-descriptions-item>
  199. </el-descriptions>
  200. </div>
  201. <!-- 确认表单 -->
  202. <el-divider content-position="left">确认信息</el-divider>
  203. <el-form :model="confirmData" label-position="top" style="margin-left: 5px; margin-top: -5px;">
  204. <el-row :gutter="20">
  205. <el-col :span="12">
  206. <el-form-item label="确认角色">
  207. <el-tag :type="getRoleTagType(confirmData.roleType)" size="medium">
  208. {{ getRoleName(confirmData.roleType) }}
  209. </el-tag>
  210. </el-form-item>
  211. </el-col>
  212. <el-col :span="12">
  213. <el-form-item label="样品是否OK" required>
  214. <el-radio-group v-model="confirmData.sampleOk">
  215. <el-radio :label="true">OK</el-radio>
  216. <el-radio :label="false">NG</el-radio>
  217. </el-radio-group>
  218. </el-form-item>
  219. </el-col>
  220. </el-row>
  221. <el-row :gutter="20">
  222. <el-col :span="24">
  223. <el-form-item label="确认意见" required>
  224. <el-input
  225. v-model="confirmData.comment"
  226. type="textarea"
  227. :rows="3"
  228. placeholder="请输入确认意见"
  229. maxlength="500"
  230. show-word-limit>
  231. </el-input>
  232. </el-form-item>
  233. </el-col>
  234. </el-row>
  235. </el-form>
  236. <el-footer style="height: 40px; margin-top: 60px; text-align: center">
  237. <el-button type="success" @click="doConfirm" :loading="confirmLoading" icon="el-icon-check">
  238. {{ confirmLoading ? '确认中...' : '确认提交' }}
  239. </el-button>
  240. <el-button type="primary" @click="confirmDialogVisible = false" :disabled="confirmLoading">关闭</el-button>
  241. </el-footer>
  242. </el-dialog>
  243. </div>
  244. </template>
  245. <script>
  246. import { getPendingTriConfirmList, confirmTriApproval } from '@/api/erf/erf'
  247. export default {
  248. name: 'TriConfirm',
  249. data() {
  250. return {
  251. searchExpanded: ['0'], // 搜索条件默认展开
  252. // 查询条件
  253. queryData: {
  254. applyNo: '',
  255. experimentType: '',
  256. title: ''
  257. },
  258. // 数据列表
  259. dataList: [],
  260. filteredDataList: [],
  261. dataListLoading: false,
  262. // 确认弹窗
  263. confirmDialogVisible: false,
  264. confirmData: {
  265. applyNo: '',
  266. processStep: '',
  267. processSeq: null,
  268. roleType: '',
  269. sampleOk: true,
  270. comment: '',
  271. approverUserId: this.$store.state.user.id,
  272. approverName: this.$store.state.user.name,
  273. // 用于展示的信息
  274. experimentType: '',
  275. title: ''
  276. },
  277. confirmLoading: false
  278. }
  279. },
  280. computed: {
  281. /**
  282. * 计算可立即确认的数量
  283. */
  284. canConfirmCount() {
  285. return this.filteredDataList.filter(item => item.canConfirm).length
  286. },
  287. /**
  288. * 计算等待上道工序的数量
  289. */
  290. waitingCount() {
  291. return this.filteredDataList.filter(item => !item.canConfirm).length
  292. }
  293. },
  294. activated() {
  295. this.getDataList()
  296. },
  297. methods: {
  298. /**
  299. * 获取待确认列表
  300. */
  301. getDataList() {
  302. this.dataListLoading = true
  303. const params = {
  304. currentUserId: this.$store.state.user.id,
  305. site: this.$store.state.user.site
  306. }
  307. getPendingTriConfirmList(params).then(({data}) => {
  308. this.dataListLoading = false
  309. if (data && data.code === 0) {
  310. this.dataList = data.list || []
  311. this.filterData() // 应用筛选条件
  312. } else {
  313. this.dataList = []
  314. this.filteredDataList = []
  315. this.$message.error(data.msg || '查询失败')
  316. }
  317. }).catch(error => {
  318. this.dataListLoading = false
  319. this.$message.error('查询异常')
  320. })
  321. },
  322. /**
  323. * 处理查询按钮点击重新从后端获取数据
  324. */
  325. handleQuery() {
  326. this.getDataList()
  327. },
  328. /**
  329. * 筛选数据前端筛选
  330. */
  331. filterData() {
  332. let result = [...this.dataList]
  333. // 申请单号筛选
  334. if (this.queryData.applyNo) {
  335. const keyword = this.queryData.applyNo.toLowerCase()
  336. result = result.filter(item =>
  337. item.applyNo && item.applyNo.toLowerCase().includes(keyword)
  338. )
  339. }
  340. // 试验类型筛选
  341. if (this.queryData.experimentType) {
  342. result = result.filter(item => item.experimentType === this.queryData.experimentType)
  343. }
  344. // 试验名称筛选
  345. if (this.queryData.title) {
  346. const keyword = this.queryData.title.toLowerCase()
  347. result = result.filter(item =>
  348. item.title && item.title.toLowerCase().includes(keyword)
  349. )
  350. }
  351. this.filteredDataList = result
  352. },
  353. /**
  354. * 重置查询条件
  355. */
  356. resetQuery() {
  357. this.queryData.applyNo = ''
  358. this.queryData.experimentType = ''
  359. this.queryData.title = ''
  360. this.getDataList()
  361. },
  362. /**
  363. * 打开确认对话框
  364. */
  365. openConfirmDialog(item) {
  366. if (!item.canConfirm) {
  367. this.$message.warning(`请等待第${item.processSeq - 1}道工序完成三方确认后再操作`)
  368. return
  369. }
  370. this.confirmData = {
  371. applyNo: item.applyNo,
  372. processStep: item.processStep,
  373. processSeq: item.processSeq,
  374. roleType: item.currentRole,
  375. sampleOk: true,
  376. comment: '',
  377. approverUserId: this.$store.state.user.id,
  378. approverName: this.$store.state.user.name,
  379. // 用于展示的信息
  380. experimentType: item.experimentType,
  381. title: item.title
  382. }
  383. this.confirmDialogVisible = true
  384. },
  385. /**
  386. * 执行确认
  387. */
  388. doConfirm() {
  389. if ((!this.confirmData.comment || this.confirmData.comment.trim() === '') && !this.confirmData.sampleOk) {
  390. this.$message.warning('请输入确认意见')
  391. return
  392. }
  393. this.confirmLoading = true
  394. confirmTriApproval(this.confirmData).then(({data}) => {
  395. this.confirmLoading = false
  396. if (data && data.code === 0) {
  397. this.$message.success('确认成功')
  398. this.confirmDialogVisible = false
  399. this.getDataList() // 重新加载列表
  400. } else {
  401. this.$message.error(data.msg || '确认失败')
  402. }
  403. }).catch(error => {
  404. this.confirmLoading = false
  405. this.$message.error('确认异常')
  406. })
  407. },
  408. /**
  409. * 获取角色名称
  410. */
  411. getRoleName(roleType) {
  412. const names = {
  413. 'PROD': '生产负责人',
  414. 'QA': '质量负责人',
  415. 'TECH': '技术负责人'
  416. }
  417. return names[roleType] || roleType
  418. },
  419. /**
  420. * 获取角色标签类型
  421. */
  422. getRoleTagType(roleType) {
  423. const types = {
  424. 'PROD': 'warning',
  425. 'QA': 'success',
  426. 'TECH': 'primary'
  427. }
  428. return types[roleType] || 'info'
  429. }
  430. }
  431. }
  432. </script>
  433. <style scoped>
  434. /* 整体容器 */
  435. .tri-confirm-container {
  436. padding: 15px;
  437. background: #f5f7fa;
  438. min-height: calc(100vh - 80px);
  439. }
  440. /* ===== 页面头部 ===== */
  441. .page-header {
  442. display: flex;
  443. justify-content: space-between;
  444. align-items: center;
  445. margin-bottom: 15px;
  446. padding: 5px 20px;
  447. background: #FFFFFF;
  448. border-radius: 4px;
  449. border: 1px solid #EBEEF5;
  450. }
  451. .header-left {
  452. color: #303133;
  453. }
  454. .page-title {
  455. margin: 0;
  456. font-size: 20px;
  457. font-weight: 600;
  458. color: #303133;
  459. display: flex;
  460. align-items: center;
  461. gap: 10px;
  462. }
  463. .page-title i {
  464. font-size: 22px;
  465. color: #409EFF;
  466. }
  467. .page-subtitle {
  468. margin: 6px 0 0 0;
  469. font-size: 13px;
  470. color: #909399;
  471. }
  472. .header-right {
  473. display: flex;
  474. gap: 12px;
  475. }
  476. /* ===== 统计卡片 ===== */
  477. .stat-cards {
  478. display: flex;
  479. gap: 12px;
  480. }
  481. .stat-card {
  482. display: flex;
  483. align-items: center;
  484. gap: 12px;
  485. padding: 12px 20px;
  486. background: #FFFFFF;
  487. border-radius: 4px;
  488. border: 1px solid #EBEEF5;
  489. transition: all 0.3s ease;
  490. cursor: pointer;
  491. min-width: 140px;
  492. }
  493. .stat-card:hover {
  494. border-color: #409EFF;
  495. box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
  496. }
  497. .stat-icon {
  498. width: 40px;
  499. height: 40px;
  500. display: flex;
  501. align-items: center;
  502. justify-content: center;
  503. border-radius: 4px;
  504. font-size: 20px;
  505. color: white;
  506. }
  507. .stat-total .stat-icon {
  508. background: #409EFF;
  509. }
  510. .stat-can-confirm .stat-icon {
  511. background: #67C23A;
  512. }
  513. .stat-waiting .stat-icon {
  514. background: #E6A23C;
  515. }
  516. .stat-content {
  517. display: flex;
  518. flex-direction: column;
  519. }
  520. .stat-value {
  521. font-size: 24px;
  522. font-weight: 600;
  523. color: #303133;
  524. line-height: 1;
  525. }
  526. .stat-label {
  527. font-size: 12px;
  528. color: #909399;
  529. margin-top: 4px;
  530. }
  531. /* ===== 搜索区域 ===== */
  532. .search-section {
  533. margin-bottom: 15px;
  534. background: white;
  535. border-radius: 4px;
  536. border: 1px solid #EBEEF5;
  537. overflow: hidden;
  538. }
  539. .search-section >>> .el-collapse-item__header {
  540. padding: 0 15px;
  541. height: 45px;
  542. line-height: 45px;
  543. background: white;
  544. border-bottom: 1px solid #ebeef5;
  545. font-size: 13px;
  546. color: #303133;
  547. font-weight: 500;
  548. }
  549. .search-section >>> .el-collapse-item__content {
  550. padding: 15px;
  551. background: #FFFFFF;
  552. }
  553. .search-form {
  554. margin: 0;
  555. }
  556. .search-form >>> .el-form-item {
  557. margin-bottom: 10px;
  558. }
  559. /* ===== 卡片容器 ===== */
  560. .cards-container {
  561. min-height: 400px;
  562. position: relative;
  563. }
  564. .cards-grid {
  565. display: grid;
  566. grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  567. gap: 15px;
  568. margin-bottom: 15px;
  569. }
  570. /* ===== 确认卡片 ===== */
  571. .confirm-card {
  572. background: white;
  573. border-radius: 4px;
  574. padding: 12px;
  575. border: 1px solid #EBEEF5;
  576. transition: all 0.3s ease;
  577. cursor: pointer;
  578. position: relative;
  579. }
  580. .confirm-card::before {
  581. content: '';
  582. position: absolute;
  583. top: 0;
  584. left: 0;
  585. width: 1px;
  586. height: 100%;
  587. background: #67C23A;
  588. transition: all 0.3s ease;
  589. border-radius: 4px 0 0 4px;
  590. }
  591. .confirm-card.high-risk::before {
  592. background: #F56C6C;
  593. }
  594. .confirm-card.disabled {
  595. opacity: 0.7;
  596. cursor: not-allowed;
  597. }
  598. .confirm-card.disabled::before {
  599. background: #909399;
  600. }
  601. .confirm-card:hover:not(.disabled) {
  602. border-color: #409EFF;
  603. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
  604. }
  605. /* 卡片头部 */
  606. .card-header {
  607. display: flex;
  608. justify-content: space-between;
  609. align-items: center;
  610. margin-bottom: 4px;
  611. padding-bottom: 6px;
  612. border-bottom: 1px solid #EBEEF5;
  613. }
  614. .card-title-area {
  615. display: flex;
  616. align-items: center;
  617. gap: 10px;
  618. }
  619. .risk-tag {
  620. font-weight: 500;
  621. padding: 0px 12px;
  622. font-size: 13px;
  623. }
  624. .risk-tag i {
  625. margin-right: 4px;
  626. }
  627. .apply-no {
  628. font-size: 13px;
  629. color: #606266;
  630. font-family: 'Courier New', monospace;
  631. font-weight: 500;
  632. }
  633. .role-tag {
  634. font-weight: 500;
  635. padding: 0px 12px;
  636. font-size: 13px;
  637. }
  638. /* 卡片主体 */
  639. .card-body {
  640. margin-bottom: 4px;
  641. }
  642. .experiment-title {
  643. font-size: 15px;
  644. font-weight: 600;
  645. color: #303133;
  646. margin: 0 0 4px 0;
  647. line-height: 1.5;
  648. display: flex;
  649. align-items: flex-start;
  650. gap: 6px;
  651. }
  652. .experiment-title i {
  653. color: #409EFF;
  654. margin-top: 2px;
  655. font-size: 16px;
  656. }
  657. .process-info {
  658. display: flex;
  659. align-items: center;
  660. gap: 12px;
  661. margin-bottom: 6px;
  662. padding: 10px;
  663. border-radius: 4px;
  664. background: #F5F7FA;
  665. //border-radius: 3px;
  666. transition: all 0.2s ease;
  667. }
  668. .process-badge {
  669. display: flex;
  670. align-items: center;
  671. gap: 6px;
  672. font-size: 14px;
  673. font-weight: 600;
  674. }
  675. .process-badge i {
  676. font-size: 16px;
  677. }
  678. .process-name {
  679. flex: 1;
  680. font-size: 14px;
  681. font-weight: 500;
  682. }
  683. .card-details {
  684. display: flex;
  685. flex-direction: column;
  686. gap: 8px;
  687. margin-bottom: 6px;
  688. }
  689. .detail-row {
  690. display: flex;
  691. justify-content: space-between;
  692. align-items: center;
  693. font-size: 13px;
  694. padding: 6px 10px;
  695. background: #F5F7FA;
  696. border-radius: 3px;
  697. transition: all 0.2s ease;
  698. }
  699. .detail-row:hover {
  700. background: #ECF5FF;
  701. }
  702. .detail-label {
  703. color: #909399;
  704. display: flex;
  705. align-items: center;
  706. gap: 5px;
  707. font-weight: 500;
  708. }
  709. .detail-label i {
  710. color: #409EFF;
  711. font-size: 14px;
  712. }
  713. .detail-value {
  714. color: #606266;
  715. font-weight: 500;
  716. text-align: right;
  717. max-width: 200px;
  718. overflow: hidden;
  719. text-overflow: ellipsis;
  720. white-space: nowrap;
  721. }
  722. /* 三方确认状态 */
  723. .tri-status {
  724. display: flex;
  725. justify-content: space-around;
  726. padding: 5px;
  727. background: #F5F7FA;
  728. border-radius: 4px;
  729. }
  730. .status-item {
  731. display: flex;
  732. flex-direction: column;
  733. align-items: center;
  734. gap: 6px;
  735. color: #909399;
  736. font-size: 13px;
  737. transition: all 0.3s ease;
  738. }
  739. .status-item i {
  740. font-size: 24px;
  741. }
  742. .status-item.confirmed {
  743. color: #67C23A;
  744. }
  745. .status-item.confirmed i {
  746. animation: checkBounce 0.6s ease;
  747. }
  748. @keyframes checkBounce {
  749. 0%, 100% { transform: scale(1); }
  750. 50% { transform: scale(1.2); }
  751. }
  752. /* 卡片底部 */
  753. .card-footer {
  754. display: flex;
  755. justify-content: space-between;
  756. align-items: center;
  757. padding-top: 6px;
  758. border-top: 1px solid #EBEEF5;
  759. }
  760. .status-tip {
  761. font-size: 12px;
  762. color: #E6A23C;
  763. display: flex;
  764. align-items: center;
  765. gap: 5px;
  766. font-weight: 500;
  767. }
  768. .status-tip i {
  769. font-size: 14px;
  770. }
  771. .status-tip.ready {
  772. color: #67C23A;
  773. }
  774. .action-buttons {
  775. display: flex;
  776. gap: 8px;
  777. }
  778. .confirm-btn {
  779. background: #f0f9ff;
  780. border-color: #b3e19d;
  781. color: #67C23A;
  782. font-weight: 500;
  783. padding: 7px 20px;
  784. font-size: 13px;
  785. transition: all 0.3s ease;
  786. }
  787. .confirm-btn:hover:not(:disabled) {
  788. background: #67C23A;
  789. border-color: #67C23A;
  790. color: #FFFFFF;
  791. }
  792. .confirm-btn:disabled {
  793. opacity: 0.5;
  794. cursor: not-allowed;
  795. }
  796. /* ===== 空状态 ===== */
  797. .empty-state {
  798. text-align: center;
  799. padding: 80px 20px;
  800. background: white;
  801. border-radius: 4px;
  802. border: 1px solid #EBEEF5;
  803. }
  804. .empty-icon {
  805. font-size: 64px;
  806. color: #c0c4cc;
  807. margin-bottom: 12px;
  808. }
  809. .empty-text {
  810. font-size: 15px;
  811. color: #606266;
  812. margin: 0 0 6px 0;
  813. font-weight: 500;
  814. }
  815. .empty-subtext {
  816. font-size: 13px;
  817. color: #909399;
  818. margin: 0;
  819. }
  820. /* ===== 卡片动画 ===== */
  821. .card-list-enter-active {
  822. animation: cardFadeIn 0.4s ease;
  823. }
  824. .card-list-leave-active {
  825. animation: cardFadeOut 0.3s ease;
  826. }
  827. @keyframes cardFadeIn {
  828. from {
  829. opacity: 0;
  830. transform: translateY(15px);
  831. }
  832. to {
  833. opacity: 1;
  834. transform: translateY(0);
  835. }
  836. }
  837. @keyframes cardFadeOut {
  838. from {
  839. opacity: 1;
  840. transform: scale(1);
  841. }
  842. to {
  843. opacity: 0;
  844. transform: scale(0.95);
  845. }
  846. }
  847. /* ===== 弹窗样式 ===== */
  848. .confirm-info {
  849. margin-bottom: 20px;
  850. }
  851. .confirm-info >>> .el-descriptions {
  852. font-size: 13px;
  853. }
  854. /* ===== 响应式设计 ===== */
  855. @media screen and (max-width: 1600px) {
  856. .cards-grid {
  857. grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
  858. }
  859. }
  860. @media screen and (max-width: 1200px) {
  861. .cards-grid {
  862. grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  863. }
  864. .page-header {
  865. flex-direction: column;
  866. gap: 15px;
  867. align-items: flex-start;
  868. }
  869. .stat-cards {
  870. width: 100%;
  871. overflow-x: auto;
  872. }
  873. }
  874. @media screen and (max-width: 768px) {
  875. .cards-grid {
  876. grid-template-columns: 1fr;
  877. }
  878. .stat-cards {
  879. flex-direction: column;
  880. }
  881. .stat-card {
  882. width: 100%;
  883. }
  884. }
  885. /deep/ .no-arrow .el-collapse-item__header .el-collapse-item__arrow {
  886. display: none !important;
  887. }
  888. </style>