index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. <template>
  2. <view class="history-page">
  3. <view class="header-section">
  4. <view class="header-title">历史记录</view>
  5. <view class="filter-tabs">
  6. <view
  7. class="tab-item"
  8. v-for="(tab, index) in timeFilters"
  9. :key="index"
  10. :class="{ active: currentFilter === tab.value }"
  11. @click="switchFilter(tab.value)"
  12. >
  13. {{ tab.label }}
  14. </view>
  15. </view>
  16. </view>
  17. <scroll-view
  18. class="list-scroll"
  19. scroll-y="true"
  20. refresher-enabled
  21. :refresher-triggered="isRefreshing"
  22. @refresherrefresh="onRefresh"
  23. >
  24. <view class="list-container">
  25. <view
  26. class="date-group"
  27. v-for="groupItem in groupedRows"
  28. :key="groupItem.date"
  29. >
  30. <view class="date-group-card">
  31. <view class="group-left">
  32. <!-- <text class="calendar-icon">📅</text> -->
  33. <text class="date-label">{{ groupItem.date }}</text>
  34. </view>
  35. <button class="group-export-btn" @click.stop="handleExportByDate(groupItem.date)">
  36. 导出
  37. </button>
  38. </view>
  39. <view
  40. class="card-item"
  41. v-for="(item, index) in groupItem.list"
  42. :key="index"
  43. @click="toDetail(item)"
  44. >
  45. <view class="card-header">
  46. <view class="header-left">
  47. <text class="index-num">{{ index + 1 }}</text>
  48. <text class="company-name">{{ item.customerName }}</text>
  49. <text
  50. class="level-tag"
  51. :class="getLevelClass(item.customerLevel)"
  52. >
  53. {{ item.customerLevel }}
  54. </text>
  55. </view>
  56. <view class="header-right">
  57. <text class="alert-count" v-if="item.totalWarningAmount > 0"
  58. >{{ item.totalWarningAmount }}次预警</text
  59. >
  60. </view>
  61. </view>
  62. <view class="card-body">
  63. <view class="info-grid">
  64. <view class="info-item">
  65. <text class="label">省份</text>
  66. <text class="value">{{ item.customerProvinceName }}</text>
  67. </view>
  68. <view class="info-item">
  69. <text class="label">性质</text>
  70. <text class="value">{{ item.responsibleManager }}</text>
  71. </view>
  72. <view class="info-item">
  73. <text class="label">责任人</text>
  74. <text class="value">{{ item.customerCategory }}</text>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. </view>
  80. <view v-if="loading" class="loading-more">
  81. <image
  82. class="loading-icon"
  83. src="../../../../static/images/loading.png"
  84. />
  85. <text class="loading-text">加载中...</text>
  86. </view>
  87. <view v-if="!loading && rows.length === 0" class="empty-data">
  88. <EmptyView text="暂无历史记录" />
  89. </view>
  90. </view>
  91. </scroll-view>
  92. <!-- 蒙层 -->
  93. <view
  94. class="report-export-create-modal-mask"
  95. v-if="emailModalOpen"
  96. @click="closeEmailModal"
  97. @touchmove.stop.prevent
  98. ></view>
  99. <!-- 邮箱导出弹窗 -->
  100. <view
  101. class="report-export-create-modal report-export-create-email-modal"
  102. :class="{ 'report-export-create-modal--open': emailModalOpen }"
  103. v-if="emailModalOpen"
  104. >
  105. <view class="report-export-create-modal-title">
  106. <text>发送至邮箱</text>
  107. <view
  108. class="report-export-create-modal-close"
  109. @click.stop="closeEmailModal"
  110. >×</view
  111. >
  112. </view>
  113. <view
  114. class="report-export-create-modal-body"
  115. >
  116. <view class="export-date-tip">导出日期:{{ currentExportDate }}</view>
  117. <up-input
  118. v-model="emailForm.email"
  119. border="none"
  120. :customStyle="{
  121. backgroundColor: '#ebf3fb',
  122. height: '80rpx',
  123. paddingLeft: '20rpx',
  124. }"
  125. placeholder="请填写邮箱"
  126. @input="emailError = false"
  127. >
  128. <template #suffix>
  129. <view style="margin-right: 20rpx; color: #666">@999.com.cn</view>
  130. </template>
  131. </up-input>
  132. <text v-if="emailError" class="report-export-error-text"
  133. >请输入邮箱</text
  134. >
  135. </view>
  136. <view
  137. class="report-export-create-modal-footer"
  138. >
  139. <view
  140. class="report-export-create-modal-btn cancel-btn"
  141. @click.stop="closeEmailModal"
  142. >取消</view
  143. >
  144. <view
  145. class="report-export-create-modal-btn confirm-btn"
  146. @click.stop="handleSendEmail"
  147. >发送</view
  148. >
  149. </view>
  150. </view>
  151. </view>
  152. </template>
  153. <script>
  154. import EmptyView from "../../../../wigets/empty.vue";
  155. import { formatDate } from "../../../../utils/utils.js";
  156. import request from "../../../../request/index.js";
  157. export default {
  158. components: {
  159. EmptyView,
  160. },
  161. data() {
  162. return {
  163. isRefreshing: false,
  164. loading: false,
  165. rows: [],
  166. currentFilter: "all",
  167. timeFilters: [
  168. { label: "全部", value: "all" },
  169. { label: "近7天", value: "7" },
  170. { label: "近15天", value: "15" },
  171. { label: "近30天", value: "30" },
  172. ],
  173. emailModalOpen: false,
  174. emailForm: {
  175. email: "",
  176. },
  177. emailError: false,
  178. currentExportDate: "",
  179. };
  180. },
  181. computed: {
  182. groupedRows() {
  183. const groups = {};
  184. this.rows.forEach((item) => {
  185. // 假设接口返回的时间字段为 updatedTime
  186. const fullTime = item.updatedTime || "";
  187. const date = fullTime.split(" ")[0] || fullTime.split("T")[0] || "未知日期";
  188. if (!groups[date]) {
  189. groups[date] = [];
  190. }
  191. groups[date].push(item);
  192. });
  193. // 按日期倒序排列
  194. return Object.keys(groups)
  195. .sort((a, b) => new Date(b.replace(/-/g, "/")) - new Date(a.replace(/-/g, "/")))
  196. .map((date) => ({
  197. date,
  198. list: groups[date],
  199. }));
  200. },
  201. },
  202. created() {
  203. this.resetFetch();
  204. },
  205. methods: {
  206. formatDate,
  207. getLevelClass(level) {
  208. if (level === "VIP") return "tag-vip";
  209. if (level === "二级") return "tag-l2";
  210. if (level === "三级") return "tag-l3";
  211. return "tag-default";
  212. },
  213. switchFilter(value) {
  214. if (this.currentFilter === value) return;
  215. this.currentFilter = value;
  216. this.resetFetch();
  217. },
  218. fetchList() {
  219. if (this.loading) return;
  220. this.loading = true;
  221. const params = {
  222. path: "traceabilityReport/pages/ganmaoling/history/index.vue",
  223. };
  224. if (this.currentFilter !== "all") {
  225. params.days = this.currentFilter;
  226. }
  227. request(`/report/ganmaoling/list`, params,"get").then((res) => {
  228. if (res.code == 200) {
  229. this.rows = res.data || [];
  230. }
  231. this.loading = false;
  232. this.isRefreshing = false;
  233. });
  234. },
  235. async onRefresh() {
  236. this.isRefreshing = true;
  237. this.fetchList();
  238. },
  239. resetFetch() {
  240. this.rows = [];
  241. this.fetchList();
  242. },
  243. toDetail(item) {
  244. uni.navigateTo({
  245. url: `/traceCodePackages/traceabilityReport/pages/ganmaoling/detail/index?name=${encodeURIComponent(item.customerName)}&updatedTime=${item.updatedTime || ""}`,
  246. });
  247. },
  248. handleExportByDate(date) {
  249. this.currentExportDate = date;
  250. this.emailModalOpen = true;
  251. this.emailForm.email = "";
  252. this.emailError = false;
  253. },
  254. closeEmailModal() {
  255. this.emailModalOpen = false;
  256. },
  257. handleSendEmail() {
  258. if (!this.emailForm.email) {
  259. this.emailError = true;
  260. return;
  261. }
  262. uni.showLoading({ title: "发送中..." });
  263. // 根据实际接口文档,如果支持按日期导出,则传日期
  264. request(
  265. `/report/ganmaoling/list`,
  266. {
  267. updatedTime: this.currentExportDate,
  268. email: this.emailForm.email + "@999.com.cn",
  269. path: "traceabilityReport/pages/ganmaoling/history/index.vue",
  270. },
  271. "get",
  272. ).then((res) => {
  273. uni.hideLoading();
  274. if (res.code == 200) {
  275. uni.showToast({
  276. title: "发送成功",
  277. icon: "success",
  278. });
  279. this.closeEmailModal();
  280. } else {
  281. uni.showToast({
  282. title: res.msg || "发送失败",
  283. icon: "none",
  284. });
  285. }
  286. });
  287. },
  288. },
  289. };
  290. </script>
  291. <style scoped>
  292. .level-tag {
  293. font-size: 20rpx;
  294. padding: 4rpx 12rpx;
  295. border-radius: 8rpx;
  296. white-space: nowrap;
  297. line-height: 1.2;
  298. margin-left: 10rpx;
  299. display: flex;
  300. align-items: center;
  301. justify-content: center;
  302. }
  303. .header-right {
  304. flex-shrink: 0;
  305. }
  306. .header-left {
  307. flex: 1;
  308. display: flex;
  309. align-items: center;
  310. flex-wrap: wrap;
  311. margin-right: 20rpx;
  312. gap: 12rpx;
  313. }
  314. .index-num {
  315. font-size: 24rpx;
  316. color: #999;
  317. font-family: monospace;
  318. }
  319. .company-name {
  320. font-size: 32rpx;
  321. font-weight: 600;
  322. color: #096dd9;
  323. line-height: 1.4;
  324. text-decoration: underline;
  325. }
  326. .tag-vip {
  327. background: linear-gradient(135deg, #e6f7ff, #bae7ff);
  328. color: #096dd9;
  329. }
  330. .tag-l2 {
  331. background: linear-gradient(135deg, #f6ffed, #d9f7be);
  332. color: #389e0d;
  333. }
  334. .tag-l3 {
  335. background: linear-gradient(135deg, #fff7e6, #ffe7ba);
  336. color: #d46b08;
  337. }
  338. .history-page {
  339. display: flex;
  340. flex-direction: column;
  341. height: 100vh;
  342. background: #f5f7fa;
  343. }
  344. .header-section {
  345. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  346. padding: 30rpx 30rpx 60rpx;
  347. color: #fff;
  348. border-bottom-left-radius: 40rpx;
  349. border-bottom-right-radius: 40rpx;
  350. box-shadow: 0 10rpx 30rpx rgba(24, 144, 255, 0.2);
  351. z-index: 10;
  352. position: relative;
  353. }
  354. .header-title {
  355. font-size: 36rpx;
  356. font-weight: bold;
  357. text-align: center;
  358. margin-bottom: 30rpx;
  359. }
  360. .filter-tabs {
  361. display: flex;
  362. background: rgba(255, 255, 255, 0.2);
  363. border-radius: 16rpx;
  364. padding: 8rpx;
  365. }
  366. .tab-item {
  367. flex: 1;
  368. text-align: center;
  369. font-size: 26rpx;
  370. padding: 12rpx 0;
  371. border-radius: 12rpx;
  372. color: rgba(255, 255, 255, 0.8);
  373. transition: all 0.3s;
  374. }
  375. .tab-item.active {
  376. background: #fff;
  377. color: #1890ff;
  378. font-weight: 600;
  379. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  380. }
  381. .list-scroll {
  382. flex: 1;
  383. height: 0;
  384. margin-top: -40rpx;
  385. z-index: 11;
  386. padding: 0 24rpx;
  387. box-sizing: border-box;
  388. }
  389. .list-container {
  390. padding-top: 50rpx;
  391. padding-bottom: 40rpx;
  392. }
  393. .date-group {
  394. margin-bottom: 40rpx;
  395. }
  396. .date-group-card {
  397. display: flex;
  398. align-items: center;
  399. justify-content: space-between;
  400. background: #fff;
  401. padding: 20rpx 30rpx;
  402. border-radius: 16rpx;
  403. margin-bottom: 20rpx;
  404. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
  405. border-left: 8rpx solid #1890ff;
  406. }
  407. .group-left {
  408. display: flex;
  409. align-items: center;
  410. }
  411. .calendar-icon {
  412. font-size: 32rpx;
  413. margin-right: 16rpx;
  414. }
  415. .date-label {
  416. font-size: 30rpx;
  417. font-weight: 600;
  418. color: #333;
  419. }
  420. .group-export-btn {
  421. margin: 0;
  422. padding: 0 24rpx;
  423. height: 52rpx;
  424. line-height: 50rpx;
  425. font-size: 24rpx;
  426. color: #1890ff;
  427. background: #fff;
  428. border: 2rpx solid #1890ff;
  429. border-radius: 26rpx;
  430. font-weight: 500;
  431. }
  432. .group-export-btn::after {
  433. border: none;
  434. }
  435. .group-export-btn:active {
  436. opacity: 0.7;
  437. transform: scale(0.95);
  438. }
  439. .card-item {
  440. background: #fff;
  441. border-radius: 20rpx;
  442. padding: 30rpx;
  443. margin-bottom: 24rpx;
  444. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
  445. }
  446. .card-header {
  447. display: flex;
  448. justify-content: space-between;
  449. align-items: center;
  450. margin-bottom: 20rpx;
  451. padding-bottom: 20rpx;
  452. border-bottom: 1rpx solid #f5f5f5;
  453. }
  454. .date-text {
  455. font-size: 28rpx;
  456. font-weight: 600;
  457. color: #333;
  458. }
  459. .alert-count {
  460. font-size: 22rpx;
  461. color: #ff4d4f;
  462. background: rgba(255, 77, 79, 0.1);
  463. padding: 4rpx 12rpx;
  464. border-radius: 8rpx;
  465. }
  466. .card-body {
  467. font-size: 26rpx;
  468. }
  469. .row {
  470. display: flex;
  471. margin-bottom: 16rpx;
  472. }
  473. .info-grid {
  474. display: flex;
  475. justify-content: space-between;
  476. }
  477. .info-item {
  478. display: flex;
  479. align-items: center;
  480. width: 48%;
  481. }
  482. .label {
  483. color: #999;
  484. margin-right: 12rpx;
  485. }
  486. .value {
  487. color: #666;
  488. font-weight: 500;
  489. }
  490. .loading-more,
  491. .no-more,
  492. .empty-data {
  493. display: flex;
  494. justify-content: center;
  495. align-items: center;
  496. padding: 30rpx 0;
  497. color: #999;
  498. font-size: 24rpx;
  499. }
  500. .loading-icon {
  501. width: 32rpx;
  502. height: 32rpx;
  503. margin-right: 12rpx;
  504. animation: spin 1s linear infinite;
  505. }
  506. @keyframes spin {
  507. from {
  508. transform: rotate(0deg);
  509. }
  510. to {
  511. transform: rotate(360deg);
  512. }
  513. }
  514. /* 弹窗样式 */
  515. .report-export-create-modal-mask {
  516. position: fixed;
  517. top: 0;
  518. left: 0;
  519. right: 0;
  520. bottom: 0;
  521. background: rgba(0, 0, 0, 0.6);
  522. z-index: 999;
  523. backdrop-filter: blur(2px);
  524. }
  525. .report-export-create-modal {
  526. position: fixed;
  527. z-index: 1000;
  528. background: #fff;
  529. transition: all 0.3s ease;
  530. }
  531. .report-export-create-email-modal {
  532. top: 50%;
  533. left: 50%;
  534. transform: translate(-50%, -50%);
  535. width: 620rpx;
  536. border-radius: 24rpx;
  537. opacity: 0;
  538. visibility: hidden;
  539. overflow: hidden;
  540. }
  541. .report-export-create-email-modal.report-export-create-modal--open {
  542. opacity: 1;
  543. visibility: visible;
  544. }
  545. .report-export-create-modal-title {
  546. padding: 36rpx 32rpx;
  547. font-size: 36rpx;
  548. color: #333;
  549. text-align: center;
  550. font-weight: 600;
  551. border-bottom: 1rpx solid #f0f0f0;
  552. position: relative;
  553. }
  554. .report-export-create-modal-close {
  555. position: absolute;
  556. right: 32rpx;
  557. top: 50%;
  558. transform: translateY(-50%);
  559. font-size: 44rpx;
  560. color: #999;
  561. line-height: 1;
  562. padding: 10rpx;
  563. }
  564. .report-export-create-modal-body {
  565. padding: 40rpx 40rpx 32rpx;
  566. }
  567. .export-date-tip {
  568. font-size: 26rpx;
  569. color: #1890ff;
  570. margin-bottom: 32rpx;
  571. background: #e6f7ff;
  572. padding: 20rpx 24rpx;
  573. border-radius: 12rpx;
  574. border-left: 6rpx solid #1890ff;
  575. font-weight: 500;
  576. }
  577. .report-export-error-text {
  578. font-size: 24rpx;
  579. color: #ff4d4f;
  580. margin-top: 12rpx;
  581. display: block;
  582. }
  583. .report-export-create-modal-footer {
  584. padding: 0 40rpx 48rpx;
  585. display: flex;
  586. justify-content: space-between;
  587. gap: 24rpx;
  588. }
  589. .report-export-create-modal-btn {
  590. height: 88rpx;
  591. line-height: 88rpx;
  592. border-radius: 44rpx;
  593. text-align: center;
  594. font-size: 30rpx;
  595. font-weight: 600;
  596. flex: 1;
  597. margin: 0;
  598. }
  599. .cancel-btn {
  600. background: #f5f7fa;
  601. color: #666;
  602. border: 1rpx solid #e4e7ed;
  603. }
  604. .confirm-btn {
  605. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  606. color: #fff;
  607. box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.3);
  608. }
  609. </style>