list.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. <template>
  2. <view class="page-container">
  3. <nav-bar title="推荐官"></nav-bar>
  4. <view class="top-section">
  5. <view class="flex align-center justify-between">
  6. <view class="left flex align-center">
  7. <image src="@/static/images/my/badge.png" class="badge-icon"></image>
  8. <view>
  9. <view class="name">金牌推荐官</view>
  10. <view class="sub-name">{{ userName }}/{{ userPhone }}</view>
  11. </view>
  12. </view>
  13. <view class="poster-btn" @click="getPosterData">获取专属推荐海报</view>
  14. </view>
  15. <view class="progress-wrapper">
  16. <view class="progress-bar-wrapper">
  17. <view class="progress-bar" :style="{ width: progressBarWidth }"></view>
  18. </view>
  19. <image src="@/static/images/my/badge2.png" class="badge badge1"></image>
  20. <image src="@/static/images/my/badge2.png" class="badge badge2"></image>
  21. <image src="@/static/images/my/badge2.png" class="badge badge3"></image>
  22. </view>
  23. <view class="names flex align-center">
  24. <view class="name">推荐10人</view>
  25. <view class="name">推荐20人</view>
  26. <view class="name">推荐30人</view>
  27. </view>
  28. </view>
  29. <view class="toolbar-row flex align-center justify-between margin">
  30. <view class="add-btn" @click="getWechatQrcode">点击添加企微兑现权益</view>
  31. <view class="time-button flex align-center" @click="openDatePicker">
  32. <text>{{ dateFilterText }}</text>
  33. <u-icon name="arrow-down" size="24" style="margin-left: 2px;"></u-icon>
  34. </view>
  35. </view>
  36. <view class="list-wrapper margin">
  37. <view class="tabs flex justify-center">
  38. <view
  39. class="tab-item"
  40. :class="{ active: activeTab === item.value }"
  41. v-for="item in tabs"
  42. :key="item.value"
  43. @click="switchTab(item.value)"
  44. >
  45. {{ item.name }}
  46. <view class="border"></view>
  47. </view>
  48. </view>
  49. <scroll-view
  50. class="list-scroll"
  51. scroll-y
  52. refresher-enabled
  53. :refresher-threshold="80"
  54. refresher-default-style="black"
  55. :refresher-triggered="isRefreshing"
  56. @refresherrefresh="handleRefresherRefresh"
  57. @scrolltolower="handleScrollToLower"
  58. :lower-threshold="80"
  59. >
  60. <view class="list-scroll-inner">
  61. <view class="user-item flex justify-between" v-for="item in list" :key="item.user_id || item.id">
  62. <view class="flex align-center">
  63. <image :src="item.avatar || defaultAvatar" class="avatar"></image>
  64. <view class="user-info">
  65. <view class="user-name">{{ item.user_name }}</view>
  66. <view class="user-tel">{{ item.phone }}</view>
  67. </view>
  68. </view>
  69. <view class="time">{{ item.create_time }}</view>
  70. </view>
  71. <view v-if="!loadingMore && list.length === 0" class="list-empty">暂无数据</view>
  72. <view v-if="loadingMore" class="list-load-tip">加载中...</view>
  73. <view v-if="!hasMore && list.length > 0" class="list-nomore">— 没有更多了 —</view>
  74. </view>
  75. </scroll-view>
  76. </view>
  77. <view style="height: 20rpx;"></view>
  78. <u-popup v-model="showDatePickerPopup" mode="bottom" border-radius="20" :safeAreaInsetBottom="true">
  79. <view class="date-picker-panel">
  80. <view class="date-picker-header">
  81. <text class="date-picker-btn" @click="showDatePickerPopup = false">取消</text>
  82. <text class="date-picker-title">选择日期</text>
  83. <text class="date-picker-btn date-picker-btn--primary" @click="confirmDatePicker">确定</text>
  84. </view>
  85. <!-- indicator 高度须与每行 date-picker-item 高度一致(px),否则滚轮视觉与 value 索引错位(微信官方说明) -->
  86. <view class="date-picker-view-wrap">
  87. <picker-view
  88. v-if="showDatePickerPopup && yearList.length && dayList.length"
  89. :key="pickerMountKey"
  90. class="date-picker-view"
  91. :indicator-style="pickerIndicatorStyle"
  92. :value="datePickerValue"
  93. @change="onDatePickerViewChange"
  94. >
  95. <picker-view-column>
  96. <view v-for="(t, i) in modeColumn" :key="'mode-' + i" class="date-picker-item">
  97. <text class="date-picker-item-text">{{ t }}</text>
  98. </view>
  99. </picker-view-column>
  100. <picker-view-column>
  101. <view v-for="(y, i) in yearList" :key="'y-' + i" class="date-picker-item">
  102. <text class="date-picker-item-text">{{ y }}年</text>
  103. </view>
  104. </picker-view-column>
  105. <picker-view-column>
  106. <view v-for="(m, i) in monthList" :key="'mo-' + i" class="date-picker-item">
  107. <text class="date-picker-item-text">{{ m }}月</text>
  108. </view>
  109. </picker-view-column>
  110. <picker-view-column>
  111. <view v-for="(d, i) in dayList" :key="'d-' + i" class="date-picker-item">
  112. <text class="date-picker-item-text">{{ d }}日</text>
  113. </view>
  114. </picker-view-column>
  115. </picker-view>
  116. <!-- 全部:盖住后三列(H5 / APP-PLUS 用普通 view 即可) -->
  117. <view
  118. v-if="datePickerValue[0] === 0"
  119. class="date-picker-ymd-mask"
  120. @touchmove.stop.prevent="noop"
  121. ></view>
  122. </view>
  123. </view>
  124. </u-popup>
  125. <Poster
  126. :showTitle="false"
  127. :showTip="showTip"
  128. :value="showPoster"
  129. :posterBase64Url="posterBase64Url"
  130. :saveBtnText="saveBtnText"
  131. @close="showPoster = false"
  132. @saved="handleSaved"
  133. ></Poster>
  134. </view>
  135. </template>
  136. <script>
  137. import navBar from '@/components/nav-bar/index.vue';
  138. import Poster from '@/components/poster/poster.vue'
  139. export default {
  140. components: {
  141. navBar,
  142. Poster
  143. },
  144. data() {
  145. return {
  146. tabs: [
  147. {
  148. name: '已注册用户',
  149. value: 0
  150. },
  151. {
  152. name: '已达标用户',
  153. value: 1
  154. },
  155. ],
  156. list: [],
  157. defaultAvatar: '/static/logo.png',
  158. showPoster: false,
  159. activeTab: 0,
  160. page: 1,
  161. limit: 10,
  162. totalPages: 1,
  163. hasMore: true,
  164. isRefreshing: false,
  165. loadingMore: false,
  166. /** 内容不足一屏时自动补页,防止无法触底 */
  167. autoLoadMoreGuard: 0,
  168. /** 每次发起列表请求递增,用于丢弃过期响应(避免刷新后仍被旧请求覆盖 hasMore/list) */
  169. listReqId: 0,
  170. /** 筛选日期:null 表示「全部」;否则为 YYYY-MM-DD */
  171. filterDate: null,
  172. showDatePickerPopup: false,
  173. modeColumn: ['全部', '按日选择'],
  174. yearList: [],
  175. monthList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
  176. dayList: [],
  177. /** [模式索引, 年索引, 月索引, 日索引],与 picker-view 四列一一对应 */
  178. datePickerValue: [0, 0, 0, 0],
  179. /** 与下方 .date-picker-item 高度一致,单位 px */
  180. pickerIndicatorStyle: 'height: 50px;',
  181. /** 每次打开弹层递增,强制重建 picker-view,避免残留错位 */
  182. pickerMountKey: 0,
  183. userName: '',
  184. userPhone: '',
  185. inviteTotal: 0,
  186. posterBase64Url: '',
  187. loading: false,
  188. posterType: 0, // 0: 推荐官海报, 1: 企微二维码
  189. }
  190. },
  191. computed: {
  192. dateFilterText() {
  193. if (this.filterDate == null) return '全部'
  194. return this.filterDate.replace(/-/g, '.')
  195. },
  196. showTip() {
  197. return this.posterType === 0
  198. },
  199. saveBtnText() {
  200. return this.posterType === 0 ? '保存海报并成为推荐官' : '保存企微二维码'
  201. },
  202. progressBarWidth() {
  203. let percent = this.inviteTotal / 30
  204. if (percent > 1) {
  205. percent = 1
  206. }
  207. return percent * 100 + '%'
  208. }
  209. },
  210. onLoad() {
  211. const years = []
  212. for (let y = 2010; y <= 2040; y++) years.push(y)
  213. this.yearList = years
  214. },
  215. onShow() {
  216. // 判断有没登录,如果没有登录则跳转登录
  217. if (!uni.getStorageSync("userId")) {
  218. this.$queue.toLogin()
  219. return
  220. }
  221. this.getBasicInfo()
  222. this.page = 1
  223. this.getList()
  224. },
  225. methods: {
  226. switchTab(value) {
  227. if (this.activeTab === value) return
  228. this.activeTab = value
  229. this.page = 1
  230. this.hasMore = true
  231. this.getList()
  232. },
  233. /** scroll-view 下拉刷新(勿命名为 onPullDownRefresh,与 uni 页面生命周期重名会冲突) */
  234. handleRefresherRefresh() {
  235. this.loadingMore = false
  236. this.isRefreshing = true
  237. this.page = 1
  238. this.hasMore = true
  239. this.autoLoadMoreGuard = 0
  240. this.getList()
  241. this.getBasicInfo()
  242. },
  243. handleScrollToLower() {
  244. if (!this.hasMore || this.loadingMore || this.isRefreshing) return
  245. this.loadingMore = true
  246. this.page += 1
  247. this.getList()
  248. },
  249. /** 列表高度未超过 scroll-view 时不会触发 scrolltolower,自动再请求一页(最多 5 次) */
  250. tryFillScrollArea() {
  251. if (!this.hasMore || this.loadingMore || this.isRefreshing) return
  252. if (this.autoLoadMoreGuard >= 5) return
  253. this.$nextTick(() => {
  254. setTimeout(() => {
  255. const q = uni.createSelectorQuery().in(this)
  256. q.select('.list-scroll').boundingClientRect()
  257. q.select('.list-scroll-inner').boundingClientRect()
  258. q.exec((res) => {
  259. const scrollRect = res && res[0]
  260. const innerRect = res && res[1]
  261. if (!scrollRect || !innerRect) return
  262. // 内容区高度未量到时不补页,避免误连请求
  263. if (scrollRect.height > 20 && innerRect.height < 1) return
  264. if (innerRect.height <= scrollRect.height + 4) {
  265. this.autoLoadMoreGuard += 1
  266. this.handleScrollToLower()
  267. }
  268. })
  269. }, 100)
  270. })
  271. },
  272. /** month 为自然月 1-12 */
  273. getDaysInMonth(year, month) {
  274. return new Date(year, month, 0).getDate()
  275. },
  276. updateDayList() {
  277. let yi = Number(this.datePickerValue[1])
  278. let mi = Number(this.datePickerValue[2])
  279. yi = Math.min(Math.max(0, yi), this.yearList.length - 1)
  280. mi = Math.min(Math.max(0, mi), this.monthList.length - 1)
  281. const y = this.yearList[yi]
  282. const m = this.monthList[mi]
  283. const max = this.getDaysInMonth(y, m)
  284. const days = []
  285. for (let d = 1; d <= max; d++) days.push(d)
  286. this.dayList = days
  287. },
  288. noop() {},
  289. onDatePickerViewChange(e) {
  290. const raw = e.detail.value
  291. if (!raw || raw.length < 4) return
  292. const val = raw.map((n) => Number(n))
  293. const old = this.datePickerValue
  294. // 全部:禁止年月日列变化(与遮罩配合;兜底)
  295. if (old[0] === 0 && val[0] === 0 && (val[1] !== old[1] || val[2] !== old[2] || val[3] !== old[3])) {
  296. this.datePickerValue = [0, old[1], old[2], old[3]]
  297. return
  298. }
  299. this.datePickerValue = val
  300. if (val[0] === 0) return
  301. if (val[1] !== old[1] || val[2] !== old[2]) {
  302. this.updateDayList()
  303. this.$nextTick(() => {
  304. const maxD = this.dayList.length
  305. let di = val[3]
  306. if (di >= maxD) di = Math.max(0, maxD - 1)
  307. this.datePickerValue = [val[0], val[1], val[2], di]
  308. })
  309. }
  310. },
  311. openDatePicker() {
  312. const t = new Date()
  313. const ty = t.getFullYear()
  314. const tm = t.getMonth() + 1
  315. const td = t.getDate()
  316. const yi = Math.max(0, this.yearList.indexOf(ty))
  317. const moi = tm - 1
  318. if (this.filterDate) {
  319. const p = this.filterDate.split('-')
  320. if (p.length === 3) {
  321. const y = parseInt(p[0], 10)
  322. const m = parseInt(p[1], 10)
  323. const d = parseInt(p[2], 10)
  324. const yIdx = Math.max(0, this.yearList.indexOf(y))
  325. const mIdx = Math.min(Math.max(0, m - 1), 11)
  326. this.datePickerValue = [1, yIdx, mIdx, 0]
  327. this.updateDayList()
  328. let di = this.dayList.indexOf(d)
  329. if (di < 0) di = 0
  330. di = Math.min(di, this.dayList.length - 1)
  331. this.datePickerValue = [1, yIdx, mIdx, di]
  332. } else {
  333. this.datePickerValue = [0, yi, moi, 0]
  334. this.updateDayList()
  335. let di = this.dayList.indexOf(td)
  336. if (di < 0) di = 0
  337. di = Math.min(di, this.dayList.length - 1)
  338. this.datePickerValue = [0, yi, moi, di]
  339. }
  340. } else {
  341. this.datePickerValue = [0, yi, moi, 0]
  342. this.updateDayList()
  343. let di = this.dayList.indexOf(td)
  344. if (di < 0) di = 0
  345. di = Math.min(di, this.dayList.length - 1)
  346. this.datePickerValue = [0, yi, moi, di]
  347. }
  348. this.pickerMountKey += 1
  349. this.showDatePickerPopup = true
  350. },
  351. confirmDatePicker() {
  352. const modeIdx = Number(this.datePickerValue[0])
  353. let yi = Number(this.datePickerValue[1])
  354. let moi = Number(this.datePickerValue[2])
  355. let di = Number(this.datePickerValue[3])
  356. yi = Math.min(Math.max(0, yi), this.yearList.length - 1)
  357. moi = Math.min(Math.max(0, moi), 11)
  358. di = Math.min(Math.max(0, di), Math.max(0, this.dayList.length - 1))
  359. if (modeIdx === 0) {
  360. this.filterDate = null
  361. } else {
  362. const y = this.yearList[yi]
  363. const m = this.monthList[moi]
  364. const d = this.dayList[di]
  365. if (y != null && m != null && d != null) {
  366. this.filterDate = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
  367. }
  368. }
  369. this.showDatePickerPopup = false
  370. this.page = 1
  371. this.hasMore = true
  372. this.getList()
  373. },
  374. getList() {
  375. const expectPage = this.page
  376. const reqId = ++this.listReqId
  377. // 接口:inviteDate 为 yyyy.MM.dd,不传查全部
  378. const inviteDate =
  379. this.filterDate ? this.filterDate.replace(/-/g, '.') : ''
  380. const params = {
  381. page: expectPage,
  382. limit: this.limit,
  383. type: this.activeTab,
  384. inviteDate
  385. }
  386. return this.$Request.get('/app/invite/selectInviteList', params)
  387. .then((res) => {
  388. if (reqId !== this.listReqId) return
  389. if (expectPage !== this.page) return
  390. if (res.code !== 0) return
  391. const data = res.data || {}
  392. const newList = data.list || []
  393. const pageSize = Number(data.pageSize) || this.limit
  394. const currPage = Number(data.currPage) || expectPage
  395. let totalPage = Number(data.totalPage)
  396. const totalCount = data.totalCount != null ? Number(data.totalCount) : null
  397. if (Number.isNaN(totalPage) || totalPage < 1) {
  398. if (totalCount != null && !Number.isNaN(totalCount) && pageSize > 0) {
  399. totalPage = Math.max(1, Math.ceil(totalCount / pageSize))
  400. } else {
  401. totalPage = data.pages ?? data.pageTotal
  402. totalPage = totalPage != null && !Number.isNaN(Number(totalPage))
  403. ? Math.max(1, Number(totalPage))
  404. : 1
  405. }
  406. }
  407. if (expectPage === 1) {
  408. this.list = newList
  409. this.autoLoadMoreGuard = 0
  410. } else {
  411. this.list = this.list.concat(newList)
  412. }
  413. this.totalPages = totalPage
  414. // 优先用 totalCount 与已加载条数比较(与接口返回结构一致,最稳)
  415. if (totalCount != null && !Number.isNaN(totalCount)) {
  416. this.hasMore = this.list.length < totalCount
  417. } else {
  418. this.hasMore = currPage < totalPage
  419. }
  420. if (newList.length === 0 && expectPage > 1) this.hasMore = false
  421. })
  422. .catch(() => {
  423. if (reqId !== this.listReqId) return
  424. if (expectPage !== this.page) return
  425. if (this.page > 1) this.page -= 1
  426. })
  427. .finally(() => {
  428. if (reqId !== this.listReqId) return
  429. this.isRefreshing = false
  430. this.loadingMore = false
  431. // 必须在 finally 之后:若放在 then 的 $nextTick 里,可能早于本处执行,isRefreshing 仍为 true,tryFillScrollArea 会直接 return(下拉刷新后无法补页/触底)
  432. this.$nextTick(() => this.tryFillScrollArea())
  433. })
  434. },
  435. // 获取基本信息
  436. getBasicInfo() {
  437. this.$Request.getT('/app/invite/selectInviteTotal').then((res) => {
  438. if (res.code == 0) {
  439. this.basicInfo = res.data
  440. this.userName = res.data.userName
  441. this.userPhone = res.data.phone
  442. this.inviteTotal = res.data.totalCount
  443. }
  444. })
  445. },
  446. // 获取海报数据
  447. getPosterData() {
  448. if (this.loading) return
  449. this.posterType = 0
  450. this.loading = true
  451. this.$queue.showLoading('获取海报数据中...')
  452. this.$Request.getT('/app/invite/getMyPoster')
  453. .then(res => {
  454. if (res.code == 0) {
  455. this.showPoster = true
  456. this.posterBase64Url = res.data?.image ?? ''
  457. }
  458. }).finally(() => {
  459. this.$queue.hideLoading()
  460. this.loading = false
  461. })
  462. },
  463. handleSaved() {
  464. this.showPoster = false
  465. // #ifndef H5
  466. this.$queue.showToast('图片已保存到相册')
  467. // #endif
  468. // #ifdef H5
  469. this.$queue.showToast('保存成功')
  470. // #endif
  471. },
  472. // 获取企微二维码
  473. getWechatQrcode() {
  474. if (this.loading) return
  475. this.posterType = 1
  476. this.loading = true
  477. this.$queue.showLoading('获取企微二维码中...')
  478. this.$Request.getT('/app/common/type/830').then((res) => {
  479. if (res.code == 0) {
  480. this.posterBase64Url = res.data?.value ?? ''
  481. this.showPoster = true
  482. }
  483. }).finally(() => {
  484. this.$queue.hideLoading()
  485. this.loading = false
  486. })
  487. },
  488. }
  489. }
  490. </script>
  491. <style lang="scss" scoped>
  492. .page-container {
  493. display: flex;
  494. flex-direction: column;
  495. height: 100vh;
  496. /* 允许子项 flex:1 区域正确收缩,内部 scroll-view 才能滚 */
  497. min-height: 0;
  498. box-sizing: border-box;
  499. font-family: DM Sans;
  500. .top-section {
  501. flex-shrink: 0;
  502. margin: 24rpx 36rpx;
  503. padding: 32rpx 28rpx 40rpx;
  504. border-radius: 24rpx;
  505. background-image: url('@/static/images/my/bg3.png'),
  506. linear-gradient(90deg, #4a5cff 0%, #3d8aed 45%, #5ee7df 100%);
  507. background-size: 100% 100%;
  508. background-repeat: no-repeat;
  509. background-position: center;
  510. .badge-icon {
  511. width: 80rpx;
  512. height: 80rpx;
  513. margin-right: 4rpx;
  514. }
  515. .name {
  516. font-size: 36rpx;
  517. line-height: 1;
  518. color: #fff;
  519. }
  520. .sub-name {
  521. font-size: 24rpx;
  522. line-height: 36rpx;
  523. color: #fff;
  524. max-width: 240rpx;
  525. overflow: hidden;
  526. text-overflow: ellipsis;
  527. white-space: nowrap;
  528. }
  529. .poster-btn {
  530. color: rgba(255, 255, 255, 1);
  531. font-style: Regular;
  532. font-size: 14px;
  533. font-weight: 400;
  534. line-height: 24px;
  535. padding: 0 30rpx;
  536. border-radius: 30px;
  537. background: rgba(255, 255, 255, 0.35);
  538. }
  539. .progress-wrapper {
  540. position: relative;
  541. margin: 80rpx auto 32rpx;
  542. .progress-bar-wrapper {
  543. width: 600rpx;
  544. height: 20rpx;
  545. border-radius: 20rpx;
  546. background-color: rgba(255, 255, 255, 0.6);
  547. margin: 0 auto;
  548. .progress-bar {
  549. height: 100%;
  550. border-radius: 20rpx;
  551. background: linear-gradient(90.00deg, rgba(27, 234, 228, 1) 0%,rgba(1, 107, 251, 1) 100%);
  552. }
  553. }
  554. .badge {
  555. position: absolute;
  556. top: -22rpx;
  557. width: 60rpx;
  558. height: 60rpx;
  559. transform: translateX(-100%);
  560. }
  561. .badge1 {
  562. left: 34%;
  563. }
  564. .badge2 {
  565. left: 66%;
  566. }
  567. .badge3 {
  568. left: 100%;
  569. }
  570. }
  571. .names {
  572. .name {
  573. flex: 1;
  574. text-align: right;
  575. color: rgba(255, 255, 255, 1);
  576. font-size: 14px;
  577. font-weight: 400;
  578. line-height: 26px;
  579. }
  580. }
  581. }
  582. }
  583. .margin {
  584. margin: 0 36rpx;
  585. }
  586. .toolbar-row {
  587. flex-shrink: 0;
  588. }
  589. .add-btn {
  590. color: rgba(1, 107, 246, 1);
  591. font-size: 28rpx;
  592. font-weight: 400;
  593. line-height: 1;
  594. }
  595. .time-button {
  596. box-sizing: border-box;
  597. border: 0.5px solid rgba(1, 107, 246, 1);
  598. border-radius: 4px;
  599. background: rgba(153, 196, 250, 0.4);
  600. padding: 8rpx;
  601. color: rgba(1, 107, 246, 1);
  602. font-size: 28rpx;
  603. font-weight: 400;
  604. line-height: 1;
  605. }
  606. .list-wrapper {
  607. flex: 1;
  608. min-height: 0;
  609. /* 与内部 scroll-view 的 flex:1;height:0 配合,否则部分端无法滚动、触底不触发 */
  610. height: 0;
  611. display: flex;
  612. flex-direction: column;
  613. overflow: hidden;
  614. border-radius: 12rpx;
  615. box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.1);
  616. padding: 20rpx 0 40rpx;
  617. margin-top: 32rpx;
  618. box-sizing: border-box;
  619. .tabs {
  620. flex-shrink: 0;
  621. height: 72rpx;
  622. gap: 40rpx;
  623. .tab-item {
  624. position: relative;
  625. color: rgba(102, 102, 102, 1);
  626. font-size: 28rpx;
  627. font-weight: 500;
  628. line-height: 32rpx;
  629. .border {
  630. position: absolute;
  631. left: 50%;
  632. bottom: 0;
  633. width: 80rpx;
  634. height: 4rpx;
  635. background-color: #fff;
  636. transform: translateX(-50%);
  637. border-radius: 4rpx 4rpx 0 0;
  638. }
  639. }
  640. .active {
  641. color: #016BF6;
  642. .border {
  643. background: #016BF6;
  644. }
  645. }
  646. }
  647. .list-scroll {
  648. flex: 1;
  649. height: 0;
  650. width: 100%;
  651. }
  652. .user-item {
  653. margin: 28rpx 36rpx 0;
  654. .avatar {
  655. width: 50rpx;
  656. height: 50rpx;
  657. border-radius: 50%;
  658. margin-right: 24rpx;
  659. border: 1px solid #eee;
  660. }
  661. .user-info {
  662. color: rgba(51, 51, 51, 1);
  663. font-size: 28rpx;
  664. line-height: 32rpx;
  665. .user-name {
  666. max-width: 300rpx;
  667. overflow: hidden;
  668. text-overflow: ellipsis;
  669. white-space: nowrap;
  670. }
  671. }
  672. .time {
  673. height: 36rpx;
  674. color: rgba(153, 153, 153, 1);
  675. font-size: 24rpx;
  676. font-weight: 400;
  677. }
  678. }
  679. .list-empty,
  680. .list-load-tip,
  681. .list-nomore {
  682. text-align: center;
  683. padding: 32rpx;
  684. font-size: 26rpx;
  685. color: #999;
  686. }
  687. }
  688. .date-picker-panel {
  689. background: #fff;
  690. padding-bottom: constant(safe-area-inset-bottom);
  691. padding-bottom: env(safe-area-inset-bottom);
  692. }
  693. .date-picker-header {
  694. display: flex;
  695. align-items: center;
  696. justify-content: space-between;
  697. padding: 24rpx 32rpx;
  698. border-bottom: 1rpx solid #eee;
  699. }
  700. .date-picker-title {
  701. font-size: 32rpx;
  702. font-weight: 500;
  703. color: #303133;
  704. }
  705. .date-picker-btn {
  706. font-size: 30rpx;
  707. color: #606266;
  708. padding: 8rpx 12rpx;
  709. }
  710. .date-picker-btn--primary {
  711. color: #2979ff;
  712. }
  713. .date-picker-view-wrap {
  714. position: relative;
  715. width: 100%;
  716. min-height: 250px;
  717. }
  718. .date-picker-ymd-mask {
  719. position: absolute;
  720. left: 25%;
  721. top: 0;
  722. width: 75%;
  723. height: 100%;
  724. z-index: 2;
  725. background: rgba(0, 0, 0, 0.06);
  726. }
  727. .date-picker-view {
  728. width: 100%;
  729. /* 5 行 × 50px,与 uni-datetime-picker 一致,避免半行偏移 */
  730. height: 250px;
  731. }
  732. .date-picker-item {
  733. height: 50px;
  734. line-height: 50px;
  735. text-align: center;
  736. box-sizing: border-box;
  737. }
  738. .date-picker-item-text {
  739. font-size: 16px;
  740. color: #303133;
  741. line-height: 50px;
  742. height: 50px;
  743. }
  744. </style>