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.

1106 lines
28 KiB

3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
1 month ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <!-- 原材料清单组件 -->
  3. <div class="raw-material-container">
  4. <!-- 工具栏 -->
  5. <div class="toolbar-row">
  6. <div class="toolbar-left">
  7. <el-button
  8. type="primary"
  9. size="small"
  10. class="add-btn"
  11. v-if="canEdit"
  12. @click="openAddDialog">
  13. 新增物料
  14. </el-button>
  15. <el-button
  16. type="primary"
  17. size="small"
  18. class="reset-btn"
  19. v-if="canEdit"
  20. :disabled="selectedRows.length === 0"
  21. @click="batchDeleteRawMaterial">
  22. 批量删除
  23. </el-button>
  24. <span
  25. v-if="!disabled && !canEdit"
  26. style="margin-left: 8px; color: #909399; font-size: 12px;">
  27. 仅试验负责人可维护原材料清单
  28. </span>
  29. <span
  30. v-if="canEdit && shouldRecordChangeLog"
  31. style="margin-left: 8px; color: #E6A23C; font-size: 12px;">
  32. 当前为非草稿状态原材料增删改将自动记录详细修改日志可点击右上角修改记录查看
  33. </span>
  34. </div>
  35. <div class="toolbar-right" v-if="applyNo">
  36. <el-tooltip content="查看原材料修改记录" placement="top">
  37. <el-badge
  38. :value="changeLogList.length"
  39. :max="99"
  40. :hidden="changeLogList.length === 0"
  41. class="change-log-badge">
  42. <el-button
  43. type="warning"
  44. plain
  45. size="small"
  46. icon="el-icon-tickets"
  47. @click="openChangeLogDrawer">
  48. 修改记录
  49. </el-button>
  50. </el-badge>
  51. </el-tooltip>
  52. </div>
  53. </div>
  54. <!-- 数据表格 -->
  55. <el-table
  56. ref="rawMaterialTable"
  57. :key="`raw-material-${applyNo || 'empty'}-${canEdit ? 'edit' : 'view'}`"
  58. :data="rawMaterialList"
  59. v-loading="tableLoading"
  60. border
  61. :height="260"
  62. @selection-change="handleSelectionChange"
  63. style="width: 100%">
  64. <!-- 多选框 -->
  65. <el-table-column
  66. type="selection"
  67. width="55"
  68. align="center"
  69. v-if="canEdit">
  70. </el-table-column>
  71. <!-- 序号 -->
  72. <el-table-column
  73. type="index"
  74. label="序号"
  75. width="60"
  76. align="center">
  77. </el-table-column>
  78. <!-- 物料编码 -->
  79. <el-table-column
  80. prop="partNo"
  81. label="物料编码"
  82. min-width="150"
  83. align="center"
  84. header-align="center"
  85. show-overflow-tooltip>
  86. <template slot-scope="scope">
  87. {{ scope.row.partNo || '-' }}
  88. </template>
  89. </el-table-column>
  90. <!-- 物料描述 -->
  91. <el-table-column
  92. prop="partDesc"
  93. label="物料描述"
  94. min-width="200"
  95. align="center"
  96. header-align="center"
  97. show-overflow-tooltip>
  98. </el-table-column>
  99. <!-- 工序 -->
  100. <el-table-column
  101. prop="processStep"
  102. label="工序"
  103. min-width="140"
  104. align="center"
  105. header-align="center"
  106. show-overflow-tooltip>
  107. <template slot-scope="scope">
  108. {{ scope.row.processStep || '-' }}
  109. </template>
  110. </el-table-column>
  111. <!-- 数量 -->
  112. <el-table-column
  113. prop="quantity"
  114. label="数量"
  115. width="120"
  116. align="center"
  117. header-align="center">
  118. </el-table-column>
  119. <!-- 单位 -->
  120. <el-table-column
  121. prop="umid"
  122. label="单位"
  123. width="80"
  124. align="center"
  125. header-align="center">
  126. <template slot-scope="scope">
  127. {{ scope.row.umid || '-' }}
  128. </template>
  129. </el-table-column>
  130. <!-- 备注 -->
  131. <el-table-column
  132. prop="remark"
  133. label="备注"
  134. min-width="150"
  135. align="left"
  136. header-align="center"
  137. show-overflow-tooltip>
  138. <template slot-scope="scope">
  139. {{ scope.row.remark || '-' }}
  140. </template>
  141. </el-table-column>
  142. <!-- 操作列 -->
  143. <el-table-column
  144. label="操作"
  145. width="150"
  146. align="center"
  147. header-align="center"
  148. v-if="canEdit">
  149. <template slot-scope="scope">
  150. <el-link
  151. style="cursor:pointer; margin-right: 10px;"
  152. @click="openEditDialog(scope.row)">
  153. 修改
  154. </el-link>
  155. <el-link
  156. style="cursor:pointer; color: #F56C6C;"
  157. @click="deleteRawMaterial(scope.row)">
  158. 删除
  159. </el-link>
  160. </template>
  161. </el-table-column>
  162. </el-table>
  163. <!-- 原材料修改记录抽屉 -->
  164. <el-drawer
  165. title="原材料修改记录"
  166. :visible.sync="changeLogDrawerVisible"
  167. :append-to-body="true"
  168. size="55%">
  169. <div class="change-log-drawer-body">
  170. <div class="change-log-drawer-toolbar">
  171. <span class="change-log-count"> {{ changeLogList.length }} </span>
  172. <el-button
  173. type="text"
  174. size="small"
  175. :loading="changeLogLoading"
  176. @click="loadChangeLogList">
  177. 刷新
  178. </el-button>
  179. </div>
  180. <el-table
  181. :data="changeLogList"
  182. v-loading="changeLogLoading"
  183. border
  184. size="small"
  185. class="change-log-table"
  186. height="68vh"
  187. style="width: 100%">
  188. <el-table-column type="expand" width="50">
  189. <template slot-scope="scope">
  190. <div class="log-detail-wrapper">
  191. <div class="log-detail-item">
  192. <span class="log-detail-label">详细说明</span>
  193. <pre class="log-detail-pre">{{ scope.row.detailContent || '-' }}</pre>
  194. </div>
  195. <!-- <div class="log-detail-item" v-if="scope.row.beforeContent">
  196. <span class="log-detail-label">修改前快照</span>
  197. <pre class="log-detail-pre">{{ scope.row.beforeContent }}</pre>
  198. </div>
  199. <div class="log-detail-item" v-if="scope.row.afterContent">
  200. <span class="log-detail-label">修改后快照</span>
  201. <pre class="log-detail-pre">{{ scope.row.afterContent }}</pre>
  202. </div>-->
  203. </div>
  204. </template>
  205. </el-table-column>
  206. <el-table-column
  207. prop="createdDate"
  208. label="时间"
  209. width="160"
  210. align="center"
  211. header-align="center">
  212. <template slot-scope="scope">
  213. {{ formatDateTime(scope.row.createdDate) }}
  214. </template>
  215. </el-table-column>
  216. <el-table-column
  217. prop="operationType"
  218. label="操作类型"
  219. width="90"
  220. align="center"
  221. class-name="operation-type-cell"
  222. header-align="center">
  223. <template slot-scope="scope">
  224. <span
  225. :class="['operation-type-pill', getOperationTypeClass(scope.row.operationType)]">
  226. {{ formatOperationType(scope.row.operationType) || '-' }}
  227. </span>
  228. </template>
  229. </el-table-column>
  230. <el-table-column
  231. prop="operatorDisplayName"
  232. label="操作人"
  233. width="120"
  234. align="center"
  235. header-align="center">
  236. <template slot-scope="scope">
  237. {{ scope.row.operatorDisplayName || scope.row.operatorUserName || '-' }}
  238. </template>
  239. </el-table-column>
  240. <el-table-column
  241. prop="applyStatus"
  242. label="单据状态"
  243. width="90"
  244. align="center"
  245. header-align="center">
  246. </el-table-column>
  247. <el-table-column
  248. prop="operationDescDisplay"
  249. label="操作摘要"
  250. min-width="240"
  251. show-overflow-tooltip>
  252. </el-table-column>
  253. </el-table>
  254. </div>
  255. </el-drawer>
  256. <!-- 新增/编辑弹窗 -->
  257. <el-dialog
  258. :title="dialogTitle"
  259. :visible.sync="dialogVisible"
  260. width="420px"
  261. append-to-body
  262. :close-on-click-modal="false">
  263. <el-form :model="formData" label-width="80px" size="small">
  264. <el-form-item label="工序" required>
  265. <el-select
  266. v-model="formData.processStep"
  267. placeholder="请选择工序"
  268. filterable
  269. clearable
  270. :loading="processOptionsLoading"
  271. style="width: 100%">
  272. <el-option
  273. v-for="item in processOptions"
  274. :key="item.value"
  275. :label="item.label"
  276. :value="item.value">
  277. </el-option>
  278. </el-select>
  279. </el-form-item>
  280. <el-form-item label="物料编码">
  281. <el-input
  282. v-model="formData.partNo"
  283. placeholder="请输入物料编码(可为空)"
  284. clearable
  285. @blur="handlePartNoBlur"
  286. @keyup.enter.native="handlePartNoBlur">
  287. </el-input>
  288. <div style="color: #909399; font-size: 12px; margin-top: -10px;">
  289. 输入物料编码后按回车或失去焦点自动查询物料描述
  290. </div>
  291. </el-form-item>
  292. <el-form-item label="物料描述" required>
  293. <el-input
  294. v-model="formData.partDesc"
  295. placeholder="请输入物料描述(必填)"
  296. clearable>
  297. </el-input>
  298. </el-form-item>
  299. <el-form-item label="数量" required>
  300. <el-input
  301. v-model="formData.quantity"
  302. :precision="2"
  303. :min="0.01"
  304. :controls="true"
  305. style="width: 100%">
  306. </el-input>
  307. </el-form-item>
  308. <el-form-item label="单位">
  309. <el-input
  310. v-model="formData.umid"
  311. placeholder="输入物料编码后自动带出"
  312. clearable>
  313. </el-input>
  314. </el-form-item>
  315. <el-form-item label="备注">
  316. <el-input
  317. type="textarea"
  318. v-model="formData.remark"
  319. placeholder="请输入备注"
  320. :autosize="{minRows: 2, maxRows: 3}"
  321. clearable>
  322. </el-input>
  323. </el-form-item>
  324. </el-form>
  325. <span slot="footer" class="dialog-footer" style="margin-top: 10px">
  326. <el-button type="primary" @click="saveRawMaterial" :loading="saveLoading">
  327. {{ saveLoading ? '保存中...' : '保存' }}
  328. </el-button>
  329. <el-button @click="dialogVisible = false">关闭</el-button>
  330. </span>
  331. </el-dialog>
  332. </div>
  333. </template>
  334. <script>
  335. import { getRawMaterialList, getRawMaterialChangeLogList, saveRawMaterial, deleteRawMaterial, batchDeleteRawMaterial, getPartDescByPartNo } from '@/api/erf/erf'
  336. import { searchStandardRoutingOperationList } from '@/api/part/standardRoutingOperation'
  337. export default {
  338. name: 'ExpRawMaterialList',
  339. props: {
  340. // 试验单号
  341. applyNo: {
  342. type: String,
  343. required: true
  344. },
  345. // 工厂编码
  346. site: {
  347. type: String,
  348. default: ''
  349. },
  350. // buNo
  351. buNo: {
  352. type: String,
  353. default: ''
  354. },
  355. // 当前申请单状态
  356. applyStatus: {
  357. type: String,
  358. default: ''
  359. },
  360. // 试验负责人(显示名)
  361. projectLeader: {
  362. type: String,
  363. default: ''
  364. },
  365. // 试验负责人(用户名)
  366. projectLeaderName: {
  367. type: String,
  368. default: ''
  369. },
  370. // 是否禁用编辑
  371. disabled: {
  372. type: Boolean,
  373. default: false
  374. },
  375. },
  376. data() {
  377. return {
  378. // 原材料清单数据
  379. rawMaterialList: [],
  380. // 原材料修改记录
  381. changeLogList: [],
  382. // 表格加载状态
  383. tableLoading: false,
  384. // 修改记录加载状态
  385. changeLogLoading: false,
  386. // 修改记录抽屉
  387. changeLogDrawerVisible: false,
  388. // 已选中的行
  389. selectedRows: [],
  390. // 弹窗显示状态
  391. dialogVisible: false,
  392. // 弹窗标题
  393. dialogTitle: '新增物料',
  394. // 表单数据
  395. formData: {
  396. id: null,
  397. applyNo: '',
  398. site: '',
  399. buNo: '',
  400. processStep: '',
  401. partNo: '',
  402. partDesc: '',
  403. quantity: '',
  404. umid: '',
  405. remark: ''
  406. },
  407. // 保存加载状态
  408. saveLoading: false,
  409. // 标准工序下拉选项
  410. processOptions: [],
  411. // 标准工序加载状态
  412. processOptionsLoading: false
  413. }
  414. },
  415. mounted() {
  416. this.loadRawMaterialList()
  417. this.loadChangeLogList()
  418. },
  419. watch: {
  420. applyNo(newVal) {
  421. if (newVal) {
  422. this.loadRawMaterialList()
  423. this.loadChangeLogList()
  424. } else {
  425. this.rawMaterialList = []
  426. this.selectedRows = []
  427. this.changeLogList = []
  428. this.changeLogDrawerVisible = false
  429. }
  430. },
  431. buNo(newVal, oldVal) {
  432. if (newVal !== oldVal) {
  433. if (this.canEdit) {
  434. this.loadProcessOptions()
  435. } else {
  436. this.processOptions = []
  437. }
  438. }
  439. },
  440. canEdit(newVal) {
  441. if (newVal) {
  442. this.loadProcessOptions()
  443. } else {
  444. this.processOptions = []
  445. }
  446. this.selectedRows = []
  447. this.refreshRawMaterialTableLayout()
  448. }
  449. },
  450. computed: {
  451. /**
  452. * 是否可编辑原材料清单仅试验负责人
  453. */
  454. canEdit() {
  455. if (this.disabled) {
  456. return false
  457. }
  458. const currentUserName = (this.$store.state.user.name || '').trim()
  459. const currentUserDisplay = (this.$store.state.user.userDisplay || '').trim()
  460. const leaderList = [this.projectLeaderName, this.projectLeader]
  461. .filter(item => item && item.trim())
  462. .map(item => item.trim())
  463. if (leaderList.length === 0) {
  464. return false
  465. }
  466. return leaderList.some(item => item === currentUserName || item === currentUserDisplay)
  467. },
  468. /**
  469. * 非草稿状态下需记录详细修改日志
  470. */
  471. shouldRecordChangeLog() {
  472. return !!this.applyStatus && this.applyStatus !== '草稿'
  473. }
  474. },
  475. methods: {
  476. /**
  477. * 加载原材料清单列表
  478. */
  479. loadRawMaterialList() {
  480. if (!this.applyNo) {
  481. return
  482. }
  483. this.tableLoading = true
  484. getRawMaterialList({ applyNo: this.applyNo }).then(({data}) => {
  485. this.tableLoading = false
  486. if (data && data.code === 0) {
  487. this.rawMaterialList = data.list || []
  488. this.refreshRawMaterialTableLayout()
  489. } else {
  490. this.rawMaterialList = []
  491. this.$message.error(data.msg || '查询原材料清单失败')
  492. }
  493. }).catch(error => {
  494. this.tableLoading = false
  495. this.$message.error('查询原材料清单异常')
  496. })
  497. },
  498. /**
  499. * 刷新原材料表格布局避免列结构切换后错位
  500. */
  501. refreshRawMaterialTableLayout() {
  502. this.$nextTick(() => {
  503. if (this.$refs.rawMaterialTable && this.$refs.rawMaterialTable.doLayout) {
  504. this.$refs.rawMaterialTable.doLayout()
  505. }
  506. })
  507. },
  508. /**
  509. * 加载原材料修改记录
  510. */
  511. loadChangeLogList() {
  512. if (!this.applyNo) {
  513. this.changeLogList = []
  514. return
  515. }
  516. this.changeLogLoading = true
  517. getRawMaterialChangeLogList({ applyNo: this.applyNo }).then(({data}) => {
  518. this.changeLogLoading = false
  519. if (data && data.code === 0) {
  520. const logList = data.list || []
  521. this.changeLogList = logList.map(item => {
  522. const rowData = item || {}
  523. return Object.assign({}, rowData, {
  524. operationDescDisplay: this.buildOperationDescDisplay(rowData)
  525. })
  526. })
  527. } else {
  528. this.changeLogList = []
  529. this.$message.error(data.msg || '查询原材料修改记录失败')
  530. }
  531. }).catch(() => {
  532. this.changeLogLoading = false
  533. this.changeLogList = []
  534. this.$message.error('查询原材料修改记录异常')
  535. })
  536. },
  537. /**
  538. * 打开修改记录抽屉
  539. */
  540. openChangeLogDrawer() {
  541. this.changeLogDrawerVisible = true
  542. this.loadChangeLogList()
  543. },
  544. /**
  545. * 按BU加载标准工序下拉
  546. */
  547. loadProcessOptions() {
  548. if (!this.buNo) {
  549. this.processOptions = []
  550. return
  551. }
  552. this.processOptionsLoading = true
  553. const queryData = {
  554. userName: this.$store.state.user.name,
  555. site: this.site || this.$store.state.user.site,
  556. buNo: this.buNo,
  557. page: 1,
  558. limit: 500
  559. }
  560. searchStandardRoutingOperationList(queryData).then(({data}) => {
  561. this.processOptionsLoading = false
  562. if (data && data.code === 0) {
  563. const list = (data.page && data.page.list) ? data.page.list : []
  564. const optionMap = {}
  565. list.forEach(item => {
  566. const processName = item.operationName ? item.operationName.trim() : ''
  567. if (!processName) {
  568. return
  569. }
  570. if (!optionMap[processName]) {
  571. const hasOperationNo = item.operationNo !== null && item.operationNo !== undefined && item.operationNo !== ''
  572. optionMap[processName] = {
  573. value: processName,
  574. label: hasOperationNo ? `${item.operationNo} - ${processName}` : processName
  575. }
  576. }
  577. })
  578. this.processOptions = Object.values(optionMap)
  579. } else {
  580. this.processOptions = []
  581. this.$message.error(data.msg || '加载标准工序失败')
  582. }
  583. }).catch(() => {
  584. this.processOptionsLoading = false
  585. this.processOptions = []
  586. this.$message.error('加载标准工序异常')
  587. })
  588. },
  589. /**
  590. * 编辑场景兜底已选工序不在下拉时补充显示
  591. */
  592. ensureProcessOption(processStep) {
  593. if (!processStep) {
  594. return
  595. }
  596. const exists = this.processOptions.some(item => item.value === processStep)
  597. if (!exists) {
  598. this.processOptions.push({
  599. value: processStep,
  600. label: processStep
  601. })
  602. }
  603. },
  604. /**
  605. * 打开新增弹窗
  606. */
  607. openAddDialog() {
  608. this.loadProcessOptions()
  609. this.dialogTitle = '新增物料'
  610. this.formData = {
  611. id: null,
  612. applyNo: this.applyNo,
  613. site: this.site || this.$store.state.user.site,
  614. buNo: this.buNo,
  615. processStep: '',
  616. partNo: '',
  617. partDesc: '',
  618. quantity: '',
  619. umid: '',
  620. remark: ''
  621. }
  622. this.dialogVisible = true
  623. },
  624. /**
  625. * 打开编辑弹窗
  626. */
  627. openEditDialog(row) {
  628. this.dialogTitle = '修改物料'
  629. this.formData = {
  630. id: row.id,
  631. applyNo: row.applyNo,
  632. site: row.site,
  633. buNo: this.buNo,
  634. processStep: row.processStep || '',
  635. partNo: row.partNo,
  636. partDesc: row.partDesc,
  637. quantity: row.quantity,
  638. umid: row.umid || '',
  639. remark: row.remark
  640. }
  641. this.ensureProcessOption(this.formData.processStep)
  642. this.dialogVisible = true
  643. },
  644. /**
  645. * 保存物料
  646. */
  647. saveRawMaterial() {
  648. // 数据验证
  649. if (!this.formData.partDesc) {
  650. this.$message.warning('请输入物料描述')
  651. return
  652. }
  653. if (!this.formData.processStep) {
  654. this.$message.warning('请选择工序')
  655. return
  656. }
  657. const qty = Number(this.formData.quantity)
  658. if (isNaN(qty) || qty <= 0) {
  659. this.$message.warning('请输入有效数字(必须大于0)')
  660. return
  661. }
  662. this.saveLoading = true
  663. // 保存数据
  664. const saveData = {
  665. id: this.formData.id,
  666. applyNo: this.formData.applyNo,
  667. site: this.formData.site,
  668. processStep: this.formData.processStep,
  669. partNo: this.formData.partNo || null,
  670. partDesc: this.formData.partDesc,
  671. quantity: this.formData.quantity,
  672. umid: this.formData.umid || null,
  673. remark: this.formData.remark
  674. }
  675. saveRawMaterial(saveData).then(({data}) => {
  676. this.saveLoading = false
  677. if (data && data.code === 0) {
  678. this.$message.success('保存成功')
  679. this.dialogVisible = false
  680. this.loadRawMaterialList()
  681. this.loadChangeLogList()
  682. } else {
  683. this.$message.error(data.msg || '保存失败')
  684. }
  685. }).catch(error => {
  686. this.saveLoading = false
  687. this.$message.error('保存异常')
  688. })
  689. },
  690. /**
  691. * 删除物料
  692. */
  693. deleteRawMaterial(row) {
  694. this.$confirm('确定删除该物料记录?', '操作提示', {
  695. confirmButtonText: '确定',
  696. cancelButtonText: '取消',
  697. type: 'warning'
  698. }).then(() => {
  699. deleteRawMaterial({ id: row.id }).then(({data}) => {
  700. if (data && data.code === 0) {
  701. this.$message.success('删除成功')
  702. this.loadRawMaterialList()
  703. this.loadChangeLogList()
  704. } else {
  705. this.$message.error(data.msg || '删除失败')
  706. }
  707. }).catch(error => {
  708. this.$message.error('删除异常')
  709. })
  710. })
  711. },
  712. /**
  713. * 批量删除物料
  714. */
  715. batchDeleteRawMaterial() {
  716. if (this.selectedRows.length === 0) {
  717. this.$message.warning('请先选择要删除的物料')
  718. return
  719. }
  720. this.$confirm(`确定删除选中的 ${this.selectedRows.length} 条物料记录?`, '操作提示', {
  721. confirmButtonText: '确定',
  722. cancelButtonText: '取消',
  723. type: 'warning'
  724. }).then(() => {
  725. const ids = this.selectedRows.map(row => row.id)
  726. batchDeleteRawMaterial({ ids: ids }).then(({data}) => {
  727. if (data && data.code === 0) {
  728. this.$message.success('删除成功')
  729. this.loadRawMaterialList()
  730. this.loadChangeLogList()
  731. this.selectedRows = []
  732. } else {
  733. this.$message.error(data.msg || '删除失败')
  734. }
  735. }).catch(error => {
  736. this.$message.error('删除异常')
  737. })
  738. })
  739. },
  740. /**
  741. * 物料编码失去焦点或回车时查询part表
  742. */
  743. handlePartNoBlur() {
  744. const partNo = this.formData.partNo
  745. if (!partNo || !partNo.trim()) {
  746. return
  747. }
  748. // 查询part表获取物料描述
  749. getPartDescByPartNo({
  750. partNo: partNo.trim(),
  751. site: this.formData.site,
  752. buNo: ''
  753. }).then(({data}) => {
  754. if (data && data.code === 0 && data.partDesc) {
  755. this.formData.partDesc = data.partDesc
  756. this.formData.umid = data.umid || ''
  757. } else {
  758. this.$message.warning('未找到该物料编码,请手动填写物料描述')
  759. }
  760. }).catch(error => {
  761. console.error('查询物料描述异常:', error)
  762. })
  763. },
  764. /**
  765. * 表格多选变化
  766. */
  767. handleSelectionChange(selection) {
  768. this.selectedRows = selection
  769. },
  770. /**
  771. * 格式化时间
  772. */
  773. formatDateTime(dateValue) {
  774. if (!dateValue) {
  775. return '-'
  776. }
  777. if (typeof dateValue === 'string') {
  778. return dateValue
  779. }
  780. const date = new Date(dateValue)
  781. if (isNaN(date.getTime())) {
  782. return '-'
  783. }
  784. const y = date.getFullYear()
  785. const m = String(date.getMonth() + 1).padStart(2, '0')
  786. const d = String(date.getDate()).padStart(2, '0')
  787. const hh = String(date.getHours()).padStart(2, '0')
  788. const mm = String(date.getMinutes()).padStart(2, '0')
  789. const ss = String(date.getSeconds()).padStart(2, '0')
  790. return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
  791. },
  792. /**
  793. * 操作类型对应样式
  794. */
  795. getOperationTypeClass(operationType) {
  796. const operation = this.formatOperationType(operationType)
  797. const classMap = {
  798. '新增': 'operation-type-add',
  799. '删除': 'operation-type-delete',
  800. '修改': 'operation-type-edit'
  801. }
  802. return classMap[operation] || 'operation-type-default'
  803. },
  804. /**
  805. * 标准化操作类型
  806. */
  807. formatOperationType(operationType) {
  808. return operationType ? String(operationType).trim() : ''
  809. },
  810. /**
  811. * 生成摘要展示文本兼容历史仅字段名日志
  812. */
  813. buildOperationDescDisplay(logItem) {
  814. const rowData = logItem || {}
  815. const operationDesc = rowData.operationDesc ? String(rowData.operationDesc).trim() : ''
  816. const operationType = this.formatOperationType(rowData.operationType)
  817. const detailDesc = this.buildOperationDescFromDetail(rowData.detailContent)
  818. if (operationType === '修改' && detailDesc) {
  819. return detailDesc
  820. }
  821. return operationDesc || detailDesc || '-'
  822. },
  823. /**
  824. * 从详细说明提取旧值 -> 新值明细并拼成摘要
  825. */
  826. buildOperationDescFromDetail(detailContent) {
  827. if (!detailContent) {
  828. return ''
  829. }
  830. const diffList = String(detailContent)
  831. .split('\n')
  832. .map(item => item.trim())
  833. .filter(item => /^\d+\.\s+.+\s+->\s+.+$/.test(item))
  834. .map(item => item.replace(/^\d+\.\s*/, ''))
  835. if (diffList.length === 0) {
  836. return ''
  837. }
  838. return `非草稿状态修改原材料,变更明细:${diffList.join(';')}`
  839. }
  840. }
  841. }
  842. </script>
  843. <style scoped>
  844. .raw-material-container {
  845. padding: 10px;
  846. background-color: #ffffff;
  847. }
  848. .toolbar-row {
  849. margin-bottom: 10px;
  850. display: flex;
  851. align-items: center;
  852. justify-content: space-between;
  853. gap: 12px;
  854. }
  855. .toolbar-left {
  856. display: flex;
  857. align-items: center;
  858. flex-wrap: wrap;
  859. gap: 8px;
  860. min-width: 0;
  861. }
  862. .toolbar-right {
  863. display: flex;
  864. align-items: center;
  865. flex-shrink: 0;
  866. }
  867. .change-log-badge {
  868. margin-right: 2px;
  869. }
  870. .change-log-drawer-body {
  871. padding: 0 16px 12px 16px;
  872. }
  873. .change-log-drawer-toolbar {
  874. display: flex;
  875. justify-content: space-between;
  876. align-items: center;
  877. margin-bottom: 8px;
  878. }
  879. .change-log-count {
  880. color: #606266;
  881. font-size: 13px;
  882. font-weight: 600;
  883. }
  884. /* 覆盖全局 .el-table .cell 固定14px,避免操作类型被上下裁切 */
  885. .change-log-table >>> td.operation-type-cell .cell {
  886. height: auto !important;
  887. line-height: 20px !important;
  888. overflow: visible !important;
  889. padding-top: 2px;
  890. padding-bottom: 2px;
  891. }
  892. .operation-type-pill {
  893. display: inline-flex;
  894. align-items: center;
  895. justify-content: center;
  896. min-width: 44px;
  897. min-height: 20px;
  898. line-height: 16px;
  899. padding: 0 8px;
  900. box-sizing: border-box;
  901. border-radius: 10px;
  902. font-size: 12px;
  903. font-weight: 600;
  904. }
  905. .operation-type-add {
  906. background: #f0f9eb;
  907. color: #67c23a;
  908. border: 1px solid #c2e7b0;
  909. }
  910. .operation-type-delete {
  911. background: #fef0f0;
  912. color: #f56c6c;
  913. border: 1px solid #f5bcbc;
  914. }
  915. .operation-type-edit {
  916. background: #fdf6ec;
  917. color: #e6a23c;
  918. border: 1px solid #f5dab1;
  919. }
  920. .operation-type-default {
  921. background: #f4f4f5;
  922. color: #909399;
  923. border: 1px solid #e9e9eb;
  924. }
  925. .log-detail-wrapper {
  926. padding: 8px 12px;
  927. background: #fafafa;
  928. }
  929. .log-detail-item {
  930. margin-bottom: 8px;
  931. }
  932. .log-detail-item:last-child {
  933. margin-bottom: 0;
  934. }
  935. .log-detail-label {
  936. display: inline-block;
  937. margin-bottom: 4px;
  938. color: #606266;
  939. font-size: 12px;
  940. font-weight: 600;
  941. }
  942. .log-detail-pre {
  943. margin: 0;
  944. white-space: pre-wrap;
  945. word-break: break-all;
  946. background: #fff;
  947. border: 1px solid #ebeef5;
  948. border-radius: 2px;
  949. padding: 6px 8px;
  950. color: #606266;
  951. font-size: 12px;
  952. line-height: 1.5;
  953. }
  954. /* 按钮样式 - 与附件上传保持一致 */
  955. .add-btn {
  956. background-color: #F0F9FF;
  957. border-color: #C0E6C7;
  958. color: #67C23A;
  959. }
  960. .add-btn:hover:not(:disabled) {
  961. background-color: #67C23A;
  962. border-color: #67C23A;
  963. color: #FFFFFF;
  964. }
  965. .reset-btn {
  966. background-color: #FEF0F0;
  967. border-color: #FAB6B6;
  968. color: #F56C6C;
  969. }
  970. .reset-btn:hover:not(:disabled) {
  971. background-color: #F56C6C;
  972. border-color: #F56C6C;
  973. color: #FFFFFF;
  974. }
  975. .reset-btn:hover:disabled {
  976. background-color: #FEF0F0;
  977. border-color: #FAB6B6;
  978. color: #F56C6C;
  979. }
  980. .reset-btn:disabled {
  981. opacity: 0.5;
  982. cursor: not-allowed;
  983. }
  984. /* 表格样式 */
  985. .el-table >>> .el-table__header-wrapper th {
  986. background-color: #F5F7FA;
  987. color: #606266;
  988. font-weight: 600;
  989. font-size: 13px;
  990. }
  991. .el-table >>> .el-table__body-wrapper td {
  992. font-size: 13px;
  993. color: #606266;
  994. }
  995. /* 弹窗表单样式 */
  996. .dialog-footer {
  997. text-align: center;
  998. }
  999. </style>