index.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <ele-page flex-table>
  3. <page-search ref="searchRef" @search="reload" @generate-rule="openRuleDialog" />
  4. <common-table
  5. ref="pageRef"
  6. :pageConfig="pageConfig"
  7. :columns="columns"
  8. :tools="false"
  9. :datasource="datasource"
  10. >
  11. <template #toolbar>
  12. <el-button type="warning" @click="importVisible = true">
  13. <el-icon class="mr-1"><Upload /></el-icon>
  14. 批量导入
  15. </el-button>
  16. </template>
  17. <template #cover="{ row }">
  18. <el-image
  19. style="width: 80px; height: 110px; border-radius: 4px"
  20. fit="cover"
  21. :src="row.cover"
  22. :preview-src-list="[row.cover]"
  23. :initial-index="0"
  24. preview-teleported
  25. />
  26. </template>
  27. <template #info="{ row }">
  28. <div class="flex flex-col text-sm">
  29. <div class="info-basic-grid">
  30. <div>
  31. <span class="font-bold">书名: </span>{{ row.bookName }}
  32. </div>
  33. <div>
  34. <span class="font-bold">作者: </span>{{ row.author }}
  35. </div>
  36. <div>
  37. <span class="font-bold">ISBN: </span>{{ row.isbn }}
  38. </div>
  39. <div>
  40. <span class="font-bold">出版社: </span>{{ row.publish }}
  41. </div>
  42. <div>
  43. <span class="font-bold">出版时间: </span>{{ row.pubDate }}
  44. </div>
  45. <div>
  46. <span class="font-bold">所属类型: </span>{{ row.bookTagName }}
  47. </div>
  48. <div>
  49. <span class="font-bold">回收状态: </span>{{ row.recycleStatusName }}
  50. </div>
  51. </div>
  52. <div class="mt-2 border-t pt-2">
  53. <span class="mr-4">定价: {{ row.price }}</span>
  54. <span class="mr-4"
  55. >回收折扣: {{ row.recycleDiscount }}折
  56. <span class="text-red-500 text-xs"
  57. >{{ row.sugDiscountStr || '建议折扣' }}</span
  58. ></span
  59. >
  60. <span>回收价格: {{ row.recyclePrice }}</span>
  61. </div>
  62. <div class="mb-1">
  63. <span class="mr-4"
  64. >销售价格: {{ row.productPrice }}
  65. <el-icon class="cursor-pointer text-blue-500"
  66. ><Edit /></el-icon
  67. ></span>
  68. <span class="text-green-500 text-xs"
  69. >{{ row.productPriceChangeLog?.operatorName || '' }} {{ row.productPriceChangeLog?.createTime || '' }}</span
  70. >
  71. </div>
  72. <div class="mb-1">
  73. <span
  74. >(已回收数量: {{ row.recycleNum }} 当前库存:
  75. {{ row.stockNum }})</span
  76. >
  77. </div>
  78. </div>
  79. </template>
  80. <template #turnoverRate="{ row }">
  81. <div class="flex items-center">
  82. <el-tag
  83. :type="row.cycleName === '年' ? 'primary' : 'danger'"
  84. effect="plain"
  85. class="mr-1"
  86. >
  87. {{ row.cycleName }}
  88. </el-tag>
  89. <span>{{ row.turnoverRate }}</span>
  90. <el-icon
  91. class="ml-2 cursor-pointer text-blue-500 text-lg"
  92. @click="handleHistory(row)"
  93. >
  94. <DataLine />
  95. </el-icon>
  96. </div>
  97. </template>
  98. <template #action="{ row }">
  99. <div class="flex flex-col items-start gap-1">
  100. <el-button link type="primary" @click="handleHistory(row)"
  101. >[历史周转率]</el-button
  102. >
  103. <el-button link type="primary" @click="handleLog(row)"
  104. >[操作记录]</el-button
  105. >
  106. </div>
  107. </template>
  108. </common-table>
  109. <import-dialog v-model="importVisible" @done="reload" />
  110. <rule-dialog
  111. v-model="ruleVisible"
  112. :condition="lastWhere"
  113. :get-condition="getRuleCondition"
  114. @done="handleRuleDone"
  115. />
  116. <history-dialog v-model="historyVisible" ref="historyRef" />
  117. <log-dialog v-model="logVisible" ref="logRef" />
  118. </ele-page>
  119. </template>
  120. <script setup>
  121. import { ref, reactive } from 'vue';
  122. import request from '@/utils/request';
  123. import { cleanParams, withOffsetLimit } from '@/api/priceWorkbench';
  124. import {
  125. Upload,
  126. Edit,
  127. DataLine
  128. } from '@element-plus/icons-vue';
  129. import CommonTable from '@/components/CommonPage/CommonTable.vue';
  130. import PageSearch from './components/page-search.vue';
  131. import ImportDialog from './components/import-dialog.vue';
  132. import RuleDialog from './components/rule-dialog.vue';
  133. import HistoryDialog from './components/history-dialog.vue';
  134. import LogDialog from './components/log-dialog.vue';
  135. defineOptions({ name: 'StockTurnoverRate' });
  136. // Component Refs
  137. const pageRef = ref(null);
  138. const searchRef = ref(null);
  139. const historyRef = ref(null);
  140. const logRef = ref(null);
  141. // Dialog Visibilities
  142. const importVisible = ref(false);
  143. const ruleVisible = ref(false);
  144. const historyVisible = ref(false);
  145. const logVisible = ref(false);
  146. const columns = ref([
  147. { type: 'selection', width: 50, align: 'center', fixed: 'left' },
  148. { label: '图片', prop: 'cover', width: 100, slot: 'cover' },
  149. { label: '信息', prop: 'info', minWidth: 300, slot: 'info' },
  150. {
  151. label: '库存周转率',
  152. prop: 'turnoverRate',
  153. width: 140,
  154. slot: 'turnoverRate',
  155. sortable: true
  156. },
  157. {
  158. label: '剩余可卖天数',
  159. prop: 'remainSellDays',
  160. width: 130,
  161. sortable: true
  162. },
  163. { label: '销量', prop: 'turnoverSalesNum', width: 100, sortable: true },
  164. { label: '期初库存', prop: 'beginStock', width: 100, sortable: true },
  165. { label: '期末库存', prop: 'endStock', width: 100, sortable: true },
  166. { label: '平均库存', prop: 'avgStock', width: 100, sortable: true },
  167. {
  168. label: '操作',
  169. prop: 'action',
  170. width: 120,
  171. slot: 'action',
  172. fixed: 'right'
  173. }
  174. ]);
  175. const lastWhere = ref({});
  176. const pageConfig = reactive({
  177. fileName: '库存周转率',
  178. cacheKey: 'stockTurnoverTable',
  179. params: {},
  180. rowKey: 'bookId'
  181. });
  182. const datasource = ({ pages, where, orders, sorter }) => {
  183. lastWhere.value = { ...(where || {}) };
  184. const params = withOffsetLimit(pages, {
  185. ...(pageConfig.params || {}),
  186. ...cleanParams(where || {})
  187. });
  188. if (orders && orders.orderByColumn) {
  189. params.orderType = sorter?.column?.columnKey || orders.orderByColumn;
  190. params.orderWay = orders.isAsc === 'ascending' ? 'asc' : 'desc';
  191. }
  192. return request
  193. .get('/book/bookTurnover/list', { params })
  194. .then((res) => {
  195. if (res.data.code === 200) return res.data;
  196. return Promise.reject(new Error(res.data.msg));
  197. });
  198. };
  199. function reload(where) {
  200. if (where) lastWhere.value = { ...where };
  201. pageRef.value?.reload(where);
  202. }
  203. function getRuleCondition() {
  204. const live = searchRef.value?.getCondition?.();
  205. if (live && Object.keys(live).length) {
  206. return live;
  207. }
  208. return lastWhere.value || {};
  209. }
  210. function handleRuleDone() {
  211. pageRef.value?.reload();
  212. }
  213. function openRuleDialog() {
  214. // 打开前先同步一次当前筛选,避免弹窗读到空条件
  215. const live = getRuleCondition();
  216. lastWhere.value = { ...(live || {}) };
  217. ruleVisible.value = true;
  218. }
  219. function handleHistory(row) {
  220. historyVisible.value = true;
  221. historyRef.value?.setData(row);
  222. }
  223. function handleLog(row) {
  224. logVisible.value = true;
  225. logRef.value?.handleOpen(row);
  226. }
  227. </script>
  228. <style scoped>
  229. .info-basic-grid {
  230. display: grid;
  231. grid-template-columns: 1fr 1fr;
  232. column-gap: 16px;
  233. row-gap: 4px;
  234. }
  235. </style>