order-book-list.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <template>
  2. <ele-data-table
  3. row-key="userId"
  4. :columns="columns"
  5. :data="dataList"
  6. border
  7. class="order-books"
  8. :span-method="handleSpanMethod"
  9. >
  10. <template #baseInfo="{ row }">
  11. <div class="base-info flex justify-between">
  12. <div class="base-info-left flex flex-1">
  13. <el-image
  14. style="min-width: 80px; width: 80px; height: 100px"
  15. fit="cover"
  16. :src="row.cover"
  17. />
  18. <div
  19. class="base-info-left-con flex flex-col items-start ml-3"
  20. >
  21. <div
  22. ><el-text
  23. type="primary"
  24. style="cursor: pointer"
  25. @click="handleBooksEdit(row)"
  26. >{{ row.bookName }}</el-text
  27. ></div
  28. >
  29. <div
  30. ><el-text>ISBN:{{ row.isbn }}</el-text></div
  31. >
  32. <div class="base-info-btns flex">
  33. <el-button
  34. size="small"
  35. color="#4f4f4f"
  36. v-if="row.bookStatus != 3"
  37. @click="handleBlackList([row.isbn])"
  38. >加入黑名单</el-button
  39. >
  40. <el-button
  41. size="small"
  42. color="#4f4f4f"
  43. v-if="row.bookStatus == 3"
  44. >已在黑名单</el-button
  45. >
  46. <el-button
  47. size="small"
  48. type="success"
  49. v-if="row.bookStatus == 1"
  50. @click="handleAddBookList(row)"
  51. >加入回收书单</el-button
  52. >
  53. <el-button
  54. size="small"
  55. color="#2d430a"
  56. v-if="row.bookStatus == 2"
  57. >已在回收书单</el-button
  58. >
  59. <el-button
  60. size="small"
  61. type="warning"
  62. v-if="row.settingStatus == 0"
  63. @click="handleSpecifiedDiscount(row)"
  64. >指定回收折扣</el-button
  65. >
  66. <el-button
  67. size="small"
  68. color="#7728f5"
  69. v-if="row.settingStatus == 1"
  70. @click="handleModifyDiscount(row)"
  71. >修改回收折扣</el-button
  72. >
  73. </div>
  74. <div
  75. ><el-text type="danger"
  76. >(已回收数量:{{ row.recycleNum }}当前库存:{{
  77. row.stockNum
  78. }})</el-text
  79. ></div
  80. >
  81. </div>
  82. </div>
  83. <div class="base-info-right shrink-0" style="width: 210px">
  84. <div class="common-text flex">
  85. <el-text>定  价:</el-text>
  86. <el-text>¥ {{ row.bookPrice }}</el-text>
  87. </div>
  88. <div class="common-text flex">
  89. <el-text>回收折扣:</el-text>
  90. <el-text>{{ row.recycleDiscount }}折</el-text>
  91. <el-text
  92. v-if="row.sugDiscountStr"
  93. style="color: #f56c6c; margin-left: 10px"
  94. >{{ row.sugDiscountStr }}</el-text
  95. >
  96. </div>
  97. <div class="common-text flex">
  98. <el-text>回收单价:</el-text>
  99. <el-text>¥ {{ row.recyclePrice }}</el-text>
  100. <el-text
  101. v-if="row.isupSell"
  102. style="
  103. color: #f56c6c;
  104. font-weight: 500;
  105. margin-left: 2px;
  106. "
  107. >
  108. +{{ row.upsellPrice }}</el-text
  109. >
  110. </div>
  111. <div class="common-text flex">
  112. <el-text>销售价格:</el-text>
  113. <el-text>¥ {{ row.productPrice }}</el-text>
  114. </div>
  115. </div>
  116. </div>
  117. </template>
  118. <template #action="{ row }">
  119. <div class="action-btns">
  120. <el-button
  121. class="mb-10"
  122. color="#4f4f4f"
  123. @click="handleAuditPic(row)"
  124. >审核图片</el-button
  125. >
  126. <el-button
  127. class="mb-10"
  128. color="#a4adb3"
  129. @click="handleViewUrl(row, 'dd')"
  130. >查看当当</el-button
  131. >
  132. <el-button
  133. class="mb-10"
  134. color="#e99d42"
  135. @click="handleRecycleLog(row)"
  136. >回收日志</el-button
  137. >
  138. <el-button
  139. class="mb-10"
  140. color="#f27606"
  141. @click="handleViewUrl(row, 'tb')"
  142. >查看淘宝</el-button
  143. >
  144. <el-button color="#0f7dc7" @click="handleSalesLog(row)"
  145. >售价日志</el-button
  146. >
  147. <el-button color="#399420" @click="handleViewUrl(row, 'kw')"
  148. >查看孔网</el-button
  149. >
  150. </div>
  151. </template>
  152. <template #auditInfo="{ row }">
  153. <div class="audit-info flex justify-center">
  154. <el-radio-group
  155. v-model="row.sts"
  156. style="width: 120px"
  157. :disabled="!(detail.status == 8 || detail.status == 9)"
  158. @change="(value) => handleAuditInfo(value, row)"
  159. :class="row.sts == 1 ? 'class-green' : 'class-red'"
  160. >
  161. <el-radio :value="1">品相良好</el-radio>
  162. <el-radio :value="2" disabled>品相一般</el-radio>
  163. <el-radio :value="3">品相极差</el-radio>
  164. </el-radio-group>
  165. <el-select
  166. v-model="row.com"
  167. style="width: 180px"
  168. placeholder="请选择品相极差的原因"
  169. multiple
  170. :disabled="
  171. !(detail.status == 8 || detail.status == 9) ||
  172. row.sts !== 3
  173. "
  174. class="reason-select"
  175. @change="(value) => handleSelectReason(value, row)"
  176. >
  177. <el-option
  178. v-for="item in auditReason"
  179. :key="item.dictValue"
  180. :label="item.dictValue"
  181. :value="item.dictValue"
  182. />
  183. </el-select>
  184. </div>
  185. </template>
  186. </ele-data-table>
  187. <orderModifyDiscount
  188. ref="specifiedRef"
  189. @refresh="handleRefresh('specified')"
  190. />
  191. <setParams ref="modifyRef" @refresh="handleRefresh('modify')" />
  192. <orderBlacklist ref="blacklistRef" @refresh="handleRefresh('blacklist')" />
  193. <orderRecycleLog ref="recycleLogRef" />
  194. <orderSalesLog ref="salesLogRef" />
  195. <booksEdit ref="booksEditRef" />
  196. <auditScreenshotIsbn ref="auditScreenshotIsbnRef" />
  197. </template>
  198. <script setup>
  199. import { ref, reactive, watch, nextTick, onMounted } from 'vue';
  200. import orderModifyDiscount from '@/views/recycle/components/modify-discount.vue';
  201. import orderBlacklist from '@/views/recycleOrder/detail/order-blacklist.vue';
  202. import orderRecycleLog from '@/views/recycleOrder/detail/order-recycle-log.vue';
  203. import orderSalesLog from '@/views/recycleOrder/detail/order-sales-log.vue';
  204. import setParams from '@/views/recycle/components/set-params.vue';
  205. import BooksEdit from '@/views/data/books/components/books-edit.vue';
  206. import auditScreenshotIsbn from '@/views/recycleOrder/components/audit-screenshot-isbn.vue';
  207. import request from '@/utils/request';
  208. const props = defineProps({
  209. detail: {
  210. type: Object,
  211. default: () => ({
  212. detailVoList: []
  213. })
  214. },
  215. isExpand: {
  216. type: Boolean,
  217. default: false
  218. }
  219. });
  220. const dataList = ref([]);
  221. const booksEditRef = ref();
  222. function handleBooksEdit(row) {
  223. row.id = row.bookId;
  224. booksEditRef.value?.handleOpen(row);
  225. }
  226. // 处理detailVoList数据
  227. const processDetailList = (list) => {
  228. if (!list) return [];
  229. const result = [];
  230. let currentIndex = 0;
  231. list.forEach((item) => {
  232. let auditInfo = item.auditCommentList;
  233. // 根据num拆分对象
  234. for (let i = 0; i < item.num; i++) {
  235. let audit = auditInfo
  236. ? auditInfo[i]
  237. ? auditInfo[i]
  238. : { sts: 0, com: [] }
  239. : { sts: 0, com: [] };
  240. // 如果com存在且包含逗号,则分割为数组
  241. if (audit.com && typeof audit.com === 'string') {
  242. audit.com = audit.com.split(',').filter(Boolean);
  243. } else if (!Array.isArray(audit.com)) {
  244. audit.com = [];
  245. }
  246. result.push({
  247. ...item,
  248. ...audit,
  249. _index: i,
  250. _groupIndex: currentIndex,
  251. _isFirstRow: i === 0,
  252. isupSell: item.upsellNum > i
  253. });
  254. }
  255. currentIndex++;
  256. });
  257. console.log(result, 'result');
  258. return result;
  259. };
  260. // 初始化数据
  261. const initData = () => {
  262. const list = props.detail.detailVoList || [];
  263. console.log('Initializing data with list:', list);
  264. if (props.isExpand) {
  265. dataList.value = processDetailList(list);
  266. } else {
  267. const processedList = [];
  268. list.forEach((item) => {
  269. processedList.push({
  270. ...item,
  271. _index: 0,
  272. _groupIndex: processedList.length,
  273. _isFirstRow: true,
  274. sts:
  275. item.auditCommentList && item.auditCommentList[0]
  276. ? item.auditCommentList[0].sts
  277. : 0,
  278. com:
  279. item.auditCommentList &&
  280. item.auditCommentList[0] &&
  281. item.auditCommentList[0].com
  282. ? typeof item.auditCommentList[0].com === 'string'
  283. ? item.auditCommentList[0].com
  284. .split(',')
  285. .filter(Boolean)
  286. : item.auditCommentList[0].com
  287. : []
  288. });
  289. });
  290. dataList.value = processedList;
  291. }
  292. console.log('Initialized dataList:', dataList.value);
  293. };
  294. // 组件挂载时初始化数据
  295. onMounted(() => {
  296. initData();
  297. });
  298. // 添加对detail.detailVoList的监听,确保弹窗打开时数据能正确显示
  299. watch(
  300. () => props.detail.detailVoList,
  301. (newVal) => {
  302. if (!newVal) return;
  303. console.log('detail.detailVoList changed:', newVal);
  304. initData();
  305. },
  306. { immediate: true, deep: true }
  307. );
  308. watch(
  309. () => props.isExpand,
  310. (newVal) => {
  311. console.log('isExpand changed:', newVal);
  312. initData();
  313. },
  314. { deep: true, immediate: true }
  315. );
  316. // 处理单元格合并
  317. const handleSpanMethod = ({ row, column, rowIndex }) => {
  318. if (column.property === 'num') {
  319. // 确保行有必要的属性
  320. if (!row._groupIndex && row._groupIndex !== 0) {
  321. return {
  322. rowspan: 1,
  323. colspan: 1
  324. };
  325. }
  326. // 找到当前行所在组的所有行
  327. const currentGroup = dataList.value.filter(
  328. (item) => item._groupIndex === row._groupIndex
  329. );
  330. if (row._isFirstRow) {
  331. // 如果是组内第一行,设置合并行数
  332. return {
  333. rowspan: currentGroup.length,
  334. colspan: 1
  335. };
  336. } else {
  337. // 组内其他行不显示
  338. return {
  339. rowspan: 0,
  340. colspan: 0
  341. };
  342. }
  343. }
  344. return {
  345. rowspan: 1,
  346. colspan: 1
  347. };
  348. };
  349. //审核书籍
  350. function handleAudit(row) {
  351. const payload = {
  352. orderId: props.detail.orderId,
  353. isbn: row.isbn,
  354. inx: row._index,
  355. sts: row.sts,
  356. com: Array.isArray(row.com) ? row.com.join(',') : ''
  357. };
  358. request
  359. .post('/order/orderInfo/adminCheckOrder', payload)
  360. .then((res) => {
  361. if (res.data.code == 200) {
  362. ElMessage.success('操作成功');
  363. } else {
  364. ElMessage.error(res.data.msg);
  365. }
  366. });
  367. }
  368. //其余审核良好 列表中sts为0的设置为1,并提交
  369. function handleOtherAuditGood() {
  370. let stsList = [];
  371. dataList.value.forEach((item) => {
  372. if (item.sts == 0) {
  373. item.sts = 1;
  374. stsList.push({
  375. orderId: props.detail.orderId,
  376. isbn: item.isbn,
  377. sts: 1,
  378. com: '',
  379. inx: item._index
  380. });
  381. }
  382. });
  383. let data = {
  384. orderIds: [props.detail.orderId],
  385. checkType: 2
  386. };
  387. //后端接口批量审核
  388. request.post('/order/orderInfo/adminCheckBatch', data).then((res) => {
  389. if (res.data.code == 200) {
  390. ElMessage.success('操作成功');
  391. emit('close');
  392. } else {
  393. ElMessage.error(res.data.msg);
  394. }
  395. });
  396. }
  397. //单选框选择变化
  398. function handleAuditInfo(value, row) {
  399. if (value == 1) {
  400. row.com = [];
  401. nextTick(() => {
  402. handleAudit(row);
  403. });
  404. } else if (value == 3 && row.com.length > 0) {
  405. nextTick(() => {
  406. handleAudit(row);
  407. });
  408. }
  409. }
  410. //选择审核原因
  411. function handleSelectReason(value, row) {
  412. if (row.sts == 3) {
  413. row.com = value;
  414. nextTick(() => {
  415. handleAudit(row);
  416. });
  417. }
  418. }
  419. const emit = defineEmits(['update:detail', 'refresh', 'close']);
  420. const columns = ref([
  421. {
  422. type: 'index',
  423. columnKey: 'index',
  424. width: 60,
  425. align: 'center'
  426. },
  427. {
  428. label: '信息',
  429. prop: 'baseInfo',
  430. slot: 'baseInfo',
  431. minWidth: 620,
  432. align: 'center'
  433. },
  434. {
  435. label: '操作',
  436. prop: 'action',
  437. slot: 'action',
  438. width: 220,
  439. align: 'center'
  440. },
  441. {
  442. label: '数量',
  443. prop: 'num',
  444. minWidth: 90,
  445. align: 'center',
  446. formatter: (row) => {
  447. return `× ${row.num}`;
  448. }
  449. },
  450. {
  451. label: '审核信息',
  452. prop: 'auditInfo',
  453. slot: 'auditInfo',
  454. align: 'center',
  455. minWidth: 317
  456. },
  457. {
  458. label: '审核金额',
  459. prop: 'recyclePrice',
  460. align: 'center',
  461. minWidth: 100,
  462. formatter: (row) => {
  463. return row.sts == 1
  464. ? `¥ ${(row.recyclePrice + (row.isupSell ? row.upsellPrice : 0)).toFixed(2)}`
  465. : '0';
  466. }
  467. }
  468. ]);
  469. //获取审核原因的字典 book_audit_reason
  470. const auditReason = ref([]);
  471. const getAuditReason = async () => {
  472. const res = await request.get(
  473. '/system/dict/data/type/book_audit_reason'
  474. );
  475. auditReason.value = res.data.data;
  476. console.log(res, 'xxxx');
  477. };
  478. getAuditReason();
  479. //查看当当、淘宝、豆瓣链接
  480. const handleViewUrl = (row, type) => {
  481. let url = '';
  482. if (type == 'dd') {
  483. url = `https://search.dangdang.com/?key=${row.isbn}&act=input`;
  484. } else if (type == 'tb') {
  485. url = `https://s.taobao.com/search?page=1&q=${row.isbn}&sort=sale-desc&tab=all`;
  486. } else if (type == 'db') {
  487. url = `https://search.douban.com/book/subject_search?search_text=${row.isbn}`;
  488. } else if (type == 'kw') {
  489. url = `https://search.kongfz.com/product_result/?key=${row.isbn}&status=0&_stpmt=eyJzZWFyY2hfdHlwZSI6ImFjdGl2ZSJ9`;
  490. }
  491. window.open(url, '_blank');
  492. };
  493. //加入回收书单
  494. const handleAddBookList = (row) => {
  495. ElMessageBox.confirm('确认加入回收书单?', '提示', {
  496. confirmButtonText: '确定',
  497. cancelButtonText: '关闭',
  498. type: 'warning'
  499. }).then(() => {
  500. request
  501. .post('/book/bookRecycleInfo/addIn', {
  502. isbnList: [row.isbn]
  503. })
  504. .then(() => {
  505. ElMessage.success('操作成功');
  506. // 更新列表数据
  507. const index = props.detail.detailVoList.findIndex(
  508. (item) => item.isbn === row.isbn
  509. );
  510. if (index > -1) {
  511. const newList = [...props.detail.detailVoList];
  512. newList[index] = { ...newList[index], bookStatus: 2 };
  513. dataList.value = newList;
  514. }
  515. });
  516. });
  517. };
  518. const currentRow = ref(null);
  519. //修改回收折扣
  520. const modifyRef = ref();
  521. const handleModifyDiscount = (row) => {
  522. currentRow.value = row;
  523. modifyRef.value?.handleOpen(row);
  524. };
  525. //指定回收折扣
  526. const specifiedRef = ref();
  527. const handleSpecifiedDiscount = (row) => {
  528. currentRow.value = row;
  529. specifiedRef.value?.handleOpen(row);
  530. };
  531. //加入黑名单
  532. const blacklistRef = ref();
  533. const handleBlackList = (row) => {
  534. currentRow.value = row;
  535. blacklistRef.value?.handleOpen(row);
  536. };
  537. //查看回收日志
  538. const recycleLogRef = ref();
  539. const handleRecycleLog = (row) => {
  540. recycleLogRef.value?.handleOpen(row);
  541. };
  542. //查看售价日志
  543. const salesLogRef = ref();
  544. const handleSalesLog = (row) => {
  545. salesLogRef.value?.handleOpen(row);
  546. };
  547. //审核图片
  548. const auditScreenshotIsbnRef = ref();
  549. const handleAuditPic = (row) => {
  550. row.orderId = props.detail.orderId;
  551. auditScreenshotIsbnRef.value?.handleOpen(row);
  552. };
  553. const handleRefresh = (type) => {
  554. if (!currentRow.value) return;
  555. const index = dataList.value.findIndex(
  556. (item) => item.isbn === currentRow.value.isbn
  557. );
  558. if (index > -1) {
  559. const newList = [...dataList.value];
  560. if (type === 'specified' || type === 'modify') {
  561. newList[index] = { ...newList[index], settingStatus: 1 };
  562. } else if (type === 'blacklist') {
  563. newList[index] = { ...newList[index], bookStatus: 3 };
  564. }
  565. dataList.value = newList;
  566. }
  567. };
  568. // 手动刷新数据
  569. const refreshData = () => {
  570. console.log('Manual refresh triggered');
  571. initData();
  572. };
  573. defineExpose({
  574. handleOtherAuditGood,
  575. refreshData
  576. });
  577. </script>
  578. <style lang="scss" scoped>
  579. .mb-10 {
  580. margin-bottom: 7px;
  581. }
  582. .reason-select {
  583. :deep(.el-select__wrapper) {
  584. height: 120px !important;
  585. }
  586. :deep(.el-select__placeholder) {
  587. top: 0;
  588. text-wrap: wrap;
  589. white-space: normal;
  590. text-overflow: initial;
  591. }
  592. }
  593. .class-green {
  594. :deep(.el-radio.is-checked .el-radio__inner) {
  595. background: #399420;
  596. border-color: #399420;
  597. }
  598. :deep(.el-radio__input.is-checked + .el-radio__label) {
  599. color: #399420;
  600. }
  601. }
  602. .class-red {
  603. :deep(.el-radio.is-checked .el-radio__inner) {
  604. background: #f56c6c;
  605. border-color: #f56c6c;
  606. }
  607. :deep(.el-radio__input.is-checked + .el-radio__label) {
  608. color: #f56c6c;
  609. }
  610. }
  611. .order-books {
  612. .action-btns {
  613. display: flex;
  614. flex-wrap: wrap;
  615. gap: 10px;
  616. .el-button {
  617. margin: 0;
  618. color: #fff;
  619. }
  620. }
  621. // 处理合并单元格效果
  622. .el-table {
  623. td.el-table__cell {
  624. &.first-row {
  625. border-bottom: none;
  626. }
  627. }
  628. }
  629. }
  630. </style>