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.

699 lines
22 KiB

2 months ago
  1. <template>
  2. <div class="mod-config">
  3. <el-card class="overview-card" shadow="never">
  4. <div slot="header" class="card-title-row">
  5. <div>
  6. <div class="page-title">任务执行手工补录台账</div>
  7. <div class="page-desc">用于 PLC 信息缺失场景的人工补录确保任务执行链路完整可追溯</div>
  8. </div>
  9. <el-button plain class="add-btn" @click="openDialog">新增补录</el-button>
  10. </div>
  11. <div class="overview-grid">
  12. <div class="overview-item">
  13. <div class="overview-label">补录总数</div>
  14. <div class="overview-value">{{ summary.totalCount }}</div>
  15. </div>
  16. <div class="overview-item">
  17. <div class="overview-label">补录总数量</div>
  18. <div class="overview-value">{{ summary.totalQty }}</div>
  19. </div>
  20. <div class="overview-item">
  21. <div class="overview-label">过站补录</div>
  22. <div class="overview-value">{{ summary.typeCount['过站补录'] || 0 }}</div>
  23. </div>
  24. <div class="overview-item">
  25. <div class="overview-label">上挂补录</div>
  26. <div class="overview-value">{{ summary.typeCount['上挂补录'] || 0 }}</div>
  27. </div>
  28. <div class="overview-item">
  29. <div class="overview-label">下挂补录</div>
  30. <div class="overview-value">{{ summary.typeCount['下挂补录'] || 0 }}</div>
  31. </div>
  32. <div class="overview-item">
  33. <div class="overview-label">完工补录</div>
  34. <div class="overview-value">{{ summary.typeCount['完工补录'] || 0 }}</div>
  35. </div>
  36. </div>
  37. </el-card>
  38. <el-form :inline="true" label-position="top" class="query-form">
  39. <el-form-item label="任务单号">
  40. <el-input v-model.trim="query.jobCode" clearable placeholder="请输入任务单号" style="width: 200px" @keyup.enter.native="loadList" />
  41. </el-form-item>
  42. <el-form-item label="补录类型">
  43. <el-select v-model="query.recordType" clearable placeholder="全部类型" style="width: 180px">
  44. <el-option v-for="item in recordTypeOptions" :key="item" :label="item" :value="item" />
  45. </el-select>
  46. </el-form-item>
  47. <el-form-item label="关联单号">
  48. <el-input v-model.trim="query.docNo" clearable placeholder="模糊查询" style="width: 180px" @keyup.enter.native="loadList" />
  49. </el-form-item>
  50. <el-form-item label=" ">
  51. <el-button plain class="search-btn" :loading="loading" @click="loadList">查询</el-button>
  52. <el-button plain class="reset-btn" @click="resetQuery">重置</el-button>
  53. </el-form-item>
  54. </el-form>
  55. <el-table
  56. class="data-table"
  57. :data="dataList"
  58. border
  59. stripe
  60. style="width: 100%"
  61. v-loading="loading"
  62. :height="tableHeight">
  63. <el-table-column type="index" width="50" />
  64. <el-table-column prop="recordId" label="record_id" width="170" />
  65. <el-table-column label="类型" width="110">
  66. <template slot-scope="scope">
  67. <el-tag size="mini" :type="recordTypeTag(scope.row.recordType)">{{ scope.row.recordType || '-' }}</el-tag>
  68. </template>
  69. </el-table-column>
  70. <el-table-column prop="jobId" label="job_id" width="130" />
  71. <el-table-column prop="docNo" label="关联单号" min-width="150" show-overflow-tooltip />
  72. <el-table-column label="挂具" width="100">
  73. <template slot-scope="scope">{{ payloadValue(scope.row, 'rackCode') || '-' }}</template>
  74. </el-table-column>
  75. <el-table-column label="工序" width="120">
  76. <template slot-scope="scope">{{ payloadValue(scope.row, 'stepCode') || '-' }}</template>
  77. </el-table-column>
  78. <el-table-column label="站点" width="120">
  79. <template slot-scope="scope">{{ payloadValue(scope.row, 'stationId') || '-' }}</template>
  80. </el-table-column>
  81. <el-table-column label="缺失原因" min-width="150" show-overflow-tooltip>
  82. <template slot-scope="scope">{{ payloadValue(scope.row, 'missingReason') || '-' }}</template>
  83. </el-table-column>
  84. <el-table-column prop="qty" label="数量" width="80" align="right" />
  85. <el-table-column prop="operatorName" label="补录人" width="110" />
  86. <el-table-column prop="reviewerName" label="复核人" width="110" />
  87. <el-table-column label="补录时间" min-width="170">
  88. <template slot-scope="scope">{{ formatDateTime(scope.row.recordTime) }}</template>
  89. </el-table-column>
  90. </el-table>
  91. <el-dialog
  92. title="PLC缺失信息手工补录"
  93. :visible.sync="dialogVisible"
  94. width="900px"
  95. top="5vh"
  96. class="manual-dialog"
  97. :close-on-click-modal="false"
  98. v-drag>
  99. <el-form ref="manualForm" :model="form" :rules="rules" label-position="top" class="manual-form">
  100. <el-row :gutter="12">
  101. <el-col :span="8">
  102. <el-form-item label="补录类型" prop="recordType">
  103. <el-select v-model="form.recordType" style="width: 100%">
  104. <el-option v-for="item in recordTypeOptions" :key="item" :label="item" :value="item" />
  105. </el-select>
  106. </el-form-item>
  107. </el-col>
  108. <el-col :span="10">
  109. <el-form-item label="任务单号" prop="selectedJobCode">
  110. <el-select
  111. v-model="form.selectedJobCode"
  112. filterable
  113. clearable
  114. :loading="jobLoading"
  115. placeholder="请选择任务单号"
  116. style="width: 100%"
  117. @change="onDialogJobChange">
  118. <el-option
  119. v-for="item in jobOptions"
  120. :key="item.jobCode"
  121. :label="`${item.jobCode} / ${item.status || '-'} / ${item.inboundNos || '-'}`"
  122. :value="item.jobCode" />
  123. </el-select>
  124. </el-form-item>
  125. </el-col>
  126. <el-col :span="6">
  127. <el-form-item label="补录数量" prop="qty">
  128. <el-input-number v-model="form.qty" :min="0" :precision="0" style="width: 100%" />
  129. </el-form-item>
  130. </el-col>
  131. </el-row>
  132. <el-row :gutter="12">
  133. <el-col :span="12">
  134. <el-form-item label="任务明细(可选)">
  135. <el-select
  136. v-model="form.selectedDetailKey"
  137. clearable
  138. filterable
  139. :loading="detailLoading"
  140. placeholder="选择后自动带入 batch_id / part_id"
  141. style="width: 100%"
  142. @change="onDetailChange">
  143. <el-option
  144. v-for="item in jobDetailOptions"
  145. :key="item._key"
  146. :label="`${item.inboundNo || '-'} / ${item.partNo || '-'} / 剩余${item.remainingQty || 0}`"
  147. :value="item._key" />
  148. </el-select>
  149. </el-form-item>
  150. </el-col>
  151. <el-col :span="12">
  152. <el-form-item label="关联单号" prop="docNo">
  153. <el-input v-model.trim="form.docNo" placeholder="建议:任务单+工序+挂具组合号">
  154. <el-button slot="append" @click="fillDocNoByContext">自动生成</el-button>
  155. </el-input>
  156. </el-form-item>
  157. </el-col>
  158. </el-row>
  159. <el-row :gutter="12">
  160. <el-col :span="8"><el-form-item label="batch_id"><el-input v-model="form.batchId" disabled /></el-form-item></el-col>
  161. <el-col :span="8"><el-form-item label="part_id"><el-input v-model="form.partId" disabled /></el-form-item></el-col>
  162. <el-col :span="8">
  163. <el-form-item label="补录时间">
  164. <el-date-picker v-model="form.recordTime" type="datetime" style="width: 100%" />
  165. </el-form-item>
  166. </el-col>
  167. </el-row>
  168. <el-row :gutter="12">
  169. <el-col :span="8"><el-form-item label="挂具码"><el-input v-model.trim="form.rackCode" placeholder="例如 RACK-001" /></el-form-item></el-col>
  170. <el-col :span="8"><el-form-item label="工序编码"><el-input v-model.trim="form.stepCode" placeholder="例如 POOL-001" /></el-form-item></el-col>
  171. <el-col :span="8"><el-form-item label="站点ID"><el-input v-model.trim="form.stationId" placeholder="例如 PLC-POOL-001" /></el-form-item></el-col>
  172. </el-row>
  173. <el-row :gutter="12">
  174. <el-col :span="8">
  175. <el-form-item label="缺失原因">
  176. <el-select v-model="form.missingReason" style="width: 100%">
  177. <el-option v-for="item in missingReasonOptions" :key="item" :label="item" :value="item" />
  178. </el-select>
  179. </el-form-item>
  180. </el-col>
  181. <el-col :span="16">
  182. <el-form-item label="原因说明">
  183. <el-input v-model.trim="form.reasonRemark" placeholder="可填写故障说明、补录依据等" />
  184. </el-form-item>
  185. </el-col>
  186. </el-row>
  187. <el-row :gutter="12">
  188. <el-col :span="12"><el-form-item label="补录人"><el-input v-model.trim="form.operatorName" placeholder="例如 班组长A" /></el-form-item></el-col>
  189. <el-col :span="12"><el-form-item label="复核人"><el-input v-model.trim="form.reviewerName" placeholder="例如 工艺工程师B" /></el-form-item></el-col>
  190. </el-row>
  191. <el-form-item label="扩展JSON(可选)" prop="payloadJson">
  192. <el-input
  193. v-model.trim="form.payloadJson"
  194. type="textarea"
  195. :rows="4"
  196. placeholder='如 {"manualSource":"paper-log"},留空则自动按上方字段生成' />
  197. </el-form-item>
  198. </el-form>
  199. <el-footer style="height: 40px; text-align: center; margin-top: 10px;">
  200. <el-button plain class="reset-btn" @click="dialogVisible = false">取消</el-button>
  201. <el-button plain class="add-btn" :loading="submitLoading" @click="submit">保存补录</el-button>
  202. </el-footer>
  203. </el-dialog>
  204. </div>
  205. </template>
  206. <script>
  207. import { listJob, listJobDetailsByCode, listManualRecord, saveManualRecord } from '@/api/rack/closedLoop'
  208. const RECORD_TYPE_OPTIONS = ['过站补录', '上挂补录', '下挂补录', '完工补录', '其他补录']
  209. const MISSING_REASON_OPTIONS = ['PLC数据缺失', 'PLC离线', 'PLC通信中断', 'PLC数据丢包', '人工确认补录', '其他']
  210. const emptyForm = () => ({
  211. recordType: '过站补录',
  212. selectedJobCode: '',
  213. jobId: null,
  214. selectedDetailKey: '',
  215. batchId: '',
  216. partId: '',
  217. docNo: '',
  218. qty: 0,
  219. rackCode: '',
  220. stepCode: '',
  221. stationId: '',
  222. missingReason: 'PLC数据缺失',
  223. reasonRemark: '',
  224. operatorName: '',
  225. reviewerName: '',
  226. recordTime: new Date(),
  227. payloadJson: ''
  228. })
  229. export default {
  230. data () {
  231. return {
  232. tableHeight: window.innerHeight - 320,
  233. loading: false,
  234. jobLoading: false,
  235. detailLoading: false,
  236. submitLoading: false,
  237. dialogVisible: false,
  238. dataList: [],
  239. jobOptions: [],
  240. jobDetailOptions: [],
  241. query: {
  242. jobCode: '',
  243. recordType: '',
  244. docNo: ''
  245. },
  246. form: emptyForm(),
  247. recordTypeOptions: RECORD_TYPE_OPTIONS,
  248. missingReasonOptions: MISSING_REASON_OPTIONS,
  249. rules: {
  250. recordType: [{ required: true, message: '请选择补录类型', trigger: 'change' }],
  251. selectedJobCode: [{ required: true, message: '请选择任务单号', trigger: 'change' }],
  252. docNo: [{ required: true, message: '请输入关联单号', trigger: 'blur' }],
  253. qty: [{ type: 'number', required: true, message: '请输入补录数量', trigger: 'change' }],
  254. payloadJson: [{ validator: (rule, value, callback) => this.validatePayloadJson(value, callback), trigger: 'blur' }]
  255. }
  256. }
  257. },
  258. computed: {
  259. summary () {
  260. const result = {
  261. totalCount: this.dataList.length,
  262. totalQty: 0,
  263. typeCount: {}
  264. }
  265. this.dataList.forEach(item => {
  266. const type = item.recordType || '未知'
  267. result.typeCount[type] = (result.typeCount[type] || 0) + 1
  268. result.totalQty += Number(item.qty) || 0
  269. })
  270. return result
  271. }
  272. },
  273. mounted () {
  274. this.loadJobOptions()
  275. this.loadList()
  276. },
  277. methods: {
  278. async loadJobOptions () {
  279. this.jobLoading = true
  280. try {
  281. const { data } = await listJob({})
  282. this.jobOptions = data.rows || []
  283. } finally {
  284. this.jobLoading = false
  285. }
  286. },
  287. resolveJobByCode (jobCode) {
  288. const key = String(jobCode || '').trim()
  289. if (!key) {
  290. return null
  291. }
  292. return (this.jobOptions || []).find(item => item && String(item.jobCode || '').trim() === key) || null
  293. },
  294. async resolveJobIdForQuery () {
  295. const key = String(this.query.jobCode || '').trim()
  296. if (!key) {
  297. return null
  298. }
  299. let matched = this.resolveJobByCode(key)
  300. if (!matched) {
  301. const { data } = await listJob({ jobCode: key })
  302. const rows = data.rows || []
  303. matched = rows.find(item => item && String(item.jobCode || '').trim() === key) || rows[0] || null
  304. }
  305. return matched ? matched.jobId : -1
  306. },
  307. async loadList () {
  308. this.loading = true
  309. try {
  310. const jobId = await this.resolveJobIdForQuery()
  311. if (jobId === -1) {
  312. this.dataList = []
  313. this.$message.warning(`任务单不存在:${this.query.jobCode}`)
  314. return
  315. }
  316. const params = {
  317. recordType: this.query.recordType,
  318. docNo: this.query.docNo
  319. }
  320. if (jobId) {
  321. params.jobId = jobId
  322. }
  323. const { data } = await listManualRecord(params)
  324. this.dataList = (data.rows || []).map(row => ({
  325. ...row,
  326. _payload: this.safeParseJson(row.payloadJson)
  327. }))
  328. } finally {
  329. this.loading = false
  330. }
  331. },
  332. resetQuery () {
  333. this.query = { jobCode: '', recordType: '', docNo: '' }
  334. this.loadList()
  335. },
  336. openDialog () {
  337. this.form = emptyForm()
  338. this.jobDetailOptions = []
  339. if (!this.jobOptions.length) {
  340. this.loadJobOptions()
  341. }
  342. if (this.query.jobCode) {
  343. this.form.selectedJobCode = this.query.jobCode
  344. this.onDialogJobChange(this.query.jobCode)
  345. }
  346. this.dialogVisible = true
  347. this.$nextTick(() => {
  348. this.$refs.manualForm && this.$refs.manualForm.clearValidate()
  349. })
  350. },
  351. async onDialogJobChange (jobCode) {
  352. this.form.jobId = null
  353. this.form.selectedDetailKey = ''
  354. this.form.batchId = ''
  355. this.form.partId = ''
  356. this.jobDetailOptions = []
  357. if (!jobCode) {
  358. return
  359. }
  360. let job = this.resolveJobByCode(jobCode)
  361. if (!job) {
  362. const { data } = await listJob({ jobCode })
  363. const rows = data.rows || []
  364. job = rows.find(item => item && item.jobCode === jobCode) || rows[0] || null
  365. }
  366. this.form.jobId = job ? job.jobId : null
  367. await this.loadJobDetails(jobCode)
  368. },
  369. async loadJobDetails (jobCode) {
  370. if (!jobCode) {
  371. this.jobDetailOptions = []
  372. return
  373. }
  374. this.detailLoading = true
  375. try {
  376. const { data } = await listJobDetailsByCode(jobCode)
  377. const rows = data.rows || []
  378. this.jobDetailOptions = rows.map(item => ({
  379. ...item,
  380. _key: `${item.inboundId || ''}_${item.partNo || ''}`
  381. }))
  382. } finally {
  383. this.detailLoading = false
  384. }
  385. },
  386. onDetailChange (key) {
  387. if (!key) {
  388. this.form.batchId = ''
  389. this.form.partId = ''
  390. return
  391. }
  392. const detail = (this.jobDetailOptions || []).find(item => item._key === key)
  393. if (!detail) {
  394. return
  395. }
  396. this.form.batchId = detail.inboundId || ''
  397. this.form.partId = detail.partId || ''
  398. if (!this.form.qty || Number(this.form.qty) <= 0) {
  399. this.form.qty = Number(detail.remainingQty) > 0 ? Number(detail.remainingQty) : (Number(detail.plannedQty) || 0)
  400. }
  401. if (!this.form.docNo) {
  402. this.fillDocNoByContext()
  403. }
  404. },
  405. fillDocNoByContext () {
  406. const code = this.form.selectedJobCode || 'NOJOB'
  407. const step = this.form.stepCode || 'NOSTEP'
  408. const rack = this.form.rackCode || 'NORACK'
  409. this.form.docNo = `${code}-${step}-${rack}-${Date.now()}`
  410. },
  411. safeParseJson (jsonText) {
  412. if (!jsonText) {
  413. return {}
  414. }
  415. try {
  416. return JSON.parse(jsonText)
  417. } catch (e) {
  418. return {}
  419. }
  420. },
  421. payloadValue (row, field) {
  422. const payload = row && row._payload ? row._payload : {}
  423. return payload[field]
  424. },
  425. validatePayloadJson (value, callback) {
  426. if (!value) {
  427. callback()
  428. return
  429. }
  430. try {
  431. JSON.parse(value)
  432. callback()
  433. } catch (e) {
  434. callback(new Error('扩展JSON格式不合法'))
  435. }
  436. },
  437. buildPayloadFromForm () {
  438. let payload = {}
  439. if (this.form.payloadJson) {
  440. payload = JSON.parse(this.form.payloadJson)
  441. }
  442. const selectedDetail = (this.jobDetailOptions || []).find(item => item._key === this.form.selectedDetailKey)
  443. return {
  444. ...payload,
  445. fillScene: 'PLC_MISSING',
  446. manualFill: true,
  447. recordType: this.form.recordType,
  448. jobCode: this.form.selectedJobCode || '',
  449. rackCode: this.form.rackCode || '',
  450. stepCode: this.form.stepCode || '',
  451. stationId: this.form.stationId || '',
  452. missingReason: this.form.missingReason || '',
  453. reasonRemark: this.form.reasonRemark || '',
  454. inboundNo: selectedDetail ? (selectedDetail.inboundNo || '') : '',
  455. partNo: selectedDetail ? (selectedDetail.partNo || '') : ''
  456. }
  457. },
  458. async submit () {
  459. if (this.submitLoading) {
  460. return
  461. }
  462. this.$refs.manualForm.validate(async valid => {
  463. if (!valid) {
  464. return
  465. }
  466. let payloadObj = null
  467. try {
  468. payloadObj = this.buildPayloadFromForm()
  469. } catch (e) {
  470. this.$message.warning((e && e.message) || '扩展JSON格式错误')
  471. return
  472. }
  473. this.submitLoading = true
  474. try {
  475. await saveManualRecord({
  476. recordType: this.form.recordType,
  477. docNo: this.form.docNo,
  478. batchId: this.form.batchId || null,
  479. jobId: this.form.jobId || null,
  480. partId: this.form.partId || null,
  481. qty: Number(this.form.qty) || 0,
  482. operatorName: this.form.operatorName,
  483. reviewerName: this.form.reviewerName,
  484. recordTime: this.form.recordTime || new Date(),
  485. payloadJson: JSON.stringify(payloadObj)
  486. })
  487. this.$message.success('补录保存成功')
  488. this.dialogVisible = false
  489. this.loadList()
  490. } catch (e) {
  491. this.$message.error((e && e.message) || '补录保存失败')
  492. } finally {
  493. this.submitLoading = false
  494. }
  495. })
  496. },
  497. recordTypeTag (recordType) {
  498. const map = {
  499. 过站补录: 'warning',
  500. 上挂补录: 'success',
  501. 下挂补录: '',
  502. 完工补录: 'danger',
  503. 其他补录: 'info'
  504. }
  505. return map[recordType] || 'info'
  506. },
  507. formatDateTime (v) {
  508. if (!v) return '-'
  509. let date = null
  510. if (typeof v === 'number') {
  511. date = new Date(v)
  512. } else if (typeof v === 'string') {
  513. const trimmed = v.trim()
  514. if (/^\d+$/.test(trimmed)) {
  515. date = new Date(Number(trimmed))
  516. } else {
  517. date = new Date(trimmed)
  518. }
  519. } else if (v instanceof Date) {
  520. date = v
  521. } else {
  522. date = new Date(v)
  523. }
  524. if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
  525. return String(v).replace('T', ' ')
  526. }
  527. const y = date.getFullYear()
  528. const m = String(date.getMonth() + 1).padStart(2, '0')
  529. const d = String(date.getDate()).padStart(2, '0')
  530. const hh = String(date.getHours()).padStart(2, '0')
  531. const mm = String(date.getMinutes()).padStart(2, '0')
  532. const ss = String(date.getSeconds()).padStart(2, '0')
  533. return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
  534. }
  535. }
  536. }
  537. </script>
  538. <style scoped>
  539. .overview-card {
  540. margin-bottom: 12px;
  541. }
  542. .card-title-row {
  543. display: flex;
  544. align-items: center;
  545. justify-content: space-between;
  546. }
  547. .page-title {
  548. font-size: 16px;
  549. font-weight: 600;
  550. color: #303133;
  551. }
  552. .page-desc {
  553. margin-top: 4px;
  554. color: #909399;
  555. font-size: 12px;
  556. }
  557. .overview-grid {
  558. display: grid;
  559. grid-template-columns: repeat(6, minmax(120px, 1fr));
  560. gap: 10px;
  561. }
  562. .overview-item {
  563. border: 1px solid #ebeef5;
  564. border-radius: 6px;
  565. padding: 10px;
  566. background: #fff;
  567. }
  568. .overview-label {
  569. color: #909399;
  570. font-size: 12px;
  571. }
  572. .overview-value {
  573. margin-top: 6px;
  574. font-size: 20px;
  575. font-weight: 600;
  576. color: #303133;
  577. }
  578. .manual-dialog >>> .el-dialog__body {
  579. padding: 12px 20px 10px;
  580. max-height: 72vh;
  581. overflow-y: auto;
  582. }
  583. .manual-form >>> .el-form-item {
  584. margin-bottom: 10px;
  585. }
  586. .data-table {
  587. background-color: #fff;
  588. border-radius: 4px;
  589. }
  590. .data-table >>> .el-table__header-wrapper th,
  591. .data-table >>> .el-table__fixed-header-wrapper th {
  592. background-color: #f5f7fa !important;
  593. color: #333;
  594. font-weight: 600;
  595. border-color: #ebeef5;
  596. padding: 8px 0;
  597. }
  598. .data-table >>> .el-table__header-wrapper .cell,
  599. .data-table >>> .el-table__fixed-header-wrapper .cell,
  600. .data-table >>> .el-table__body-wrapper .cell,
  601. .data-table >>> .el-table__fixed-body-wrapper .cell {
  602. padding: 0 10px;
  603. overflow: hidden;
  604. text-overflow: ellipsis;
  605. white-space: nowrap;
  606. font-size: 13px !important;
  607. }
  608. .query-form {
  609. background-color: #fff;
  610. padding: 15px 15px 5px 15px;
  611. border-radius: 4px;
  612. margin-bottom: 12px;
  613. }
  614. .query-form >>> .el-form-item__label {
  615. color: #333;
  616. font-size: 13px;
  617. padding-bottom: 5px;
  618. }
  619. .query-form >>> .el-input__inner {
  620. height: 32px;
  621. line-height: 32px;
  622. border-radius: 4px;
  623. font-size: 13px;
  624. }
  625. .query-form >>> .el-button {
  626. height: 32px;
  627. padding: 0 15px;
  628. font-size: 13px;
  629. border-radius: 4px;
  630. }
  631. .search-btn {
  632. background-color: #ecf5ff;
  633. border-color: #b3d8ff;
  634. color: #409eff;
  635. }
  636. .search-btn:hover {
  637. background-color: #409eff;
  638. border-color: #409eff;
  639. color: #fff;
  640. }
  641. .reset-btn {
  642. background-color: #f5f7fa;
  643. border-color: #d3d4d6;
  644. color: #606266;
  645. }
  646. .reset-btn:hover {
  647. background-color: #909399;
  648. border-color: #909399;
  649. color: #fff;
  650. }
  651. .add-btn {
  652. background-color: #f0f9eb;
  653. border-color: #c2e7b0;
  654. color: #67c23a;
  655. }
  656. .add-btn:hover {
  657. background-color: #67c23a;
  658. border-color: #67c23a;
  659. color: #fff;
  660. }
  661. </style>