index.vue 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. <template>
  2. <view class="detail-page">
  3. <!-- 顶部状态栏 -->
  4. <view class="header-section">
  5. <view class="header-content">
  6. <view class="title-group">
  7. <text class="main-title">黑名单企业监控</text>
  8. <text class="sub-title">监控异常经营企业</text>
  9. </view>
  10. <view class="stat-box">
  11. <text class="stat-num">{{ rows?.length || 0 }}</text>
  12. <text class="stat-unit">家</text>
  13. </view>
  14. </view>
  15. <view class="header-toolbar">
  16. <view class="filter-tabs">
  17. <view class="tab-item" v-for="(tab, index) in timeFilters" :key="index"
  18. :class="{ active: currentFilter === tab.value }" @click="switchFilter(tab.value)">
  19. {{ tab.label }}
  20. </view>
  21. </view>
  22. <view class="filter-wrap">
  23. <view class="selector" @click.stop="toggleProvinceDropdown">
  24. <text class="selector-text">{{
  25. selectedProvince || "全部省份"
  26. }}</text>
  27. <text class="selector-arrow" :class="{ open: dropdownProvinceOpen }"></text>
  28. </view>
  29. </view>
  30. </view>
  31. <!-- 背景装饰 -->
  32. <view class="header-circle circle-1"></view>
  33. <view class="header-circle circle-2"></view>
  34. </view>
  35. <scroll-view class="list-scroll" scroll-y="true" refresher-enabled :refresher-triggered="isRefreshing"
  36. @refresherrefresh="onRefresh">
  37. <view class="list-container">
  38. <view class="date-group" v-for="groupItem in groupedRows" :key="groupItem.date">
  39. <view class="date-group-card">
  40. <view class="group-left">
  41. <view class="group-select-btn" :class="{
  42. selected: isGroupFullySelected(groupItem),
  43. partial: isGroupPartiallySelected(groupItem),
  44. }" @click.stop="toggleGroupSelection(groupItem)">
  45. <text v-if="isGroupFullySelected(groupItem)">✓</text>
  46. <text v-else-if="isGroupPartiallySelected(groupItem)">-</text>
  47. </view>
  48. <text class="date-label">{{ groupItem.date }}</text>
  49. </view>
  50. </view>
  51. <view class="card-item" v-for="(item, index) in groupItem.list" :key="item.__selectId || index"
  52. @click="toDetail(item)">
  53. <view class="card-header">
  54. <view class="header-left">
  55. <view class="company-select-btn" :class="{ selected: isCompanySelected(item) }"
  56. @click.stop="toggleCompanySelection(item)">
  57. <text v-if="isCompanySelected(item)">✓</text>
  58. </view>
  59. <text class="company-name">{{ item.queryCustomer }}</text>
  60. </view>
  61. <view class="header-right">
  62. <text class="status-tag">黑名单</text>
  63. </view>
  64. </view>
  65. <view class="card-body">
  66. <view class="code-row">
  67. <text class="label">信用代码:</text>
  68. <text class="value code-text">{{ item.socialCreditCode }}</text>
  69. </view>
  70. <view class="info-grid">
  71. <view class="info-item">
  72. <text class="label">省份</text>
  73. <text class="value">{{ item.province }}</text>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. <view v-if="!loading && rows.length === 0" class="empty-data">
  80. <EmptyView text="无相关数据" />
  81. </view>
  82. </view>
  83. </scroll-view>
  84. <view class="footer-btn-area">
  85. <button class="action-btn export-btn" @click="handleExport">
  86. 导出至邮箱
  87. </button>
  88. </view>
  89. <!-- 蒙层 -->
  90. <view class="report-export-create-modal-mask" v-if="emailModalOpen" @click="closeEmailModal"
  91. @touchmove.stop.prevent></view>
  92. <!-- 邮箱导出弹窗 -->
  93. <view class="report-export-create-modal report-export-create-email-modal"
  94. :class="{ 'report-export-create-modal--open': emailModalOpen }" v-if="emailModalOpen">
  95. <view class="report-export-create-modal-title">
  96. <text>发送至邮箱</text>
  97. <view class="report-export-create-modal-close" @click.stop="closeEmailModal">×</view>
  98. </view>
  99. <view class="report-export-create-modal-body" :style="{ padding: '40rpx' }">
  100. <up-input v-model="emailForm.email" border="none" :customStyle="{
  101. backgroundColor: '#ebf3fb',
  102. height: '80rpx',
  103. paddingLeft: '20rpx',
  104. }" placeholder="请填写邮箱" @input="emailError = false" disabled>
  105. <template #suffix>
  106. <view style="margin-right: 20rpx; color: #666">@999.com.cn</view>
  107. </template>
  108. </up-input>
  109. <text v-if="emailError" class="report-export-error-text">请输入邮箱</text>
  110. </view>
  111. <view class="report-export-create-modal-footer" :style="{
  112. padding: '20rpx 40rpx 40rpx',
  113. display: 'flex',
  114. 'justify-content': 'space-between',
  115. }">
  116. <view class="report-export-create-modal-btn cancel-btn" @click.stop="closeEmailModal">取消</view>
  117. <view class="report-export-create-modal-btn confirm-btn" @click.stop="handleSendEmail">发送</view>
  118. </view>
  119. </view>
  120. <!-- 独立的下拉菜单层 -->
  121. <view class="dropdown-layer" v-if="dropdownProvinceOpen" @click.stop>
  122. <view class="dropdown">
  123. <view class="dropdown-search-bar">
  124. <input class="dropdown-search-input" v-model="provinceSearchText" @input="onProvinceSearch"
  125. placeholder="搜索省份..." placeholder-style="color: #999" />
  126. </view>
  127. <scroll-view scroll-y="true" class="dropdown-scroll-view">
  128. <view class="dropdown-item" v-for="(p, i) in filteredProvinceList" :key="p || i" :class="{
  129. active: p === selectedProvince || (!selectedProvince && i === 0),
  130. }" @click.stop="selectProvince(p)">
  131. {{ p || "全部省份" }}
  132. <text v-if="p === selectedProvince || (!selectedProvince && i === 0)" class="check-mark">✓</text>
  133. </view>
  134. <view v-if="filteredProvinceList.length === 0" class="dropdown-empty">暂无数据</view>
  135. </scroll-view>
  136. </view>
  137. </view>
  138. </view>
  139. <BottomScrollTip :text="'下滑动查看更多内容'" :bottom="'140rpx'" />
  140. </template>
  141. <script>
  142. import EmptyView from "../../../wigets/empty.vue";
  143. import BottomScrollTip from "../../../wigets/BottomScrollTip.vue";
  144. import request from "../../../request/index.js";
  145. import { formatDate, guid } from "../../../utils/utils.js";
  146. export default {
  147. components: {
  148. EmptyView,
  149. BottomScrollTip
  150. },
  151. data() {
  152. return {
  153. isRefreshing: false,
  154. loading: false,
  155. rows: [],
  156. allRows: [],
  157. dropdownProvinceOpen: false,
  158. provinceSearchText: "",
  159. provinceList: [""],
  160. selectedProvince: "",
  161. currentFilter: "all",
  162. timeFilters: [
  163. { label: "全部", value: "all" },
  164. { label: "近7天", value: "7" },
  165. { label: "近15天", value: "15" },
  166. { label: "近30天", value: "30" },
  167. ],
  168. emailModalOpen: false,
  169. emailForm: {
  170. email: "",
  171. },
  172. emailError: false,
  173. // 仅用于展示/校验(不影响请求逻辑)
  174. currentFromDate: "",
  175. currentToDate: "",
  176. // 多选导出选择状态:存 socialCreditCode(或其他唯一字段)的集合
  177. selectedCompanyKeys: [],
  178. };
  179. },
  180. created() {
  181. this.resetFetch();
  182. this.getProviceList();
  183. const userEmail = uni.getStorageSync('traceCode_useremail')
  184. this.emailForm.email = userEmail
  185. },
  186. methods: {
  187. formatDate,
  188. switchFilter(value) {
  189. if (this.currentFilter === value) return;
  190. this.currentFilter = value;
  191. this.resetFetch();
  192. },
  193. // 用于选择的唯一 key(优先 socialCreditCode)
  194. getCompanyKey(item = {}) {
  195. return String(item.__selectId || item.socialCreditCode || item.id || item.queryCustomer || "");
  196. },
  197. isCompanySelected(item) {
  198. const key = this.getCompanyKey(item);
  199. if (!key) return false;
  200. return this.selectedCompanyKeys.includes(key);
  201. },
  202. toggleCompanySelection(item) {
  203. const key = this.getCompanyKey(item);
  204. if (!key) return;
  205. const has = this.selectedCompanyKeys.includes(key);
  206. if (has) {
  207. this.selectedCompanyKeys = this.selectedCompanyKeys.filter((k) => k !== key);
  208. } else {
  209. this.selectedCompanyKeys = [...this.selectedCompanyKeys, key];
  210. }
  211. },
  212. isGroupFullySelected(groupItem = {}) {
  213. const list = Array.isArray(groupItem.list) ? groupItem.list : [];
  214. if (!list.length) return false;
  215. return list.every((it) => this.isCompanySelected(it));
  216. },
  217. isGroupPartiallySelected(groupItem = {}) {
  218. const list = Array.isArray(groupItem.list) ? groupItem.list : [];
  219. if (!list.length) return false;
  220. const selectedCount = list.filter((it) => this.isCompanySelected(it)).length;
  221. return selectedCount > 0 && selectedCount < list.length;
  222. },
  223. toggleGroupSelection(groupItem = {}) {
  224. const list = Array.isArray(groupItem.list) ? groupItem.list : [];
  225. if (!list.length) return;
  226. const groupKeys = list.map((it) => this.getCompanyKey(it)).filter(Boolean);
  227. if (!groupKeys.length) return;
  228. const allSelected = groupKeys.every((k) =>
  229. this.selectedCompanyKeys.includes(k),
  230. );
  231. if (allSelected) {
  232. this.selectedCompanyKeys = this.selectedCompanyKeys.filter(
  233. (k) => !groupKeys.includes(k),
  234. );
  235. } else {
  236. const merged = new Set([...this.selectedCompanyKeys, ...groupKeys]);
  237. this.selectedCompanyKeys = Array.from(merged);
  238. }
  239. },
  240. getProviceList() {
  241. request("/common/getProviceList", {
  242. path: "traceabilityReport/pages/blacklist/index.vue",
  243. }).then((res) => {
  244. if (res.code == 200) {
  245. const _data = res.data || [];
  246. this.provinceList = ["", ..._data.map((item) => item.regionName)];
  247. }
  248. });
  249. },
  250. fetchList() {
  251. if (this.loading) return;
  252. this.loading = true;
  253. const today = new Date();
  254. const toDateStr = this.formatDate(today, "YYYY-MM-DD");
  255. let fromDateStr = "";
  256. if (this.currentFilter === "all") {
  257. const twoMonthsAgo = new Date();
  258. twoMonthsAgo.setMonth(today.getMonth() - 2);
  259. fromDateStr = this.formatDate(twoMonthsAgo, "YYYY-MM-DD");
  260. } else {
  261. const days = parseInt(this.currentFilter);
  262. const pastDate = new Date();
  263. pastDate.setDate(today.getDate() - days);
  264. fromDateStr = this.formatDate(pastDate, "YYYY-MM-DD");
  265. }
  266. request("/blacklist-report/get-export-company-data", {
  267. fromDate: fromDateStr,
  268. toDate: toDateStr,
  269. path: "traceabilityReport/pages/blacklist/index.vue",
  270. }).then((res) => {
  271. if (res.code == 200) {
  272. this.allRows = (res.data || []).map((it) => ({
  273. ...it,
  274. __selectId: it?.__selectId || guid("blk_"),
  275. }));
  276. this.applyFilter();
  277. }
  278. this.loading = false;
  279. this.isRefreshing = false;
  280. });
  281. },
  282. async onRefresh() {
  283. this.isRefreshing = true;
  284. this.fetchList();
  285. },
  286. resetFetch() {
  287. this.rows = [];
  288. this.selectedCompanyKeys = [];
  289. this.fetchList();
  290. },
  291. toDetail(item) {
  292. uni.navigateTo({
  293. url: `/traceCodePackages/traceabilityReport/pages/blacklist/detail/index?id=${item.socialCreditCode}&dateStr=${item.billTime}&name=${encodeURIComponent(item.queryCustomer)}`,
  294. });
  295. },
  296. handleHistory() {
  297. uni.navigateTo({
  298. url: "/traceCodePackages/traceabilityReport/pages/blacklist/history/index",
  299. });
  300. },
  301. handleExport() {
  302. if (this.selectedCompanyKeys?.length === 0) {
  303. uni.showToast({
  304. title: "请选择要导出的企业",
  305. icon: 'none'
  306. });
  307. return
  308. }
  309. this.emailModalOpen = true;
  310. this.emailError = false;
  311. },
  312. closeEmailModal() {
  313. this.emailModalOpen = false;
  314. },
  315. handleSendEmail() {
  316. if (!this.emailForm.email) {
  317. this.emailError = true;
  318. return;
  319. }
  320. const selectedTaskIds = this.getSelectedTaskIds();
  321. if (!selectedTaskIds.length) {
  322. uni.showToast({ title: "未找到可导出的任务", icon: "none" });
  323. return;
  324. }
  325. uni.showLoading({ title: "发送中..." });
  326. const doSingleSend = async (taskId) => {
  327. return request("/report/sendemail", {
  328. taskId,
  329. emailAddress: this.emailForm.email + "@999.com.cn",
  330. path: "traceabilityReport/pages/blacklist/index.vue",
  331. });
  332. };
  333. const run = async () => {
  334. try {
  335. // 先尝试一次性传多个 taskId(后端若支持)
  336. const res = await request("/report/sendemail", {
  337. taskId: selectedTaskIds,
  338. emailAddress: this.emailForm.email + "@999.com.cn",
  339. path: "traceabilityReport/pages/blacklist/index.vue",
  340. });
  341. if (res?.code == 200) {
  342. uni.hideLoading();
  343. uni.showToast({ title: "发送成功", icon: "success" });
  344. this.closeEmailModal();
  345. return;
  346. }
  347. } catch (e) {
  348. // ignore, fallback below
  349. }
  350. // 不支持 combinedTask:逐个发送
  351. for (let i = 0; i < selectedTaskIds.length; i++) {
  352. const taskId = selectedTaskIds[i];
  353. const r = await doSingleSend(taskId);
  354. if (r?.code != 200) {
  355. uni.hideLoading();
  356. uni.showToast({
  357. title: r?.msg || "发送失败",
  358. icon: "none",
  359. });
  360. return;
  361. }
  362. }
  363. uni.hideLoading();
  364. uni.showToast({ title: "发送成功", icon: "success" });
  365. this.closeEmailModal();
  366. };
  367. run();
  368. },
  369. getSelectedTaskIds() {
  370. const keys = new Set(this.selectedCompanyKeys || []);
  371. const selectedRows = (this.rows || []).filter((r) =>
  372. keys.has(this.getCompanyKey(r)),
  373. );
  374. const ids = selectedRows.map((r) => r?.taskId).filter(Boolean);
  375. return Array.from(new Set(ids));
  376. },
  377. applyFilter() {
  378. if (!this.selectedProvince) {
  379. this.rows = [...this.allRows];
  380. } else {
  381. this.rows = this.allRows.filter(item => item.customerProvinceName === this.selectedProvince);
  382. }
  383. this.totalCount = this.rows.length;
  384. // 切换省份/刷新数据后,清空已选企业,避免“跨筛选脏选中”
  385. this.selectedCompanyKeys = [];
  386. },
  387. toggleProvinceDropdown() {
  388. this.dropdownProvinceOpen = !this.dropdownProvinceOpen;
  389. },
  390. closeProvinceDropdown() {
  391. this.dropdownProvinceOpen = false;
  392. },
  393. selectProvince(province) {
  394. this.selectedProvince = province || "";
  395. this.dropdownProvinceOpen = false;
  396. this.applyFilter();
  397. },
  398. },
  399. computed: {
  400. filteredProvinceList() {
  401. if (!this.provinceSearchText) {
  402. return this.provinceList;
  403. }
  404. return this.provinceList.filter((p) =>
  405. p?.toLowerCase()?.includes(this.provinceSearchText.toLowerCase()),
  406. );
  407. },
  408. groupedRows() {
  409. const groups = {};
  410. (this.rows || []).forEach((item) => {
  411. const date = item?.billTime || "未知日期";
  412. if (!groups[date]) groups[date] = [];
  413. groups[date].push(item);
  414. });
  415. return Object.keys(groups)
  416. .sort(
  417. (a, b) =>
  418. new Date(String(b).replace(/-/g, "/")).getTime() -
  419. new Date(String(a).replace(/-/g, "/")).getTime(),
  420. )
  421. .map((date) => ({
  422. date,
  423. list: groups[date],
  424. }));
  425. },
  426. },
  427. };
  428. </script>
  429. <style scoped>
  430. .detail-page {
  431. display: flex;
  432. flex-direction: column;
  433. height: calc(100vh - 116rpx - env(safe-area-inset-bottom));
  434. box-sizing: border-box;
  435. background: #f5f7fa;
  436. }
  437. /* Header Section */
  438. .header-section {
  439. background: linear-gradient(135deg, #2b32b2 0%, #1488cc 100%);
  440. padding: 40rpx 40rpx 80rpx;
  441. /* Extra padding at bottom for overlap effect */
  442. position: relative;
  443. z-index: 20;
  444. color: #fff;
  445. border-bottom-left-radius: 40rpx;
  446. border-bottom-right-radius: 40rpx;
  447. overflow: hidden;
  448. box-shadow: 0 10rpx 30rpx rgba(20, 136, 204, 0.2);
  449. }
  450. .header-content {
  451. display: flex;
  452. justify-content: space-between;
  453. align-items: center;
  454. position: relative;
  455. z-index: 2;
  456. margin-bottom: 24rpx;
  457. }
  458. .title-group {
  459. display: flex;
  460. flex-direction: column;
  461. }
  462. .main-title {
  463. font-size: 40rpx;
  464. font-weight: bold;
  465. margin-bottom: 8rpx;
  466. letter-spacing: 2rpx;
  467. }
  468. .sub-title {
  469. font-size: 24rpx;
  470. opacity: 0.8;
  471. }
  472. .stat-box {
  473. display: flex;
  474. align-items: baseline;
  475. }
  476. .stat-num {
  477. font-size: 56rpx;
  478. font-weight: bold;
  479. margin-right: 8rpx;
  480. font-family: "DINAlternate-Bold", sans-serif;
  481. }
  482. .stat-unit {
  483. font-size: 24rpx;
  484. opacity: 0.8;
  485. }
  486. .header-toolbar {
  487. display: flex;
  488. justify-content: space-between;
  489. align-items: center;
  490. position: relative;
  491. z-index: 3;
  492. gap: 20rpx;
  493. }
  494. .update-tip {
  495. display: inline-flex;
  496. align-items: center;
  497. background: rgba(255, 255, 255, 0.15);
  498. padding: 8rpx 20rpx;
  499. border-radius: 30rpx;
  500. font-size: 22rpx;
  501. backdrop-filter: blur(10px);
  502. }
  503. .tip-icon {
  504. margin-right: 8rpx;
  505. font-size: 20rpx;
  506. }
  507. /* Filter Styles */
  508. .filter-wrap {
  509. position: relative;
  510. }
  511. .selector {
  512. display: flex;
  513. align-items: center;
  514. color: #fff;
  515. font-size: 26rpx;
  516. font-weight: 500;
  517. padding: 8rpx 20rpx;
  518. background: rgba(255, 255, 255, 0.2);
  519. border-radius: 30rpx;
  520. border: 1rpx solid rgba(255, 255, 255, 0.3);
  521. }
  522. .selector-text {
  523. max-width: 200rpx;
  524. overflow: hidden;
  525. white-space: nowrap;
  526. text-overflow: ellipsis;
  527. }
  528. .selector-arrow {
  529. margin-left: 10rpx;
  530. width: 0;
  531. height: 0;
  532. border-left: 8rpx solid transparent;
  533. border-right: 8rpx solid transparent;
  534. border-top: 10rpx solid #fff;
  535. transition: transform 0.3s;
  536. }
  537. .selector-arrow.open {
  538. transform: rotate(180deg);
  539. }
  540. /* Dropdown Layer (Absolute on top of everything) */
  541. .dropdown-layer {
  542. position: absolute;
  543. top: 300rpx;
  544. /* Adjust based on header layout */
  545. right: 40rpx;
  546. z-index: 999;
  547. }
  548. /* Header date filter tabs */
  549. .filter-tabs {
  550. display: flex;
  551. background: rgba(255, 255, 255, 0.2);
  552. border-radius: 16rpx;
  553. padding: 6rpx 6rpx;
  554. z-index: 30;
  555. position: relative;
  556. flex: 1;
  557. max-width: 420rpx;
  558. }
  559. .tab-item {
  560. flex: 1;
  561. text-align: center;
  562. font-size: 24rpx;
  563. padding: 10rpx 0;
  564. border-radius: 12rpx;
  565. color: rgba(255, 255, 255, 0.8);
  566. transition: all 0.3s;
  567. }
  568. .tab-item.active {
  569. background: #fff;
  570. color: #2b32b2;
  571. font-weight: 600;
  572. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  573. }
  574. /* Date-group (history style) */
  575. .date-group {
  576. margin-bottom: 40rpx;
  577. }
  578. .date-group-card {
  579. display: flex;
  580. align-items: center;
  581. justify-content: flex-start;
  582. background: #fff;
  583. padding: 20rpx 30rpx;
  584. border-radius: 16rpx;
  585. margin-bottom: 20rpx;
  586. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
  587. border-left: 8rpx solid #1488cc;
  588. }
  589. .group-left {
  590. display: flex;
  591. align-items: center;
  592. }
  593. .date-label {
  594. font-size: 30rpx;
  595. font-weight: 600;
  596. color: #333;
  597. }
  598. .group-right {
  599. flex-shrink: 0;
  600. }
  601. .group-select-btn {
  602. width: 44rpx;
  603. height: 44rpx;
  604. border-radius: 50%;
  605. border: 2rpx solid #1488cc;
  606. color: #1488cc;
  607. display: flex;
  608. align-items: center;
  609. justify-content: center;
  610. font-size: 26rpx;
  611. background: rgba(20, 136, 204, 0.06);
  612. margin-right: 18rpx;
  613. }
  614. .group-select-btn.selected {
  615. background: #1488cc;
  616. color: #fff;
  617. }
  618. .group-select-btn.partial {
  619. background: rgba(20, 136, 204, 0.15);
  620. }
  621. .company-select-btn {
  622. width: 44rpx;
  623. height: 44rpx;
  624. border-radius: 50%;
  625. border: 2rpx solid #d0d7e2;
  626. color: #d0d7e2;
  627. display: flex;
  628. align-items: center;
  629. justify-content: center;
  630. margin-right: 14rpx;
  631. font-size: 26rpx;
  632. background: #fff;
  633. }
  634. .company-select-btn.selected {
  635. border-color: #1488cc;
  636. color: #1488cc;
  637. background: rgba(20, 136, 204, 0.08);
  638. }
  639. .dropdown {
  640. width: 360rpx;
  641. background: #fff;
  642. border-radius: 12rpx;
  643. box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.15);
  644. overflow: hidden;
  645. }
  646. .dropdown-search-bar {
  647. padding: 16rpx;
  648. border-bottom: 1rpx solid #f0f0f0;
  649. }
  650. .dropdown-search-input {
  651. background: #f5f7fa;
  652. height: 64rpx;
  653. border-radius: 32rpx;
  654. padding: 0 24rpx;
  655. font-size: 26rpx;
  656. }
  657. .dropdown-scroll-view {
  658. max-height: 400rpx;
  659. }
  660. .dropdown-item {
  661. padding: 20rpx 30rpx;
  662. font-size: 28rpx;
  663. color: #333;
  664. display: flex;
  665. justify-content: space-between;
  666. align-items: center;
  667. transition: background 0.2s;
  668. }
  669. .dropdown-item:active {
  670. background: #f5f7fa;
  671. }
  672. .dropdown-item.active {
  673. color: #1890ff;
  674. font-weight: 500;
  675. background: #e6f7ff;
  676. }
  677. .check-mark {
  678. font-size: 24rpx;
  679. }
  680. .dropdown-empty {
  681. padding: 40rpx;
  682. text-align: center;
  683. color: #999;
  684. font-size: 26rpx;
  685. }
  686. /* Header Decor Circles */
  687. .header-circle {
  688. position: absolute;
  689. border-radius: 50%;
  690. background: rgba(255, 255, 255, 0.05);
  691. }
  692. .circle-1 {
  693. width: 300rpx;
  694. height: 300rpx;
  695. top: -100rpx;
  696. right: -50rpx;
  697. }
  698. .circle-2 {
  699. width: 200rpx;
  700. height: 200rpx;
  701. bottom: -50rpx;
  702. left: -50rpx;
  703. }
  704. /* List Scroll */
  705. .list-scroll {
  706. flex: 1;
  707. height: 0;
  708. padding: 0 24rpx;
  709. /* Remove vertical padding from scroll container */
  710. box-sizing: border-box;
  711. margin-top: -50rpx;
  712. /* Negative margin to pull list up */
  713. position: relative;
  714. z-index: 21;
  715. }
  716. .list-container {
  717. padding-top: 10rpx;
  718. padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
  719. }
  720. .card-item {
  721. background: #fff;
  722. border-radius: 20rpx;
  723. padding: 30rpx;
  724. margin-bottom: 24rpx;
  725. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
  726. transition: transform 0.2s;
  727. }
  728. .card-item:active {
  729. transform: scale(0.99);
  730. }
  731. .card-header {
  732. display: flex;
  733. justify-content: space-between;
  734. align-items: flex-start;
  735. margin-bottom: 24rpx;
  736. }
  737. .header-left {
  738. flex: 1;
  739. display: flex;
  740. align-items: center;
  741. margin-right: 20rpx;
  742. }
  743. .index-num {
  744. font-size: 24rpx;
  745. color: #999;
  746. margin-right: 12rpx;
  747. font-family: monospace;
  748. }
  749. .company-name {
  750. font-size: 32rpx;
  751. font-weight: 600;
  752. color: #096dd9;
  753. line-height: 1.4;
  754. text-decoration: underline;
  755. }
  756. .header-right {
  757. flex-shrink: 0;
  758. }
  759. .status-tag {
  760. font-size: 20rpx;
  761. padding: 6rpx 16rpx;
  762. border-radius: 20rpx;
  763. background: #fff1f0;
  764. color: #ff4d4f;
  765. font-weight: 600;
  766. border: 1rpx solid rgba(255, 77, 79, 0.2);
  767. }
  768. .card-body {
  769. background: #f9fbfd;
  770. border-radius: 12rpx;
  771. padding: 24rpx;
  772. }
  773. .code-row {
  774. display: flex;
  775. align-items: center;
  776. margin-bottom: 20rpx;
  777. padding-bottom: 20rpx;
  778. border-bottom: 1rpx solid #edf0f5;
  779. }
  780. .code-row:last-child {
  781. margin-bottom: 0;
  782. padding-bottom: 0;
  783. border-bottom: none;
  784. }
  785. .code-text {
  786. font-family: monospace;
  787. color: #333 !important;
  788. font-weight: 600 !important;
  789. letter-spacing: 1rpx;
  790. }
  791. .info-grid {
  792. display: flex;
  793. flex-wrap: wrap;
  794. gap: 24rpx;
  795. }
  796. .info-item {
  797. display: flex;
  798. align-items: center;
  799. width: 45%;
  800. }
  801. .info-item .label,
  802. .code-row .label {
  803. font-size: 24rpx;
  804. color: #999;
  805. margin-right: 12rpx;
  806. }
  807. .info-item .value,
  808. .code-row .value {
  809. font-size: 26rpx;
  810. color: #666;
  811. font-weight: 500;
  812. }
  813. /* Loading & Footer */
  814. .loading-more {
  815. display: flex;
  816. justify-content: center;
  817. align-items: center;
  818. padding: 30rpx 0;
  819. }
  820. .loading-text {
  821. font-size: 24rpx;
  822. color: #999;
  823. }
  824. .loading-icon {
  825. width: 32rpx;
  826. height: 32rpx;
  827. margin-right: 12rpx;
  828. animation: spin 1s linear infinite;
  829. }
  830. .empty-data {
  831. padding-top: 120rpx;
  832. }
  833. .no-more {
  834. text-align: center;
  835. color: #ccc;
  836. font-size: 24rpx;
  837. padding: 30rpx 0;
  838. }
  839. @keyframes spin {
  840. from {
  841. transform: rotate(0deg);
  842. }
  843. to {
  844. transform: rotate(360deg);
  845. }
  846. }
  847. .footer-btn-area {
  848. padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
  849. background: #fff;
  850. box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
  851. position: fixed;
  852. z-index: 100;
  853. bottom: 0;
  854. width: 100%;
  855. box-sizing: border-box;
  856. display: flex;
  857. gap: 24rpx;
  858. }
  859. .action-btn {
  860. flex: 1;
  861. height: 88rpx;
  862. line-height: 88rpx;
  863. border-radius: 44rpx;
  864. font-size: 30rpx;
  865. font-weight: 600;
  866. text-align: center;
  867. border: none;
  868. transition: all 0.3s;
  869. }
  870. .action-btn::after {
  871. border: none;
  872. }
  873. .action-btn:active {
  874. transform: scale(0.96);
  875. }
  876. .history-btn {
  877. background: #fff;
  878. color: #1890ff;
  879. border: 2rpx solid #1890ff;
  880. box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.1);
  881. }
  882. .export-btn {
  883. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  884. color: #fff;
  885. box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.35);
  886. }
  887. /* 弹窗样式 */
  888. .report-export-create-modal-mask {
  889. position: fixed;
  890. top: 0;
  891. left: 0;
  892. right: 0;
  893. bottom: 0;
  894. background: rgba(0, 0, 0, 0.6);
  895. z-index: 999;
  896. backdrop-filter: blur(2px);
  897. }
  898. .report-export-create-modal {
  899. position: fixed;
  900. z-index: 1000;
  901. background: #fff;
  902. transition: all 0.3s ease;
  903. }
  904. .report-export-create-email-modal {
  905. top: 50%;
  906. left: 50%;
  907. transform: translate(-50%, -50%);
  908. width: 620rpx;
  909. border-radius: 24rpx;
  910. opacity: 0;
  911. visibility: hidden;
  912. overflow: hidden;
  913. }
  914. .report-export-create-email-modal.report-export-create-modal--open {
  915. opacity: 1;
  916. visibility: visible;
  917. }
  918. .report-export-create-modal-title {
  919. padding: 36rpx 32rpx;
  920. font-size: 36rpx;
  921. color: #333;
  922. text-align: center;
  923. font-weight: 600;
  924. border-bottom: 1rpx solid #f0f0f0;
  925. position: relative;
  926. }
  927. .report-export-create-modal-close {
  928. position: absolute;
  929. right: 32rpx;
  930. top: 50%;
  931. transform: translateY(-50%);
  932. font-size: 44rpx;
  933. color: #999;
  934. line-height: 1;
  935. padding: 10rpx;
  936. }
  937. .report-export-create-modal-body {
  938. padding: 48rpx 40rpx 32rpx;
  939. }
  940. .report-export-error-text {
  941. font-size: 24rpx;
  942. color: #ff4d4f;
  943. margin-top: 12rpx;
  944. display: block;
  945. }
  946. .report-export-create-modal-footer {
  947. padding: 0 40rpx 48rpx;
  948. display: flex;
  949. justify-content: space-between;
  950. gap: 24rpx;
  951. }
  952. .report-export-create-modal-btn {
  953. height: 88rpx;
  954. line-height: 88rpx;
  955. border-radius: 44rpx;
  956. text-align: center;
  957. font-size: 30rpx;
  958. font-weight: 600;
  959. flex: 1;
  960. margin: 0;
  961. }
  962. .cancel-btn {
  963. background: #f5f7fa;
  964. color: #666;
  965. border: 1rpx solid #e4e7ed;
  966. }
  967. .confirm-btn {
  968. background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
  969. color: #fff;
  970. box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.3);
  971. }
  972. </style>