index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. <template>
  2. <view class="detail-page">
  3. <!-- 背景层 -->
  4. <view class="header-bg"></view>
  5. <!-- 顶部内容层 -->
  6. <view class="header-section">
  7. <view class="header-content">
  8. <view class="title-group">
  9. <text class="main-title">感冒灵大批量跨区域客户</text>
  10. <text class="sub-title">订单维度:单笔订单实际盒数 {{ ">" }} 20000盒</text>
  11. <text class="sub-title">地域维度:出库企业所在省份 ≠ 入库企业所在省份</text>
  12. </view>
  13. <view class="stat-box">
  14. <text class="stat-num">{{ totalCount }}</text>
  15. <text class="stat-unit">家</text>
  16. </view>
  17. </view>
  18. <view class="header-toolbar">
  19. <view class="update-tip">
  20. <!-- <text class="tip-icon">🕒</text> -->
  21. <text>更新:{{
  22. formatDate(
  23. new Date().setDate(new Date().getDate() - 1),
  24. "YYYY-MM-DD",
  25. ) || "--"
  26. }}</text>
  27. </view>
  28. </view>
  29. </view>
  30. <!-- 列表区域 -->
  31. <scroll-view class="list-scroll" scroll-y="true" refresher-enabled :refresher-triggered="isRefreshing"
  32. @refresherrefresh="onRefresh">
  33. <view class="list-container">
  34. <view class="card-item" v-for="(item, index) in rows" :key="index" @click="toDetail(item)">
  35. <view class="card-header">
  36. <view class="header-left">
  37. <text class="index-num">{{ index + 1 }}</text>
  38. <text class="company-name">{{ item.customerName }}</text>
  39. <text class="level-tag" :class="getLevelClass(item.customerLevel)">
  40. {{ item.customerLevel }}
  41. </text>
  42. </view>
  43. <view class="header-right">
  44. <text class="alert-count">{{ item.totalWarningAmount }}次预警</text>
  45. </view>
  46. </view>
  47. <view class="card-body">
  48. <view class="info-grid">
  49. <view class="info-item">
  50. <text class="label">省份</text>
  51. <text class="value">{{ item.customerProvinceName }}</text>
  52. </view>
  53. <view class="info-item">
  54. <text class="label">责任人</text>
  55. <text class="value">{{ item.responsibleManager }}</text>
  56. </view>
  57. <view class="info-item">
  58. <text class="label">性质</text>
  59. <text class="value">{{ item.customerCategory }}</text>
  60. </view>
  61. </view>
  62. </view>
  63. </view>
  64. <view v-if="!loading && rows.length === 0" class="empty-data">
  65. <EmptyView text="无相关数据" />
  66. </view>
  67. </view>
  68. </scroll-view>
  69. <!-- 底部按钮区 -->
  70. <view class="footer-btn-area">
  71. <button class="action-btn history-btn" @click="handleHistory">
  72. 历史记录
  73. </button>
  74. <button class="action-btn export-btn" @click="handleExport">
  75. 导出至邮箱
  76. </button>
  77. </view>
  78. <!-- 蒙层 -->
  79. <view class="report-export-create-modal-mask" v-if="emailModalOpen" @click="closeEmailModal"
  80. @touchmove.stop.prevent></view>
  81. <!-- 邮箱导出弹窗 -->
  82. <view class="report-export-create-modal report-export-create-email-modal"
  83. :class="{ 'report-export-create-modal--open': emailModalOpen }" v-if="emailModalOpen">
  84. <view class="report-export-create-modal-title">
  85. <text>发送至邮箱</text>
  86. <view class="report-export-create-modal-close" @click.stop="closeEmailModal">×</view>
  87. </view>
  88. <view class="report-export-create-modal-body">
  89. <up-input v-model="emailForm.email" border="none" :customStyle="{
  90. backgroundColor: '#ebf3fb',
  91. height: '80rpx',
  92. paddingLeft: '20rpx',
  93. }" placeholder="请填写邮箱" @input="emailError = false">
  94. <template #suffix>
  95. <view style="margin-right: 20rpx; color: #666">@999.com.cn</view>
  96. </template>
  97. </up-input>
  98. <text v-if="emailError" class="report-export-error-text">请输入邮箱</text>
  99. </view>
  100. <view class="report-export-create-modal-footer">
  101. <view class="report-export-create-modal-btn cancel-btn" @click.stop="closeEmailModal">取消</view>
  102. <view class="report-export-create-modal-btn confirm-btn" @click.stop="handleSendEmail">发送</view>
  103. </view>
  104. </view>
  105. </view>
  106. </template>
  107. <script>
  108. import EmptyView from "../../../wigets/empty.vue";
  109. import request from "../../../request/index.js";
  110. import { formatDate } from "../../../utils/utils.js";
  111. export default {
  112. components: {
  113. EmptyView,
  114. },
  115. data() {
  116. return {
  117. isRefreshing: false,
  118. loading: false,
  119. rows: [],
  120. totalCount: 0,
  121. emailModalOpen: false,
  122. emailForm: {
  123. email: "",
  124. },
  125. emailError: false,
  126. };
  127. },
  128. created() {
  129. this.resetFetch();
  130. const userEmail = uni.getStorageSync('traceCode_useremail')
  131. this.emailForm.email = userEmail
  132. },
  133. methods: {
  134. formatDate,
  135. getLevelClass(level) {
  136. if (level === "VIP") return "tag-vip";
  137. if (level === "二级") return "tag-l2";
  138. if (level === "三级") return "tag-l3";
  139. return "tag-default";
  140. },
  141. fetchList() {
  142. if (this.loading) return;
  143. this.loading = true;
  144. request(
  145. `/report/ganmaoling/list?days=1`,
  146. {
  147. path: "traceabilityReport/pages/ganmaoling/index.vue",
  148. },
  149. "get",
  150. ).then((res) => {
  151. if (res.code == 200) {
  152. this.rows = res.data || [];
  153. this.totalCount = res.data?.length || 0;
  154. }
  155. this.loading = false;
  156. this.isRefreshing = false;
  157. });
  158. },
  159. async onRefresh() {
  160. this.isRefreshing = true;
  161. this.fetchList();
  162. },
  163. resetFetch() {
  164. this.rows = [];
  165. this.fetchList();
  166. },
  167. toDetail(item) {
  168. uni.navigateTo({
  169. url: `/traceCodePackages/traceabilityReport/pages/ganmaoling/detail/index?name=${encodeURIComponent(item.customerName)}&updatedTime=${item.updatedTime || ""}`,
  170. });
  171. },
  172. handleExport() {
  173. if (this.rows.length == 0) {
  174. uni.showToast({
  175. title: '暂无数据...',
  176. icon: 'none'
  177. })
  178. return
  179. }
  180. this.emailModalOpen = true;
  181. this.emailForm.email = "";
  182. this.emailError = false;
  183. },
  184. closeEmailModal() {
  185. this.emailModalOpen = false;
  186. },
  187. handleSendEmail() {
  188. if (!this.emailForm.email) {
  189. this.emailError = true;
  190. return;
  191. }
  192. uni.showLoading({ title: "发送中..." });
  193. const yesterday = new Date();
  194. yesterday.setDate(yesterday.getDate() - 1);
  195. const dateStr = this.formatDate(yesterday, "YYYY-MM-DD");
  196. request(
  197. `/report/ganmaoling/sendemail`,
  198. {
  199. updatedTime: dateStr,
  200. emailAddress: this.emailForm.email + "@999.com.cn",
  201. path: "traceabilityReport/pages/ganmaoling/index.vue",
  202. },
  203. "post",
  204. ).then((res) => {
  205. uni.hideLoading();
  206. if (res.code == 200) {
  207. uni.showToast({
  208. title: "发送成功",
  209. icon: "success",
  210. });
  211. this.closeEmailModal();
  212. } else {
  213. uni.showToast({
  214. title: res.msg || "发送失败",
  215. icon: "none",
  216. });
  217. }
  218. });
  219. },
  220. handleHistory() {
  221. uni.navigateTo({
  222. url: "/traceCodePackages/traceabilityReport/pages/ganmaoling/history/index",
  223. });
  224. },
  225. },
  226. };
  227. </script>
  228. <style scoped>
  229. .detail-page {
  230. display: flex;
  231. flex-direction: column;
  232. height: calc(100vh - 116rpx - env(safe-area-inset-bottom));
  233. box-sizing: border-box;
  234. background: #f5f7fa;
  235. position: relative;
  236. }
  237. /* Header Background Layer */
  238. .header-bg {
  239. position: absolute;
  240. top: 0;
  241. left: 0;
  242. width: 100%;
  243. height: 320rpx;
  244. /* Fixed height for background */
  245. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  246. border-bottom-left-radius: 40rpx;
  247. border-bottom-right-radius: 40rpx;
  248. z-index: 1;
  249. box-shadow: 0 10rpx 30rpx rgba(24, 144, 255, 0.2);
  250. }
  251. /* Header Content Layer */
  252. .header-section {
  253. padding: 40rpx 40rpx 80rpx;
  254. position: relative;
  255. z-index: 3;
  256. /* Above List */
  257. color: #fff;
  258. /* Transparent background to allow overlap effect */
  259. }
  260. .header-content {
  261. display: flex;
  262. justify-content: space-between;
  263. align-items: flex-start;
  264. position: relative;
  265. z-index: 2;
  266. margin-bottom: 30rpx;
  267. }
  268. .title-group {
  269. display: flex;
  270. flex-direction: column;
  271. flex: 1;
  272. margin-right: 20rpx;
  273. }
  274. .main-title {
  275. font-size: 44rpx;
  276. font-weight: bold;
  277. margin-bottom: 16rpx;
  278. letter-spacing: 2rpx;
  279. line-height: 1.2;
  280. }
  281. .sub-title {
  282. font-size: 24rpx;
  283. opacity: 0.9;
  284. margin-bottom: 8rpx;
  285. line-height: 1.4;
  286. }
  287. .stat-box {
  288. display: flex;
  289. align-items: baseline;
  290. }
  291. .stat-num {
  292. font-size: 56rpx;
  293. font-weight: bold;
  294. margin-right: 8rpx;
  295. font-family: "DINAlternate-Bold", sans-serif;
  296. }
  297. .stat-unit {
  298. font-size: 24rpx;
  299. opacity: 0.8;
  300. }
  301. .header-toolbar {
  302. display: flex;
  303. justify-content: space-between;
  304. align-items: center;
  305. position: relative;
  306. z-index: 3;
  307. }
  308. .update-tip {
  309. display: inline-flex;
  310. align-items: center;
  311. background: rgba(255, 255, 255, 0.15);
  312. padding: 8rpx 20rpx;
  313. border-radius: 30rpx;
  314. font-size: 22rpx;
  315. backdrop-filter: blur(10px);
  316. }
  317. .tip-icon {
  318. margin-right: 8rpx;
  319. font-size: 20rpx;
  320. }
  321. /* List Section */
  322. .list-scroll {
  323. flex: 1;
  324. height: 0;
  325. padding: 0 24rpx;
  326. box-sizing: border-box;
  327. margin-top: -50rpx;
  328. /* Overlap effect */
  329. position: relative;
  330. z-index: 2;
  331. /* Between bg and header content */
  332. }
  333. .list-container {
  334. padding-top: 10rpx;
  335. padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
  336. }
  337. .card-item {
  338. background: #fff;
  339. border-radius: 20rpx;
  340. padding: 30rpx;
  341. margin-bottom: 24rpx;
  342. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
  343. transition: transform 0.2s;
  344. }
  345. .card-item:active {
  346. transform: scale(0.99);
  347. }
  348. .card-header {
  349. display: flex;
  350. justify-content: space-between;
  351. align-items: flex-start;
  352. margin-bottom: 24rpx;
  353. }
  354. .header-left {
  355. flex: 1;
  356. display: flex;
  357. align-items: center;
  358. flex-wrap: wrap;
  359. margin-right: 20rpx;
  360. gap: 12rpx;
  361. }
  362. .index-num {
  363. font-size: 24rpx;
  364. color: #999;
  365. font-family: monospace;
  366. }
  367. .company-name {
  368. font-size: 32rpx;
  369. font-weight: 600;
  370. color: #096dd9;
  371. line-height: 1.4;
  372. text-decoration: underline;
  373. }
  374. .level-tag {
  375. font-size: 20rpx;
  376. padding: 4rpx 12rpx;
  377. border-radius: 8rpx;
  378. white-space: nowrap;
  379. line-height: 1.2;
  380. }
  381. .header-right {
  382. flex-shrink: 0;
  383. }
  384. .tag-vip {
  385. background: linear-gradient(135deg, #e6f7ff, #bae7ff);
  386. color: #096dd9;
  387. }
  388. .tag-l2 {
  389. background: linear-gradient(135deg, #f6ffed, #d9f7be);
  390. color: #389e0d;
  391. }
  392. .tag-l3 {
  393. background: linear-gradient(135deg, #fff7e6, #ffe7ba);
  394. color: #d46b08;
  395. }
  396. .tag-default {
  397. background: #f5f5f5;
  398. color: #999;
  399. }
  400. .card-body {
  401. background: #f9fbfd;
  402. border-radius: 12rpx;
  403. padding: 24rpx;
  404. }
  405. .info-grid {
  406. display: flex;
  407. flex-wrap: wrap;
  408. gap: 24rpx;
  409. }
  410. .info-item {
  411. display: flex;
  412. align-items: center;
  413. min-width: 45%;
  414. }
  415. .info-item .label {
  416. font-size: 24rpx;
  417. color: #999;
  418. margin-right: 12rpx;
  419. }
  420. .info-item .value {
  421. font-size: 26rpx;
  422. color: #666;
  423. font-weight: 500;
  424. }
  425. .alert-count {
  426. font-size: 22rpx;
  427. color: #ff4d4f;
  428. background: rgba(255, 77, 79, 0.1);
  429. padding: 6rpx 16rpx;
  430. border-radius: 20rpx;
  431. font-weight: 600;
  432. }
  433. .empty-data {
  434. padding-top: 120rpx;
  435. }
  436. .footer-btn-area {
  437. padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
  438. background: #fff;
  439. box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
  440. position: fixed;
  441. z-index: 100;
  442. bottom: 0;
  443. width: 100%;
  444. box-sizing: border-box;
  445. display: flex;
  446. gap: 24rpx;
  447. }
  448. .action-btn {
  449. flex: 1;
  450. height: 88rpx;
  451. line-height: 88rpx;
  452. border-radius: 44rpx;
  453. font-size: 30rpx;
  454. font-weight: 600;
  455. text-align: center;
  456. border: none;
  457. transition: all 0.3s;
  458. }
  459. .action-btn::after {
  460. border: none;
  461. }
  462. .action-btn:active {
  463. transform: scale(0.96);
  464. }
  465. .history-btn {
  466. background: #fff;
  467. color: #1890ff;
  468. border: 2rpx solid #1890ff;
  469. box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.1);
  470. }
  471. .export-btn {
  472. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  473. color: #fff;
  474. box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.35);
  475. }
  476. /* 弹窗样式 */
  477. .report-export-create-modal-mask {
  478. position: fixed;
  479. top: 0;
  480. left: 0;
  481. right: 0;
  482. bottom: 0;
  483. background: rgba(0, 0, 0, 0.6);
  484. z-index: 999;
  485. backdrop-filter: blur(2px);
  486. }
  487. .report-export-create-modal {
  488. position: fixed;
  489. z-index: 1000;
  490. background: #fff;
  491. transition: all 0.3s ease;
  492. }
  493. .report-export-create-email-modal {
  494. top: 50%;
  495. left: 50%;
  496. transform: translate(-50%, -50%);
  497. width: 620rpx;
  498. border-radius: 24rpx;
  499. opacity: 0;
  500. visibility: hidden;
  501. overflow: hidden;
  502. }
  503. .report-export-create-email-modal.report-export-create-modal--open {
  504. opacity: 1;
  505. visibility: visible;
  506. }
  507. .report-export-create-modal-title {
  508. padding: 36rpx 32rpx;
  509. font-size: 36rpx;
  510. color: #333;
  511. text-align: center;
  512. font-weight: 600;
  513. border-bottom: 1rpx solid #f0f0f0;
  514. position: relative;
  515. }
  516. .report-export-create-modal-close {
  517. position: absolute;
  518. right: 32rpx;
  519. top: 50%;
  520. transform: translateY(-50%);
  521. font-size: 44rpx;
  522. color: #999;
  523. line-height: 1;
  524. padding: 10rpx;
  525. }
  526. .report-export-create-modal-body {
  527. padding: 48rpx 40rpx 32rpx;
  528. }
  529. .report-export-error-text {
  530. font-size: 24rpx;
  531. color: #ff4d4f;
  532. margin-top: 12rpx;
  533. display: block;
  534. }
  535. .report-export-create-modal-footer {
  536. padding: 0 40rpx 48rpx;
  537. display: flex;
  538. justify-content: space-between;
  539. gap: 24rpx;
  540. }
  541. .report-export-create-modal-btn {
  542. height: 88rpx;
  543. line-height: 88rpx;
  544. border-radius: 44rpx;
  545. text-align: center;
  546. font-size: 30rpx;
  547. font-weight: 600;
  548. flex: 1;
  549. margin: 0;
  550. }
  551. .cancel-btn {
  552. background: #f5f7fa;
  553. color: #666;
  554. border: 1rpx solid #e4e7ed;
  555. }
  556. .confirm-btn {
  557. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  558. color: #fff;
  559. box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.3);
  560. }
  561. </style>