trend.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <template>
  2. <u-modal :show="trendModal" title="价格趋势" @confirm="closeTrendModal" confirmText="关闭弹窗" height="93%"><view class="trend_modal">
  3. <RemoteSearchSelect ref="searchSelect" :value="model" placeholder="请输入关键词" :resultsShow="resultsShow" :currentUrl="currentImg" :searchResults="searchResults" :imgResults="imgResults" :loading="loading"
  4. :cameraEnabled="true" @search="handleSearch" @select="handleSelect" @select-img="handleSelectImg" @select-crop-img="handleSelectCropImg" @load-more="loadMore" @load-more-img="loadMoreImg" @clear="handleClear" @focus="handleFocus" @upload="uploadImage"></RemoteSearchSelect>
  5. <view class="charts_box" v-if="chartShow">
  6. <u--text type="primary" :text="`最大价格:${Math.max(...maxPrice)}元`"></u--text>
  7. <u--text type="warning" :text="`最小价格:${Math.min(...minPrice)}元`"></u--text>
  8. <qiun-data-charts type="line" :chartData="chartData" canvasId="trendChart" :opts="opts" :ontouch="true"
  9. tooltipFormat="tooltipFormatPrice" width="700rpx" height="600rpx" backgroundColor="#ffffff"
  10. @getIndex="handleChartClick" />
  11. </view>
  12. <!-- <u--text v-if="cardData.length > 0" :text="'日期:' + date"></u--text> -->
  13. <view class="card-container" v-if="cardData.length > 0">
  14. <view class="card-item" v-for="(item, index) in cardData" :key="index">
  15. <view class="card-row">
  16. <span class="card-label">型号:</span>
  17. <span class="card-value">{{ item.model }}</span>
  18. </view>
  19. <view class="card-row">
  20. <span class="card-label">日期:</span>
  21. <span class="card-value">{{ item.recycleTime }}</span>
  22. </view>
  23. <view class="card-row">
  24. <span class="card-label">价格:</span>
  25. <span class="card-value">{{ item.price }}元</span>
  26. </view>
  27. <view class="card-row">
  28. <span class="card-label">回收情况:</span>
  29. <span class="card-value">{{ item.recycleSituation }}</span>
  30. </view>
  31. <view class="card-row">
  32. <span class="card-label">备注:</span>
  33. <span class="card-value">{{ item.remark || '-' }}</span>
  34. </view>
  35. </view>
  36. </view>
  37. </view>
  38. </u-modal>
  39. </template>
  40. <script>
  41. import { recycleSituationList } from '@/pages/wareHouse/js/public.js'
  42. import RemoteSearchSelect from '@/components/remote-search-select/index.vue'
  43. export default {
  44. name: 'ComponentName',
  45. components: {
  46. RemoteSearchSelect,
  47. },
  48. data() {
  49. return {
  50. trendModal: false,
  51. chartData: {},
  52. color: [],
  53. opts: {
  54. color: this.color,
  55. padding: [20, 10, 40, 0],
  56. dataLabel: true,
  57. dataPointShape: true,
  58. enableScroll: true,
  59. xAxis: {
  60. disableGrid: true,
  61. scrollShow: true,
  62. itemCount: 10,
  63. rotateLabel: true,
  64. rotateAngle: 45,
  65. },
  66. yAxis: {
  67. gridType: "dash",
  68. dashLength: 7,
  69. },
  70. legend: {
  71. show: false,
  72. type: 'scroll',
  73. orient: 'horizontal',
  74. pageSize: 3,
  75. pageIconSize: 12,
  76. pageIconColor: '#666',
  77. pageIconInactiveColor: '#ccc',
  78. pageTextStyle: {
  79. color: '#666',
  80. fontSize: 12
  81. },
  82. bottom: 0
  83. },
  84. extra: {
  85. line: {
  86. type: "curve",
  87. width: 3,
  88. activeType: "hollow",
  89. linearType: "custom",
  90. onShadow: true,
  91. animation: "horizontal"
  92. },
  93. tooltip: {
  94. legendShow: false,
  95. bgOpacity: 0.6,
  96. }
  97. }
  98. },
  99. model: "",
  100. maxPrice: [],
  101. minPrice: [],
  102. chartShow: false,
  103. cardData: [],
  104. // date: '',
  105. recycleSituationList: recycleSituationList,
  106. searchResults: [],
  107. loading: false,
  108. pageNum: 1,
  109. total: 0,
  110. resultsShow: false,
  111. isSelecting: false,
  112. imgResults: [],
  113. currentImg: '',
  114. imgPageNum: 1,
  115. imgTotal: 0,
  116. searchImgInfo: {},
  117. }
  118. },
  119. props: {
  120. },
  121. emits: [],
  122. methods: {
  123. handleSearch({ keyword }) {
  124. if (this.isSelecting) {
  125. this.isSelecting = false
  126. return
  127. }
  128. if (!keyword) {
  129. this.searchResults = []
  130. return
  131. }
  132. this.model = keyword
  133. this.loading = true
  134. uni.$u.api.selectModelList({
  135. inputValue: keyword,
  136. pageNum: this.pageNum,
  137. pageSize: 10,
  138. }).then(res=>{
  139. this.loading = false
  140. this.total = Number(res.total) || 0
  141. if (this.pageNum == 1){
  142. this.searchResults = res.rows || []
  143. }else{
  144. this.searchResults = [...this.searchResults, ...res.rows || []]
  145. }
  146. this.resultsShow = true
  147. })
  148. },
  149. handleFocus() {
  150. this.resultsShow = true
  151. this.handleSearch({ keyword: this.model })
  152. },
  153. handleClear() {
  154. this.resultsShow = false
  155. this.searchResults = []
  156. this.total = 0
  157. this.pageNum = 1
  158. this.imgResults = []
  159. this.imgTotal = 0
  160. this.imgPageNum = 1
  161. this.currentImg = ''
  162. this.model = ""
  163. this.minPrice = []
  164. this.maxPrice = []
  165. this.chartData = []
  166. this.cardData = []
  167. this.chartShow = false
  168. },
  169. loadMore() {
  170. if (this.searchResults.length >= this.total) {
  171. uni.$u.toast("没有更多数据了")
  172. return
  173. }
  174. this.pageNum++
  175. this.handleSearch({ keyword: this.model })
  176. },
  177. // 处理图片列表滚动到底部事件
  178. loadMoreImg() {
  179. if (this.imgResults.length >= this.imgTotal) {
  180. uni.$u.toast("没有更多数据了")
  181. return
  182. }
  183. this.imgPageNum++
  184. this.uploadImage(this.searchImgInfo)
  185. },
  186. handleSelect(item) {
  187. this.isSelecting = true
  188. this.model = item.model
  189. this.resultsShow = false
  190. this.chartShow = false
  191. this.$nextTick(() => {
  192. this.searchTrend(item.model)
  193. })
  194. },
  195. handleSelectImg(item) {
  196. this.model = item.record.model
  197. this.chartShow = false
  198. this.isSelecting = true
  199. this.pageNum = 1
  200. this.total = 0
  201. this.$nextTick(() => {
  202. this.searchTrend(item.record.model)
  203. })
  204. },
  205. handleSelectCropImg(file) {
  206. this.imgPageNum = 1
  207. this.imgTotal = 0
  208. const data = {
  209. tempFilePaths:[file]
  210. }
  211. this.uploadImage(data)
  212. },
  213. openTrendModal() {
  214. this.trendModal = true;
  215. },
  216. closeTrendModal() {
  217. this.handleClear()
  218. this.trendModal = false;
  219. },
  220. searchTrend(val) {
  221. if (val !== '') {
  222. uni.$u.api.inquiryChart({
  223. model: val,
  224. }).then(res => {
  225. if (res.data.length == 0) {
  226. uni.$u.toast("暂无数据")
  227. this.minPrice = []
  228. this.maxPrice = []
  229. this.chartShow = false
  230. this.cardData = []
  231. return
  232. }
  233. this.maxPrice = []
  234. this.minPrice = []
  235. this.color = []
  236. const categories = []
  237. const dateMap = {}
  238. const recycleData = res.data[0].list
  239. recycleData.forEach(i => {
  240. i.recycleTime = this.formatTime(i.recycleTime)
  241. if (!dateMap[i.recycleTime]) {
  242. dateMap[i.recycleTime] = true
  243. categories.push(i.recycleTime)
  244. }
  245. })
  246. const situationMap = {}
  247. recycleData.forEach(i => {
  248. const key = `${i.recycleTime}-${i.recycleSituation}`
  249. if (!situationMap[key]) {
  250. situationMap[key] = {
  251. recycleTime: i.recycleTime,
  252. recycleSituation: i.recycleSituation,
  253. prices: []
  254. }
  255. }
  256. situationMap[key].prices.push(i.price)
  257. this.maxPrice.push(i.price)
  258. this.minPrice.push(i.price)
  259. })
  260. const situationGroups = {}
  261. Object.values(situationMap).forEach(item => {
  262. const avgPrice = item.prices.reduce((sum, price) => sum + price, 0) / item.prices.length
  263. if (!situationGroups[item.recycleSituation]) {
  264. situationGroups[item.recycleSituation] = []
  265. }
  266. situationGroups[item.recycleSituation].push({
  267. recycleTime: item.recycleTime,
  268. price: avgPrice
  269. })
  270. })
  271. const series = Object.entries(situationGroups).map(([situation, data]) => {
  272. const color = this.getRandomColor()
  273. this.color.push(color)
  274. const seriesData = categories.map(date => {
  275. const itemData = data.find(i => i.recycleTime == date)
  276. return itemData ? itemData.price : null
  277. })
  278. return {
  279. name: this.formatRecycleSituation(situation),
  280. data: seriesData,
  281. list: data,
  282. setShadow: [
  283. 3,
  284. 8,
  285. 15,
  286. color
  287. ],
  288. }
  289. })
  290. this.opts = {
  291. ...this.opts,
  292. color: [...this.color]
  293. }
  294. const chartData = {
  295. categories: categories,
  296. series: series
  297. }
  298. this.chartData = chartData
  299. this.cardData = []
  300. this.chartShow = true
  301. }).catch((err) => {
  302. uni.$u.toast(err)
  303. })
  304. } else {
  305. this.minPrice = []
  306. this.maxPrice = []
  307. this.chartShow = false
  308. this.cardData = []
  309. }
  310. },
  311. uploadImage(res) {
  312. this.$nextTick(()=>{
  313. this.searchImgInfo = res;
  314. uni.$u.api.searchModelByImage(
  315. this.searchImgInfo.tempFilePaths[0],
  316. {
  317. pageNum: this.imgPageNum,
  318. pageSize: 10
  319. }
  320. ).then(res => {
  321. if (res.data) {
  322. this.imgTotal = Number(res.data.total)
  323. this.currentImg = this.searchImgInfo.tempFilePaths[0]
  324. if(this.imgPageNum == 1){
  325. this.imgResults = res.data.rows
  326. }else{
  327. this.imgResults = [...this.imgResults, ...res.data.rows]
  328. }
  329. this.$refs.searchSelect.openImgPopup()
  330. } else {
  331. uni.$u.toast('未匹配到型号,请换一张图或手动输入');
  332. }
  333. }).catch((err) => {
  334. console.log(err);
  335. uni.$u.toast('识别失败,请重试');
  336. });
  337. })
  338. },
  339. formatTime(date) {
  340. return this.$dayjs(date).format('YYYY-MM-DD')
  341. },
  342. formatRecycleSituation(situation) {
  343. const item = this.recycleSituationList.find(i => i.value == situation)
  344. return item ? item.name : '-'
  345. },
  346. handleChartClick(event) {
  347. const index = event.currentIndex.index;
  348. // 获取点击的日期
  349. const date = this.chartData.categories[index];
  350. // this.date = date;
  351. // 构建该日期的所有回收情况价格数据
  352. const cardData = [];
  353. this.chartData.series.forEach(series => {
  354. const price = series.data[index];
  355. if (price !== null) {
  356. cardData.push({
  357. model: this.model,
  358. price: price,
  359. recycleSituation: series.name,
  360. remark: '',
  361. recycleTime: date,
  362. });
  363. }
  364. });
  365. this.cardData = cardData;
  366. },
  367. getRandomColor() {
  368. var letters = '0123456789ABCDEF';
  369. var color = '#';
  370. for (var i = 0; i < 6; i++) {
  371. color += letters[Math.floor(Math.random() * 16)];
  372. }
  373. return color;
  374. },
  375. }
  376. }
  377. </script>
  378. <style lang="scss" scoped>
  379. .trend_modal {
  380. width: 100%;
  381. min-height: 500rpx;
  382. max-height: 100%;
  383. height: 100%;
  384. display: flex;
  385. flex-direction: column;
  386. gap: 20rpx;
  387. overflow-y: auto;
  388. }
  389. .charts_box {
  390. width: 100%;
  391. max-height: 600rpx;
  392. overflow: hidden;
  393. }
  394. .card-container {
  395. display: flex;
  396. flex-direction: column;
  397. gap: 16rpx;
  398. max-height: 200rpx;
  399. overflow-y: auto;
  400. font-size: 26rpx;
  401. &::-webkit-scrollbar {
  402. width: 6rpx;
  403. }
  404. &::-webkit-scrollbar-track {
  405. background: #f1f1f1;
  406. border-radius: 3rpx;
  407. }
  408. &::-webkit-scrollbar-thumb {
  409. background: #c1c1c1;
  410. border-radius: 3rpx;
  411. &:hover {
  412. background: #a8a8a8;
  413. }
  414. }
  415. }
  416. .card-item {
  417. background-color: #f5f5f5;
  418. border-radius: 8rpx;
  419. padding: 16rpx;
  420. box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
  421. }
  422. .card-row {
  423. display: flex;
  424. margin-bottom: 10rpx;
  425. align-items: flex-start;
  426. .card-label {
  427. flex-shrink: 0;
  428. }
  429. .card-value {
  430. flex: 1;
  431. word-break: break-all;
  432. }
  433. }
  434. </style>