order-book-list.vue 25 KB

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