index.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <template>
  2. <view class="history-page">
  3. <view class="header-section">
  4. <view class="filter-tabs">
  5. <view
  6. class="tab-item"
  7. v-for="(tab, index) in timeFilters"
  8. :key="index"
  9. :class="{ active: currentFilter === tab.value }"
  10. @click="switchFilter(tab.value)"
  11. >
  12. {{ tab.label }}
  13. </view>
  14. </view>
  15. </view>
  16. <scroll-view
  17. class="list-scroll"
  18. scroll-y="true"
  19. refresher-enabled
  20. :refresher-triggered="isRefreshing"
  21. @refresherrefresh="onRefresh"
  22. @scrolltolower="onLoadMore"
  23. >
  24. <view class="list-container">
  25. <view
  26. class="card-item"
  27. v-for="(item, index) in displayRows"
  28. :key="index"
  29. >
  30. <view class="card-header">
  31. <view class="header-left">
  32. <text class="date-text">{{ item.date }}</text>
  33. </view>
  34. <view class="header-right">
  35. <text class="alert-count" v-if="item.alertCount > 0"
  36. >{{ item.alertCount }}次预警</text
  37. >
  38. </view>
  39. </view>
  40. <view class="card-body">
  41. <view class="row">
  42. <text class="label">公司名称:</text>
  43. <text class="value">{{ item.receiverName }}</text>
  44. </view>
  45. <view class="info-grid">
  46. <view class="info-item">
  47. <text class="label">省份</text>
  48. <text class="value">{{ item.receiverProvince }}</text>
  49. </view>
  50. <view class="info-item">
  51. <text class="label">性质</text>
  52. <text class="value">{{ item.customerNature }}</text>
  53. </view>
  54. </view>
  55. </view>
  56. </view>
  57. <view class="loading-more" v-if="loading">
  58. <image
  59. class="loading-icon"
  60. src="../../../../static/images/loading.png"
  61. />
  62. <text class="loading-text">加载中...</text>
  63. </view>
  64. <view v-if="!loading && displayRows.length === 0" class="empty-data">
  65. <EmptyView text="暂无历史记录" />
  66. </view>
  67. <view v-if="!hasMore && displayRows.length > 0" class="no-more">
  68. <text>没有更多数据了</text>
  69. </view>
  70. </view>
  71. </scroll-view>
  72. </view>
  73. </template>
  74. <script>
  75. import EmptyView from "../../../../wigets/empty.vue";
  76. import { formatDate } from "../../../../utils/utils.js";
  77. const PROVINCES = ["北京市", "上海市", "广东省", "浙江省", "江苏省"];
  78. export default {
  79. components: {
  80. EmptyView,
  81. },
  82. data() {
  83. return {
  84. isRefreshing: false,
  85. loading: false,
  86. rows: [],
  87. hasMore: true,
  88. pageNum: 1,
  89. pageSize: 20,
  90. currentFilter: "all",
  91. timeFilters: [
  92. { label: "全部", value: "all" },
  93. { label: "近7天", value: "7" },
  94. { label: "近15天", value: "15" },
  95. { label: "近30天", value: "30" },
  96. ],
  97. };
  98. },
  99. computed: {
  100. displayRows() {
  101. return this.rows;
  102. },
  103. },
  104. created() {
  105. this.resetFetch();
  106. },
  107. methods: {
  108. formatDate,
  109. switchFilter(value) {
  110. if (this.currentFilter === value) return;
  111. this.currentFilter = value;
  112. this.resetFetch();
  113. },
  114. generateFakeData() {
  115. const newRows = [];
  116. const count = 15;
  117. const today = new Date();
  118. for (let i = 0; i < count; i++) {
  119. const date = new Date(today);
  120. // Random date within last 30 days
  121. const daysBack = Math.floor(Math.random() * 30);
  122. // Filter logic simulation
  123. if (this.currentFilter !== "all") {
  124. const limit = parseInt(this.currentFilter);
  125. if (daysBack > limit) continue;
  126. }
  127. date.setDate(date.getDate() - daysBack);
  128. newRows.push({
  129. id: Math.random().toString(36).substr(2, 9),
  130. date: formatDate(date, "YYYY-MM-DD"),
  131. receiverName: `历史收货企业${Math.floor(Math.random() * 100)}有限公司`,
  132. receiverProvince:
  133. PROVINCES[Math.floor(Math.random() * PROVINCES.length)],
  134. customerNature: Math.random() > 0.5 ? "协议客户" : "非协议客户",
  135. alertCount: Math.floor(Math.random() * 5),
  136. });
  137. }
  138. // Sort by date descending
  139. newRows.sort((a, b) => new Date(b.date) - new Date(a.date));
  140. return newRows;
  141. },
  142. async onRefresh() {
  143. this.isRefreshing = true;
  144. this.pageNum = 1;
  145. this.hasMore = true;
  146. setTimeout(() => {
  147. this.rows = this.generateFakeData();
  148. this.isRefreshing = false;
  149. }, 1000);
  150. },
  151. onLoadMore() {
  152. if (this.loading || !this.hasMore) return;
  153. this.loading = true;
  154. this.pageNum++;
  155. setTimeout(() => {
  156. const more = this.generateFakeData();
  157. if (more.length > 0) {
  158. this.rows = [...this.rows, ...more];
  159. } else {
  160. this.hasMore = false;
  161. }
  162. this.loading = false;
  163. }, 800);
  164. },
  165. resetFetch() {
  166. this.loading = true;
  167. this.pageNum = 1;
  168. this.hasMore = true;
  169. this.rows = [];
  170. setTimeout(() => {
  171. this.rows = this.generateFakeData();
  172. this.loading = false;
  173. }, 500);
  174. },
  175. },
  176. };
  177. </script>
  178. <style scoped>
  179. .history-page {
  180. display: flex;
  181. flex-direction: column;
  182. height: 100vh;
  183. background: #f5f7fa;
  184. }
  185. .header-section {
  186. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  187. padding: 30rpx 30rpx 60rpx;
  188. color: #fff;
  189. border-bottom-left-radius: 40rpx;
  190. border-bottom-right-radius: 40rpx;
  191. box-shadow: 0 10rpx 30rpx rgba(24, 144, 255, 0.2);
  192. z-index: 10;
  193. position: relative;
  194. }
  195. .header-title {
  196. font-size: 36rpx;
  197. font-weight: bold;
  198. text-align: center;
  199. margin-bottom: 30rpx;
  200. }
  201. .filter-tabs {
  202. display: flex;
  203. background: rgba(255, 255, 255, 0.2);
  204. border-radius: 16rpx;
  205. padding: 8rpx;
  206. }
  207. .tab-item {
  208. flex: 1;
  209. text-align: center;
  210. font-size: 26rpx;
  211. padding: 12rpx 0;
  212. border-radius: 12rpx;
  213. color: rgba(255, 255, 255, 0.8);
  214. transition: all 0.3s;
  215. }
  216. .tab-item.active {
  217. background: #fff;
  218. color: #1890ff;
  219. font-weight: 600;
  220. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  221. }
  222. .list-scroll {
  223. flex: 1;
  224. height: 0;
  225. margin-top: -40rpx;
  226. z-index: 11;
  227. padding: 0 24rpx;
  228. box-sizing: border-box;
  229. }
  230. .list-container {
  231. padding-bottom: 40rpx;
  232. }
  233. .card-item {
  234. background: #fff;
  235. border-radius: 20rpx;
  236. padding: 30rpx;
  237. margin-bottom: 24rpx;
  238. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
  239. }
  240. .card-header {
  241. display: flex;
  242. justify-content: space-between;
  243. align-items: center;
  244. margin-bottom: 20rpx;
  245. padding-bottom: 20rpx;
  246. border-bottom: 1rpx solid #f5f5f5;
  247. }
  248. .date-text {
  249. font-size: 28rpx;
  250. font-weight: 600;
  251. color: #333;
  252. }
  253. .alert-count {
  254. font-size: 22rpx;
  255. color: #ff4d4f;
  256. background: rgba(255, 77, 79, 0.1);
  257. padding: 4rpx 12rpx;
  258. border-radius: 8rpx;
  259. }
  260. .card-body {
  261. font-size: 26rpx;
  262. }
  263. .row {
  264. display: flex;
  265. margin-bottom: 16rpx;
  266. }
  267. .info-grid {
  268. display: flex;
  269. justify-content: space-between;
  270. }
  271. .info-item {
  272. display: flex;
  273. align-items: center;
  274. width: 48%;
  275. }
  276. .label {
  277. color: #999;
  278. margin-right: 12rpx;
  279. }
  280. .value {
  281. color: #666;
  282. font-weight: 500;
  283. }
  284. .loading-more,
  285. .no-more,
  286. .empty-data {
  287. display: flex;
  288. justify-content: center;
  289. align-items: center;
  290. padding: 30rpx 0;
  291. color: #999;
  292. font-size: 24rpx;
  293. }
  294. .loading-icon {
  295. width: 32rpx;
  296. height: 32rpx;
  297. margin-right: 12rpx;
  298. animation: spin 1s linear infinite;
  299. }
  300. @keyframes spin {
  301. from {
  302. transform: rotate(0deg);
  303. }
  304. to {
  305. transform: rotate(360deg);
  306. }
  307. }
  308. </style>