index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. <template>
  2. <div class="ele-body" v-loading="loading">
  3. <el-card shadow="never">
  4. <!-- 搜索区 -->
  5. <el-form label-width="80px" class="ele-form-search" @keyup.enter.native="applyFilters" @submit.native.prevent>
  6. <el-row :gutter="15">
  7. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  8. <el-form-item label="领用单号">
  9. <el-input v-model.trim="draftFilters.keyword" clearable placeholder="请输入单号关键字" />
  10. </el-form-item>
  11. </el-col>
  12. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  13. <el-form-item label="领用员工">
  14. <el-input v-model.trim="draftFilters.employeeName" clearable placeholder="请输入员工姓名" />
  15. </el-form-item>
  16. </el-col>
  17. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  18. <el-form-item label="状态">
  19. <el-select v-model="draftFilters.receiptStatus" clearable placeholder="请选择" style="width: 100%">
  20. <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
  21. </el-select>
  22. </el-form-item>
  23. </el-col>
  24. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  25. <div class="ele-form-actions">
  26. <el-button type="primary" icon="el-icon-search" class="ele-btn-icon" @click="applyFilters">
  27. 查询
  28. </el-button>
  29. <el-button icon="el-icon-refresh" class="ele-btn-icon" @click="resetFilters">
  30. 重置
  31. </el-button>
  32. </div>
  33. </el-col>
  34. </el-row>
  35. </el-form>
  36. <!-- 列表表格 -->
  37. <ele-pro-table ref="table" row-key="id" :columns="columns" :datasource="fetchPage" :need-page="true"
  38. :page-size="20" :cache-key="$tableCacheKey('hr-item-receipt')">
  39. <template v-slot:toolbar>
  40. <el-button v-if="$hasPermission('main:hrItemReceipt:save')" type="primary" icon="el-icon-plus"
  41. class="ele-btn-icon" :loading="creating" @click="openCreate">
  42. 新建
  43. </el-button>
  44. </template>
  45. <template v-slot:status="{ row }">
  46. <el-tag v-if="row.receiptStatus" size="mini" :type="statusTagType(row.receiptStatus)">
  47. {{ statusLabel(row.receiptStatus) }}
  48. </el-tag>
  49. <span v-else>-</span>
  50. </template>
  51. <!-- 点领用单号开详情,替代原操作列「查看」入口 -->
  52. <template v-slot:documentCode="{ row }">
  53. <el-link v-if="row.documentCode" type="primary" :underline="false" @click="openDetail(row)">
  54. {{ row.documentCode }}
  55. </el-link>
  56. <span v-else>-</span>
  57. </template>
  58. <template v-slot:itemSummary="{ row }">
  59. <template v-if="Array.isArray(row.details) && row.details.length">
  60. {{row.details.slice(0, 2).map(d => d.itemName).join('、')}}
  61. <span v-if="row.details.length > 2" class="item-summary-more">
  62. 等{{ row.details.length }}项
  63. </span>
  64. </template>
  65. <span v-else>-</span>
  66. </template>
  67. <template v-slot:action="{ row }">
  68. <el-link v-if="canEdit(row) && $hasPermission('main:hrItemReceipt:update')" type="primary"
  69. :underline="false" icon="el-icon-edit" @click="openEdit(row)">
  70. 编辑
  71. </el-link>
  72. <el-link v-if="canSubmit(row) && $hasPermission('main:hrItemReceipt:submit')" type="primary"
  73. :underline="false" icon="el-icon-s-promotion" @click="submitApproval(row)">
  74. 提交
  75. </el-link>
  76. <el-link v-if="canViewApproval(row)" type="primary" :underline="false" icon="el-icon-share"
  77. @click="openApprovalRecord(row)">
  78. 审批记录
  79. </el-link>
  80. <el-link v-if="canDelete(row) && $hasPermission('main:hrItemReceipt:delete')" type="danger"
  81. :underline="false" icon="el-icon-delete" :disabled="submitting" @click="confirmDelete(row)">
  82. 删除
  83. </el-link>
  84. </template>
  85. <template slot="empty">
  86. <el-empty description="暂无物品领用记录" />
  87. </template>
  88. </ele-pro-table>
  89. </el-card>
  90. <!-- 编辑弹框(新建/编辑 共用) -->
  91. <ele-modal :title="editTitle" :visible.sync="editVisible" width="1080px" custom-class="receipt-edit-modal"
  92. :close-on-click-modal="false" append-to-body top="5vh" :maxable="true" @closed="onEditClosed">
  93. <div v-if="editVisible" class="receipt-edit-body">
  94. <el-form label-width="88px" class="receipt-edit-form">
  95. <el-row :gutter="20">
  96. <el-col :span="8">
  97. <el-form-item label="领用员工" required>
  98. <el-select v-if="!editing.id" v-model="editing.employeeId" filterable remote
  99. :remote-method="searchEmployees" :loading="employeeLoading" placeholder="搜索姓名或工号" style="width: 100%"
  100. :rules="[{ required: true, message: '请选择领用员工', trigger: 'change' }]">
  101. <el-option v-for="item in employeeOptions" :key="String(item.id)"
  102. :label="`${item.name || '-'}(${item.employeeNo || item.jobNumber || item.id})`"
  103. :value="String(item.id)" />
  104. </el-select>
  105. <el-input v-else :value="employeeDisplay" disabled />
  106. </el-form-item>
  107. </el-col>
  108. <el-col :span="8">
  109. <el-form-item label="单据号">
  110. <el-input :value="editing.documentCode || '保存后生成'" disabled />
  111. </el-form-item>
  112. </el-col>
  113. <el-col :span="8">
  114. <el-form-item label="创建时间">
  115. <el-input :value="editing.createTime || '-'" disabled />
  116. </el-form-item>
  117. </el-col>
  118. </el-row>
  119. </el-form>
  120. <!-- 复用 ItemReceiptPanel(独立嵌入模式) -->
  121. <ItemReceiptPanel ref="itemReceiptPanel" :employee-id="employeeIdForPanel" :init-receipt-id="editing.id || ''"
  122. :embedded="true" @saved="onSavedFromPanel" @request-approval="openReceiptApproval" />
  123. </div>
  124. <span slot="footer">
  125. <el-button @click="editVisible = false">关闭</el-button>
  126. <el-button type="primary" icon="el-icon-folder-checked" :loading="panelSaving" @click="saveFromPanel">
  127. 保存草稿
  128. </el-button>
  129. </span>
  130. </ele-modal>
  131. <!-- 详情弹框 -->
  132. <ele-modal :title="'领用单详情 · ' + (detailRow?.documentCode || detailRow?.id || '')" :visible.sync="detailVisible"
  133. width="980px" custom-class="receipt-detail-modal" :close-on-click-modal="false" append-to-body top="5vh">
  134. <div v-if="detailRow" class="receipt-detail-body">
  135. <div class="detail-header">
  136. <div class="detail-header-left">
  137. <div class="detail-title">{{ detailRow.employeeName || '-' }}</div>
  138. <div class="detail-subtitle">
  139. {{ detailRow.jobNumber || '-' }} ·
  140. {{ detailRow.departmentName || '-' }} ·
  141. {{ detailRow.legalEntityName || '-' }}
  142. <span v-if="detailRow.createTime" class="detail-time">
  143. · {{ detailRow.createTime }}
  144. </span>
  145. </div>
  146. </div>
  147. <el-tag size="medium" :type="statusTagType(detailRow.receiptStatus)">
  148. {{ statusLabel(detailRow.receiptStatus) }}
  149. </el-tag>
  150. </div>
  151. <el-table :data="detailRow.details || []" border size="small" empty-text="暂无明细" class="detail-table">
  152. <el-table-column type="index" label="#" width="48" align="center" />
  153. <el-table-column prop="itemName" label="物品名称" min-width="140" show-overflow-tooltip />
  154. <el-table-column prop="itemType" label="物品类型" width="110" />
  155. <el-table-column prop="itemModel" label="型号" min-width="120" show-overflow-tooltip />
  156. <el-table-column prop="specifications" label="规格" min-width="140" show-overflow-tooltip />
  157. <el-table-column prop="itemUnit" label="单位" width="70" align="center" />
  158. <el-table-column prop="quantity" label="数量" width="80" align="center" />
  159. <el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
  160. </el-table>
  161. </div>
  162. <span slot="footer">
  163. <el-button @click="detailVisible = false">关闭</el-button>
  164. <el-button v-if="detailRow && canEdit(detailRow) && $hasPermission('main:hrItemReceipt:update')"
  165. type="primary" icon="el-icon-edit-outline" @click="editFromDetail">
  166. 编辑
  167. </el-button>
  168. </span>
  169. </ele-modal>
  170. <!-- 提交审批弹框(公共组件) -->
  171. <processSubmitDialog :processSubmitDialogFlag.sync="processSubmitDialogFlag" v-if="processSubmitDialogFlag"
  172. ref="processSubmitDialogRef" @reload="reloadTable" />
  173. <!-- 审批流程详情弹框 -->
  174. <BpmProcessDetailDialog ref="approvalRecordDialog" />
  175. </div>
  176. </template>
  177. <script>
  178. import {
  179. getItemReceiptPage,
  180. getItemReceipt,
  181. deleteItemReceipt,
  182. receiptStatusLabel,
  183. RECEIPT_APPROVAL_BUSINESS_KEY,
  184. } from "@/api/hr/onboardingItemReceipt";
  185. import { searchUsersByKeyword, getUserPage, adaptEmployee } from "@/api/organization";
  186. import { showRequestError } from "@/utils/message";
  187. import { getCurrentUser } from "@/utils/token-util";
  188. import submitGuard from "@/mixins/submit-guard";
  189. import ItemReceiptPanel from "@/views/hrManagement/onboardingHandling/ItemReceiptPanel.vue";
  190. import processSubmitDialog from "@/BIZComponents/processSubmitDialog/processSubmitDialog.vue";
  191. import BpmProcessDetailDialog from "@/views/bpm/processInstance/detailDialog.vue";
  192. export default {
  193. name: "ItemReceiptManagement",
  194. components: { ItemReceiptPanel, processSubmitDialog, BpmProcessDetailDialog },
  195. mixins: [submitGuard],
  196. data() {
  197. return {
  198. loading: false,
  199. creating: false,
  200. processSubmitDialogFlag: false,
  201. draftFilters: { keyword: "", employeeName: "", receiptStatus: "" },
  202. filters: { keyword: "", employeeName: "", receiptStatus: "" },
  203. editVisible: false,
  204. detailVisible: false,
  205. detailRow: null,
  206. editing: this.emptyEditRow(),
  207. employeeOptions: [],
  208. employeeLoading: false,
  209. statusOptions: [
  210. { value: "DRAFT", label: "草稿" },
  211. { value: "APPROVING", label: "审批中" },
  212. { value: "COMPLETED", label: "已完成" },
  213. ],
  214. };
  215. },
  216. computed: {
  217. styleResponsive() {
  218. return this.$store.state.theme?.styleResponsive !== false;
  219. },
  220. columns() {
  221. return [
  222. { type: "index", label: "序号", width: 60, align: "center" },
  223. {
  224. prop: "documentCode",
  225. label: "领用单号",
  226. minWidth: 160,
  227. align: "left",
  228. showOverflowTooltip: true,
  229. slot: "documentCode",
  230. },
  231. {
  232. prop: "employeeName",
  233. label: "领用员工",
  234. minWidth: 110,
  235. align: "left",
  236. formatter: (r, _c, v) => v || "-",
  237. },
  238. {
  239. prop: "jobNumber",
  240. label: "工号",
  241. width: 110,
  242. align: "left",
  243. formatter: (r, _c, v) => v || "-",
  244. },
  245. {
  246. prop: "legalEntityName",
  247. label: "所属机构",
  248. minWidth: 180,
  249. align: "left",
  250. showOverflowTooltip: true,
  251. formatter: (r, _c, v) => v || "-",
  252. },
  253. {
  254. prop: "departmentName",
  255. label: "所属部门",
  256. minWidth: 140,
  257. align: "left",
  258. showOverflowTooltip: true,
  259. formatter: (r, _c, v) => v || "-",
  260. },
  261. {
  262. prop: "itemSummary",
  263. label: "物品概览",
  264. minWidth: 200,
  265. align: "left",
  266. slot: "itemSummary",
  267. },
  268. {
  269. prop: "receiptStatus",
  270. label: "状态",
  271. width: 100,
  272. align: "left",
  273. slot: "status",
  274. },
  275. {
  276. prop: "createTime",
  277. label: "创建时间",
  278. width: 100,
  279. align: "left",
  280. formatter: (r, _c, v) => v || "-",
  281. },
  282. {
  283. prop: "createUserName",
  284. label: "创建人",
  285. width: 90,
  286. align: "left",
  287. formatter: (r, _c, v) => v || "-",
  288. },
  289. {
  290. prop: "updateTime",
  291. label: "修改时间",
  292. width: 100,
  293. align: "left",
  294. formatter: (r, _c, v) => v || "-",
  295. },
  296. {
  297. prop: "updateUserName",
  298. label: "修改人",
  299. width: 90,
  300. align: "left",
  301. formatter: (r, _c, v) => v || "-",
  302. },
  303. {
  304. columnKey: "action",
  305. label: "操作",
  306. width: 320,
  307. fixed: "right",
  308. slot: "action",
  309. },
  310. ];
  311. },
  312. editTitle() {
  313. return this.editing.id ? "编辑物品领用单" : "新建物品领用单";
  314. },
  315. employeeIdForPanel() {
  316. return this.editing.employeeId || "";
  317. },
  318. employeeDisplay() {
  319. return [
  320. this.editing.employeeName,
  321. this.editing.jobNumber,
  322. ].filter(Boolean).join(" · ") || String(this.editing.employeeId || "");
  323. },
  324. panelSaving() {
  325. return this.$refs.itemReceiptPanel?.saving || false;
  326. },
  327. },
  328. methods: {
  329. emptyEditRow() {
  330. return {
  331. id: "",
  332. employeeId: "",
  333. employeeName: "",
  334. jobNumber: "",
  335. departmentName: "",
  336. legalEntityName: "",
  337. documentCode: "",
  338. createTime: "",
  339. };
  340. },
  341. statusLabel(value) {
  342. return receiptStatusLabel(value) || value || "-";
  343. },
  344. statusTagType(value) {
  345. const key = String(value || "").toUpperCase();
  346. if (key === "COMPLETED") return "success";
  347. if (key === "APPROVING") return "warning";
  348. return "info";
  349. },
  350. canEdit(row) {
  351. const status = String(row?.receiptStatus || "").toUpperCase();
  352. return status === "DRAFT";
  353. },
  354. canDelete(row) {
  355. return this.canEdit(row);
  356. },
  357. canSubmit(row) {
  358. return this.canEdit(row);
  359. },
  360. canViewApproval(row) {
  361. const status = String(row?.receiptStatus || "").toUpperCase();
  362. return status === "APPROVING" || status === "COMPLETED";
  363. },
  364. // ========== 搜索 ==========
  365. applyFilters() {
  366. this.filters = { ...this.draftFilters };
  367. this.reloadTable();
  368. },
  369. resetFilters() {
  370. this.draftFilters = { keyword: "", employeeName: "", receiptStatus: "" };
  371. this.filters = { keyword: "", employeeName: "", receiptStatus: "" };
  372. this.reloadTable();
  373. },
  374. reloadTable() {
  375. this.$nextTick(() => this.$refs.table?.reload({ page: 1 }));
  376. },
  377. // ========== 列表数据源 ==========
  378. async fetchPage({ page, limit }) {
  379. const pageNum = Number(page) || 1;
  380. const size = Number(limit) || 10;
  381. this.loading = true;
  382. try {
  383. const data = await getItemReceiptPage({
  384. pageNum,
  385. size,
  386. offset: (pageNum - 1) * size,
  387. receiptStatus: this.filters.receiptStatus || undefined,
  388. sortName: "createTime",
  389. orderBy: "descending",
  390. });
  391. const list = (data.list || []).map(this.decorateRow);
  392. return { list, count: Number(data.count ?? list.length) };
  393. } catch (error) {
  394. showRequestError(error, "物品领用列表加载失败");
  395. return { list: [], count: 0 };
  396. } finally {
  397. this.loading = false;
  398. }
  399. },
  400. decorateRow(row) {
  401. // 分页返回的 list 可能不带 details(为了性能),让详情弹框再 GET 拉
  402. return row;
  403. },
  404. // ========== 员工搜索 ==========
  405. async searchEmployees(keyword) {
  406. this.employeeLoading = true;
  407. try {
  408. const data = keyword
  409. ? await searchUsersByKeyword(keyword, { pageNum: 1, size: 30 })
  410. : await getUserPage({ pageNum: 1, size: 30 });
  411. const list = Array.isArray(data?.list) ? data.list : [];
  412. this.employeeOptions = list.map((item) => {
  413. const emp = adaptEmployee(item);
  414. return { ...emp, id: String(emp.id) };
  415. });
  416. } catch (error) {
  417. this.employeeOptions = [];
  418. } finally {
  419. this.employeeLoading = false;
  420. }
  421. },
  422. // ========== 新建 ==========
  423. openCreate() {
  424. this.editing = this.emptyEditRow();
  425. this.editVisible = true;
  426. this.searchEmployees("");
  427. },
  428. // ========== 编辑 ==========
  429. openEdit(row) {
  430. if (!this.canEdit(row)) {
  431. this.$message.warning("仅草稿可编辑");
  432. return;
  433. }
  434. this.openEditFromId(row.id);
  435. },
  436. async openEditFromId(id) {
  437. try {
  438. const detail = await getItemReceipt(id);
  439. this.editing = {
  440. id: detail.id ?? "",
  441. employeeId: detail.employeeId ?? "",
  442. employeeName: detail.employeeName || "",
  443. jobNumber: detail.jobNumber || "",
  444. departmentName: detail.departmentName || "",
  445. legalEntityName: detail.legalEntityName || "",
  446. documentCode: detail.documentCode || "",
  447. createTime: detail.createTime || "",
  448. };
  449. // 确保员工下拉有这个选项(编辑时不允许换员工,但展示用)
  450. if (this.editing.employeeId) {
  451. const exists = this.employeeOptions.some(
  452. (it) => String(it.id) === String(this.editing.employeeId),
  453. );
  454. if (!exists) {
  455. this.employeeOptions.unshift({
  456. id: String(this.editing.employeeId),
  457. name: this.editing.employeeName,
  458. employeeNo: this.editing.jobNumber,
  459. });
  460. }
  461. }
  462. this.editVisible = true;
  463. } catch (error) {
  464. showRequestError(error, "领用单详情加载失败");
  465. }
  466. },
  467. // ========== 详情 ==========
  468. async openDetail(row) {
  469. this.detailVisible = true;
  470. this.detailRow = row;
  471. if (!row.details || !row.details.length) {
  472. try {
  473. this.detailRow = await getItemReceipt(row.id);
  474. } catch (error) {
  475. showRequestError(error, "领用单详情加载失败");
  476. }
  477. }
  478. },
  479. editFromDetail() {
  480. if (!this.detailRow) return;
  481. this.detailVisible = false;
  482. this.openEditFromId(this.detailRow.id);
  483. },
  484. // ========== 删除 ==========
  485. confirmDelete(row) {
  486. if (!this.canDelete(row)) {
  487. this.$message.warning("仅草稿可删除");
  488. return undefined;
  489. }
  490. return this.runSubmit(
  491. async () => {
  492. await this.$confirm(
  493. `确定删除领用单「${row.documentCode || row.id}」吗?删除后不可恢复。`,
  494. "删除确认",
  495. { type: "warning" },
  496. );
  497. await deleteItemReceipt(row.id);
  498. this.reloadTable();
  499. },
  500. { success: "删除成功", errorMessage: "删除失败" },
  501. );
  502. },
  503. // ========== ItemReceiptPanel 事件 ==========
  504. onSavedFromPanel(saved) {
  505. if (saved?.id) {
  506. this.editing.id = saved.id;
  507. this.editing.documentCode = saved.documentCode || this.editing.documentCode;
  508. this.editing.createTime = saved.createTime || this.editing.createTime;
  509. }
  510. this.reloadTable();
  511. },
  512. async saveFromPanel() {
  513. const panel = this.$refs.itemReceiptPanel;
  514. if (!panel) return;
  515. const saved = await panel.persist({ silent: false });
  516. if (saved) {
  517. this.editing.id = saved.id;
  518. // 保存成功即自动关闭弹窗(列表已由 onSavedFromPanel 刷新)
  519. this.editVisible = false;
  520. }
  521. },
  522. onEditClosed() {
  523. this.editing = this.emptyEditRow();
  524. },
  525. // ========== 提交审批 ==========
  526. async submitApproval(row) {
  527. let target = row;
  528. // 如果当前在编辑弹框里,先保存再提交
  529. if (this.editVisible && this.editing.id === row.id) {
  530. const panel = this.$refs.itemReceiptPanel;
  531. if (!panel) return;
  532. const saved = await panel.persist({ silent: true });
  533. if (!saved || !saved.id) return;
  534. target = { ...row, ...saved };
  535. this.editing.id = saved.id;
  536. }
  537. this.openReceiptApproval(target);
  538. },
  539. openReceiptApproval(receipt) {
  540. const id = receipt?.id;
  541. if (!id) {
  542. this.$message.warning("领用单保存失败,无法提交审批");
  543. return;
  544. }
  545. this.processSubmitDialogFlag = true;
  546. this.$nextTick(() => {
  547. const currentUser = getCurrentUser() || {};
  548. this.$refs.processSubmitDialogRef?.init({
  549. businessId: id,
  550. businessKey: RECEIPT_APPROVAL_BUSINESS_KEY,
  551. formCreateUserId:
  552. receipt.createUserId || currentUser.id || currentUser.userId || "",
  553. variables: {
  554. businessCode: String(id),
  555. businessName: "入职物品领用",
  556. businessType: "入职物品领用审批",
  557. },
  558. });
  559. });
  560. },
  561. // ========== 查看审批流程 ==========
  562. openApprovalRecord(row) {
  563. this.$refs.approvalRecordDialog?.open(row);
  564. },
  565. },
  566. };
  567. </script>
  568. <style lang="scss" scoped>
  569. .receipt-edit-body {
  570. .receipt-edit-form {
  571. margin-bottom: 0;
  572. }
  573. }
  574. .item-summary-more {
  575. color: #8492a6;
  576. font-size: 14px;
  577. }
  578. .receipt-detail-body {
  579. min-height: 200px;
  580. }
  581. .detail-header {
  582. display: flex;
  583. align-items: center;
  584. justify-content: space-between;
  585. padding: 10px 16px;
  586. background: #f7f9fc;
  587. border-radius: 4px;
  588. }
  589. .detail-title {
  590. font-size: 16px;
  591. font-weight: 600;
  592. color: #303133;
  593. }
  594. .detail-subtitle {
  595. font-size: 14px;
  596. color: #909399;
  597. margin-top: 4px;
  598. }
  599. .detail-time {
  600. font-size: 14px;
  601. color: #b0b3b8;
  602. }
  603. .detail-table {
  604. margin-top: 8px;
  605. }
  606. </style>