index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. <template>
  2. <view class="order-list-page">
  3. <nav-bar title="订单列表" :showBack="false"></nav-bar>
  4. <!-- 顶部状态栏 -->
  5. <view class="sticky-header">
  6. <scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
  7. <view class="tabs-row">
  8. <view v-for="tab in tabList" :key="tab.value"
  9. :class="['tab-item', { active: activeStatus === tab.value }]" @click="onTabClick(tab.value)">
  10. <text>{{ tab.title }}</text>
  11. </view>
  12. </view>
  13. </scroll-view>
  14. <!-- 搜索和过滤 -->
  15. <view class="filter-row">
  16. <picker :range="typeOptions" range-key="text" @change="onTypeChange">
  17. <view class="dropdown-btn">
  18. <text>{{ currentTypeName }}</text>
  19. <uni-icons type="bottom" size="12" color="#333"></uni-icons>
  20. </view>
  21. </picker>
  22. <view class="search-wrap">
  23. <uni-icons type="search" size="14" color="#999"></uni-icons>
  24. <input class="search-input" v-model="searchValue" placeholder="订单号/商户/宠主/手机号"
  25. placeholder-class="placeholder-style" @confirm="onSearch" />
  26. </view>
  27. </view>
  28. </view>
  29. <!-- 订单列表内容 -->
  30. <view class="list-container">
  31. <view class="order-card" v-for="order in orders" :key="order.id" @click="goToDetail(order)">
  32. <!-- 头部:订单号与状态 -->
  33. <view class="order-head">
  34. <text class="order-no">{{ order.id }}</text>
  35. <text :class="['status-text', order.statusClass]">{{ order.statusText }}</text>
  36. </view>
  37. <!-- 主体信息 -->
  38. <view class="order-body">
  39. <view class="service-row">
  40. <text class="service-name">{{ order.serviceType }}</text>
  41. <text class="service-tag tag-orange" v-if="order.serviceTags[0]">{{ order.serviceTags[0]
  42. }}</text>
  43. <text class="service-tag tag-blue" v-if="order.serviceTags[1] === '接'">接</text>
  44. <text class="service-tag tag-green" v-if="order.serviceTags[1] === '送'">送</text>
  45. </view>
  46. <view class="pet-row">
  47. <view class="pet-avatar-text">
  48. <text>{{ order.petName.substring(0, 1).toUpperCase() }}</text>
  49. </view>
  50. <view class="pet-desc">
  51. <text class="bold">{{ order.petName }}</text>
  52. <text class="sub">{{ order.petBreed }}</text>
  53. </view>
  54. <text class="user-desc">{{ order.userName }}</text>
  55. </view>
  56. <view class="info-list">
  57. <view class="info-item">
  58. <uni-icons type="location" size="14" color="#999"></uni-icons>
  59. <text>{{ order.address }}</text>
  60. </view>
  61. <view class="info-item">
  62. <uni-icons type="shop" size="14" color="#999"></uni-icons>
  63. <text>{{ order.shopName }} {{ order.userPhone }}</text>
  64. </view>
  65. <view class="info-item">
  66. <uni-icons type="calendar" size="14" color="#999"></uni-icons>
  67. <text>预约: {{ order.bookTime }}</text>
  68. </view>
  69. </view>
  70. <!-- 时间与履约信息 -->
  71. <view class="time-info-block">
  72. <text class="create-time">下单: {{ order.createTime }}</text>
  73. <view class="assign-info">
  74. <text class="assign-label">履约信息:</text>
  75. <text class="assign-name" v-if="order.assigneeName">{{ order.assigneeName }}</text>
  76. <text class="assign-none" v-else>暂未指派</text>
  77. </view>
  78. <text class="cancel-time" v-if="order.statusText === '已取消' && order.cancelTime">取消: {{
  79. order.cancelTime }}</text>
  80. </view>
  81. </view>
  82. <!-- 操作栏 (带分割线) -->
  83. <view class="order-foot">
  84. <view class="actions">
  85. <template v-if="order.statusText === '待派单' || order.statusText === '待接单'">
  86. <button size="mini" class="action-btn btn-cancel"
  87. @click.stop="onCancelOrder(order)">取消订单</button>
  88. <button size="mini" class="action-btn btn-primary"
  89. @click.stop="goToDetail(order)">查看详情</button>
  90. </template>
  91. <template v-else-if="['服务中', '待商家确认', '已完成'].includes(order.statusText)">
  92. <button v-if="['服务中', '已完成'].includes(order.statusText)" size="mini"
  93. class="action-btn btn-cancel" @click.stop="onComplaint(order)">投诉订单</button>
  94. <button size="mini" class="action-btn btn-primary"
  95. @click.stop="goToDetail(order)">查看详情</button>
  96. </template>
  97. <template v-else>
  98. <button size="mini" class="action-btn btn-primary"
  99. @click.stop="goToDetail(order)">查看详情</button>
  100. </template>
  101. </view>
  102. </view>
  103. </view>
  104. <!-- 空状态 -->
  105. <view class="empty-state" v-if="!loading && orders.length === 0">
  106. <text class="empty-text">暂无相关订单</text>
  107. </view>
  108. <!-- 加载状态 -->
  109. <view class="loading-state" v-if="loading">
  110. <text class="loading-text">加载中...</text>
  111. </view>
  112. </view>
  113. <!-- 自定义取消订单弹窗 -->
  114. <view class="custom-modal" v-if="showCancelModal">
  115. <view class="modal-mask" @click="closeCancelModal"></view>
  116. <view class="modal-content">
  117. <view class="modal-title">提示</view>
  118. <view class="modal-body">
  119. <view style="margin-bottom: 20rpx; font-size: 28rpx; color: #666;">确定要取消订单 [{{ currentCancelOrder?.id }}] 吗?</view>
  120. <textarea class="cancel-input" v-model="cancelReason" placeholder="必填,请输入取消原因" placeholder-class="ph-color" :show-confirm-bar="false"></textarea>
  121. </view>
  122. <view class="modal-footer">
  123. <view class="modal-btn btn-cancel" @click="closeCancelModal">取消</view>
  124. <view class="modal-btn btn-confirm" @click="confirmCancelOrder">确定</view>
  125. </view>
  126. </view>
  127. </view>
  128. <custom-tabbar></custom-tabbar>
  129. </view>
  130. </template>
  131. <script setup>
  132. import { ref, computed, onMounted } from 'vue'
  133. import { onLoad } from '@dcloudio/uni-app'
  134. import navBar from '@/components/nav-bar/index.vue'
  135. import customTabbar from '@/components/custom-tabbar/index.vue'
  136. import orderStatusData from '@/json/orderStatus.json'
  137. import { listAll } from '@/api/service/list'
  138. import { listSubOrder, cancelSubOrder } from '@/api/order/subOrder'
  139. import { listAreaStation } from '@/api/system/areaStation'
  140. // 加载状态
  141. const loading = ref(false)
  142. // 筛选与搜索
  143. const activeStatus = ref(-1) // -1 表示全部,其他值为枚举值
  144. const filterType = ref(0)
  145. const searchValue = ref('')
  146. // 分页参数
  147. const pagination = ref({
  148. current: 1,
  149. size: 10,
  150. total: 0
  151. })
  152. // 从 orderStatus.json 生成 tabList
  153. const tabList = ref([
  154. { title: '全部订单', value: -1 },
  155. ...orderStatusData.map(item => ({
  156. title: item.label,
  157. value: item.value,
  158. color: item.color
  159. }))
  160. ])
  161. const typeOptions = ref([{ text: '全部类型', value: 0 }])
  162. const serviceList = ref([])
  163. const areaStationList = ref([])
  164. const areaStationMap = ref({})
  165. // 加载服务类型列表
  166. const loadServiceTypes = async () => {
  167. try {
  168. const services = await listAll()
  169. if (services && services.length > 0) {
  170. serviceList.value = services
  171. const serviceTypes = services.map((service, index) => ({
  172. text: service.name,
  173. value: index + 1,
  174. id: service.id
  175. }))
  176. typeOptions.value = [
  177. { text: '全部类型', value: 0 },
  178. ...serviceTypes
  179. ]
  180. }
  181. } catch (error) {
  182. console.error('加载服务类型失败:', error)
  183. }
  184. }
  185. // 加载区域站点列表
  186. const loadAreaStations = async () => {
  187. try {
  188. const res = await listAreaStation()
  189. if (res && res.data) {
  190. areaStationList.value = res.data
  191. const map = {}
  192. for (const item of res.data) {
  193. if (item && item.id !== undefined && item.id !== null) {
  194. map[item.id] = item
  195. }
  196. }
  197. areaStationMap.value = map
  198. }
  199. } catch (error) {
  200. console.error('加载区域站点失败:', error)
  201. }
  202. }
  203. // 读取URL参数,设置初始tab状态
  204. onLoad((options) => {
  205. if (options && options.status !== undefined) {
  206. const statusValue = Number(options.status)
  207. if (!isNaN(statusValue)) {
  208. activeStatus.value = statusValue
  209. }
  210. }
  211. })
  212. onMounted(() => {
  213. loadServiceTypes()
  214. loadAreaStations()
  215. loadOrders()
  216. })
  217. const currentTypeName = computed(() => {
  218. const option = typeOptions.value.find(opt => opt.value === filterType.value)
  219. return option ? option.text : '全部类型'
  220. })
  221. // 根据枚举值获取状态信息
  222. const getStatusInfo = (value) => {
  223. return orderStatusData.find(item => item.value === value)
  224. }
  225. // 根据服务ID获取服务名称
  226. const getServiceName = (serviceId) => {
  227. const service = serviceList.value.find(s => s.id === serviceId)
  228. return service ? service.name : '未知服务'
  229. }
  230. // 获取城市/区域文本
  231. const getCityDistrictText = (row) => {
  232. if (!row || !row.site) return ''
  233. const station = areaStationMap.value[row.site]
  234. if (!station) return ''
  235. const parent = station.parentId ? areaStationMap.value[station.parentId] : undefined
  236. if (!parent) return station.name || ''
  237. if (parent.type === 0) return parent.name || ''
  238. if (parent.type === 1) {
  239. const city = parent.parentId ? areaStationMap.value[parent.parentId] : undefined
  240. return `${city?.name || ''}/${parent.name || ''}`
  241. }
  242. return parent.name || ''
  243. }
  244. // 获取服务模式标签
  245. const getServiceModeTag = (row) => {
  246. const t = row?.type
  247. if (t === 0 || t === '0' || t === 1 || t === '1') return '往返'
  248. return ''
  249. }
  250. // 获取服务订单类型标签
  251. const getServiceOrderTypeTag = (row) => {
  252. const t = row?.type
  253. if (t === 0 || t === '0') return { label: '接', type: 'blue' }
  254. if (t === 1 || t === '1') return { label: '送', type: 'green' }
  255. if (t === 2 || t === '2') return { label: '单程接', type: 'blue' }
  256. if (t === 3 || t === '3') return { label: '单程送', type: 'green' }
  257. return null
  258. }
  259. const onTabClick = (value) => {
  260. activeStatus.value = value
  261. pagination.value.current = 1
  262. loadOrders()
  263. }
  264. const onTypeChange = (e) => {
  265. const index = Number(e.detail.value)
  266. filterType.value = index
  267. pagination.value.current = 1
  268. loadOrders()
  269. }
  270. // 订单列表数据
  271. const orders = ref([])
  272. // 加载订单列表
  273. const loadOrders = async () => {
  274. loading.value = true
  275. try {
  276. const selectedType = typeOptions.value.find(opt => opt.value === filterType.value)
  277. const params = {
  278. pageNum: pagination.value.current,
  279. pageSize: pagination.value.size,
  280. status: activeStatus.value !== -1 ? activeStatus.value : undefined,
  281. service: selectedType && selectedType.id ? selectedType.id : undefined,
  282. content: searchValue.value || undefined
  283. }
  284. const res = await listSubOrder(params)
  285. console.log('后端返回数据:', res)
  286. if (res) {
  287. const rows = res.rows || []
  288. console.log('rows:', rows)
  289. orders.value = rows.map(row => transformOrder(row))
  290. console.log('转换后的orders:', orders.value)
  291. pagination.value.total = res.total || 0
  292. }
  293. } catch (error) {
  294. console.error('加载订单列表失败:', error)
  295. } finally {
  296. loading.value = false
  297. }
  298. }
  299. // 转换订单数据格式
  300. const transformOrder = (row) => {
  301. const statusInfo = getStatusInfo(row.status)
  302. const serviceName = getServiceName(row.service)
  303. const modeTag = getServiceModeTag(row)
  304. const typeTag = getServiceOrderTypeTag(row)
  305. const serviceTags = []
  306. if (modeTag) serviceTags.push(modeTag)
  307. if (typeTag) serviceTags.push(typeTag.label)
  308. return {
  309. // 先展开原始字段,后面的手动赋值具有更高优先级
  310. ...row,
  311. // 显示用的单号(优先用业务编号 code,否则用数据库 ID)
  312. id: row.code || row.id,
  313. rawId: row.id,
  314. serviceType: serviceName,
  315. serviceTags: serviceTags,
  316. petName: row.petName || '未知',
  317. petBreed: row.petBreed || '未知',
  318. userName: row.customerName || '未知',
  319. address: row.toAddress || row.fromAddress || getCityDistrictText(row),
  320. shopName: row.storeName || '未知',
  321. userPhone: row.contactPhoneNumber || '',
  322. bookTime: row.serviceTime || '',
  323. createTime: row.createTime || '',
  324. statusText: statusInfo ? statusInfo.label : '未知',
  325. statusClass: statusInfo ? `text-${statusInfo.color.replace('#', '')}` : 'text-gray',
  326. assigneeName: row.fulfillerName || '',
  327. cancelTime: row.cancelTime || ''
  328. }
  329. }
  330. // 搜索订单
  331. const onSearch = () => {
  332. pagination.value.current = 1
  333. loadOrders()
  334. }
  335. const showCancelModal = ref(false)
  336. const cancelReason = ref('')
  337. const currentCancelOrder = ref(null)
  338. // 取消订单
  339. const onCancelOrder = (order) => {
  340. currentCancelOrder.value = order
  341. cancelReason.value = ''
  342. showCancelModal.value = true
  343. }
  344. const closeCancelModal = () => {
  345. showCancelModal.value = false
  346. }
  347. const confirmCancelOrder = async () => {
  348. const reason = cancelReason.value.trim()
  349. if (!reason) {
  350. uni.showToast({ title: '取消原因不能为空', icon: 'none' })
  351. return
  352. }
  353. try {
  354. uni.showLoading({ title: '处理中' })
  355. await cancelSubOrder({ orderId: currentCancelOrder.value.rawId, reason })
  356. uni.hideLoading()
  357. uni.showToast({ title: '订单已取消', icon: 'success' })
  358. showCancelModal.value = false
  359. loadOrders()
  360. } catch (error) {
  361. uni.hideLoading()
  362. console.error('取消订单失败:', error)
  363. uni.showToast({ title: '取消失败', icon: 'none' })
  364. }
  365. }
  366. // 跳转到订单详情
  367. const goToDetail = (order) => {
  368. uni.navigateTo({
  369. url: `/pages/order/detail/index?id=${order.rawId}`
  370. })
  371. }
  372. // 投诉
  373. const onComplaint = (order) => {
  374. uni.navigateTo({
  375. url: `/pages/my/complaint/submit/index?orderId=${order.rawId}&fulfillerId=${order.fulfiller}&orderCode=${order.id}`
  376. })
  377. }
  378. </script>
  379. <style lang="scss" scoped>
  380. .order-list-page {
  381. background-color: #f2f2f2;
  382. min-height: 100vh;
  383. padding-bottom: 120rpx;
  384. }
  385. .sticky-header {
  386. position: sticky;
  387. top: calc(44px + var(--status-bar-height, 44px));
  388. z-index: 99;
  389. background-color: #fff;
  390. }
  391. .tabs-scroll {
  392. white-space: nowrap;
  393. border-bottom: 2rpx solid #EEEEEE;
  394. }
  395. .tabs-row {
  396. display: flex;
  397. padding: 0 16rpx;
  398. }
  399. .tab-item {
  400. padding: 32rpx 24rpx;
  401. font-size: 30rpx;
  402. color: #666;
  403. position: relative;
  404. flex-shrink: 0;
  405. }
  406. .tab-item.active {
  407. color: #333;
  408. font-weight: bold;
  409. }
  410. .tab-item.active::after {
  411. content: '';
  412. position: absolute;
  413. bottom: 0;
  414. left: 50%;
  415. transform: translateX(-50%);
  416. width: 48rpx;
  417. height: 6rpx;
  418. background-color: #f7ca3e;
  419. border-radius: 6rpx;
  420. }
  421. .filter-row {
  422. display: flex;
  423. align-items: center;
  424. padding: 12rpx 16rpx;
  425. background-color: #fff;
  426. border-top: 2rpx solid #EEEEEE;
  427. gap: 16rpx;
  428. }
  429. .dropdown-btn {
  430. display: flex;
  431. align-items: center;
  432. gap: 8rpx;
  433. background: #f5f5f5;
  434. border-radius: 40rpx;
  435. padding: 24rpx 28rpx;
  436. font-size: 28rpx;
  437. color: #333;
  438. }
  439. .search-wrap {
  440. flex: 1;
  441. display: flex;
  442. align-items: center;
  443. background: #f5f5f5;
  444. border-radius: 40rpx;
  445. padding: 24rpx 24rpx;
  446. gap: 12rpx;
  447. }
  448. .search-input {
  449. flex: 1;
  450. font-size: 26rpx;
  451. }
  452. .placeholder-style {
  453. color: #999;
  454. font-size: 26rpx;
  455. }
  456. .list-container {
  457. padding: 24rpx;
  458. }
  459. .order-card {
  460. padding: 28rpx;
  461. margin-bottom: 24rpx;
  462. background: #fff;
  463. border-radius: 24rpx;
  464. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
  465. }
  466. .order-head {
  467. display: flex;
  468. justify-content: space-between;
  469. align-items: center;
  470. border-bottom: 2rpx solid #EEEEEE;
  471. padding-bottom: 20rpx;
  472. margin-bottom: 20rpx;
  473. }
  474. .order-no {
  475. font-size: 28rpx;
  476. color: #333333;
  477. }
  478. .status-text {
  479. font-size: 28rpx;
  480. font-weight: bold;
  481. }
  482. .text-red {
  483. color: #f44336;
  484. }
  485. .text-orange {
  486. color: #ff9800;
  487. }
  488. .text-blue {
  489. color: #2196f3;
  490. }
  491. .text-green {
  492. color: #4caf50;
  493. }
  494. .text-gray {
  495. color: #999;
  496. }
  497. .service-row {
  498. display: flex;
  499. align-items: center;
  500. margin-bottom: 20rpx;
  501. gap: 12rpx;
  502. }
  503. .service-name {
  504. font-size: 30rpx;
  505. font-weight: bold;
  506. color: #333;
  507. }
  508. .service-tag {
  509. font-size: 20rpx;
  510. padding: 2rpx 8rpx;
  511. border-radius: 8rpx;
  512. border: 1rpx solid;
  513. }
  514. .tag-orange {
  515. color: #ff9800;
  516. border-color: #ff9800;
  517. background: #fff3e0;
  518. }
  519. .tag-blue {
  520. color: #2196f3;
  521. border-color: #2196f3;
  522. background: #e3f2fd;
  523. }
  524. .tag-green {
  525. color: #4caf50;
  526. border-color: #4caf50;
  527. background: #e8f5e9;
  528. }
  529. .pet-row {
  530. display: flex;
  531. align-items: center;
  532. margin-bottom: 20rpx;
  533. background: #f7f8fa;
  534. padding: 16rpx 20rpx;
  535. border-radius: 16rpx;
  536. }
  537. .pet-avatar-text {
  538. width: 64rpx;
  539. height: 64rpx;
  540. border-radius: 50%;
  541. background-color: #e3f2fd;
  542. color: #2196f3;
  543. display: flex;
  544. align-items: center;
  545. justify-content: center;
  546. font-weight: bold;
  547. font-size: 32rpx;
  548. margin-right: 20rpx;
  549. }
  550. .pet-desc {
  551. display: flex;
  552. align-items: baseline;
  553. gap: 12rpx;
  554. flex: 1;
  555. }
  556. .pet-desc .bold {
  557. font-size: 30rpx;
  558. font-weight: bold;
  559. color: #333;
  560. }
  561. .pet-desc .sub {
  562. font-size: 26rpx;
  563. color: #666;
  564. }
  565. .user-desc {
  566. font-size: 28rpx;
  567. color: #333;
  568. }
  569. .info-list {
  570. display: flex;
  571. flex-direction: column;
  572. gap: 12rpx;
  573. }
  574. .info-item {
  575. display: flex;
  576. align-items: center;
  577. font-size: 26rpx;
  578. color: #666;
  579. gap: 12rpx;
  580. }
  581. .time-info-block {
  582. margin-top: 24rpx;
  583. display: flex;
  584. flex-direction: column;
  585. gap: 12rpx;
  586. }
  587. .order-foot {
  588. display: flex;
  589. justify-content: flex-end;
  590. align-items: center;
  591. margin-top: 24rpx;
  592. padding-top: 24rpx;
  593. border-top: 2rpx solid #EEEEEE;
  594. }
  595. .foot-left {
  596. display: flex;
  597. flex-direction: column;
  598. gap: 12rpx;
  599. }
  600. .create-time,
  601. .cancel-time {
  602. font-size: 24rpx;
  603. color: #999;
  604. }
  605. .assign-info {
  606. display: flex;
  607. align-items: center;
  608. font-size: 24rpx;
  609. gap: 12rpx;
  610. }
  611. .assign-label {
  612. color: #999;
  613. }
  614. .assign-none {
  615. color: #ccc;
  616. }
  617. .assign-name {
  618. color: #333;
  619. font-weight: bold;
  620. }
  621. .actions {
  622. display: flex;
  623. gap: 16rpx;
  624. }
  625. .action-btn {
  626. height: 60rpx;
  627. line-height: 60rpx;
  628. min-width: 140rpx;
  629. font-size: 26rpx;
  630. font-weight: 600;
  631. padding: 0 32rpx;
  632. border-radius: 30rpx;
  633. display: inline-flex;
  634. align-items: center;
  635. justify-content: center;
  636. &::after { border: none; }
  637. }
  638. .btn-cancel {
  639. border: 2rpx solid #EEEEEE;
  640. color: #666;
  641. background: transparent;
  642. }
  643. .btn-primary {
  644. background: linear-gradient(90deg, #ffd53f, #ff9500);
  645. border: none;
  646. color: #fff;
  647. box-shadow: 0 6rpx 16rpx rgba(255, 149, 0, 0.3);
  648. }
  649. .empty-state {
  650. text-align: center;
  651. padding: 100rpx 0;
  652. }
  653. .empty-text {
  654. font-size: 28rpx;
  655. color: #999;
  656. }
  657. .loading-state {
  658. text-align: center;
  659. padding: 100rpx 0;
  660. }
  661. .loading-text {
  662. font-size: 28rpx;
  663. color: #999;
  664. }
  665. /* 自定义弹窗样式 */
  666. .custom-modal {
  667. position: fixed;
  668. top: 0;
  669. left: 0;
  670. width: 100%;
  671. height: 100%;
  672. z-index: 999;
  673. display: flex;
  674. align-items: center;
  675. justify-content: center;
  676. }
  677. .modal-mask {
  678. position: absolute;
  679. top: 0;
  680. left: 0;
  681. width: 100%;
  682. height: 100%;
  683. background-color: rgba(0, 0, 0, 0.5);
  684. }
  685. .modal-content {
  686. position: relative;
  687. width: 80%;
  688. background-color: #fff;
  689. border-radius: 16rpx;
  690. overflow: hidden;
  691. z-index: 1000;
  692. }
  693. .modal-title {
  694. padding: 30rpx 0 20rpx;
  695. text-align: center;
  696. font-size: 32rpx;
  697. font-weight: bold;
  698. color: #333;
  699. }
  700. .modal-body {
  701. padding: 10rpx 40rpx 30rpx;
  702. }
  703. .cancel-input {
  704. width: 100%;
  705. height: 160rpx;
  706. background-color: #f8f8f8;
  707. border-radius: 8rpx;
  708. padding: 20rpx;
  709. font-size: 28rpx;
  710. box-sizing: border-box;
  711. color: #333;
  712. }
  713. .ph-color {
  714. color: #999;
  715. }
  716. .modal-footer {
  717. display: flex;
  718. border-top: 1rpx solid #CCCCCC;
  719. }
  720. .modal-btn {
  721. flex: 1;
  722. height: 90rpx;
  723. line-height: 90rpx;
  724. text-align: center;
  725. font-size: 30rpx;
  726. font-weight: 500;
  727. }
  728. .btn-cancel {
  729. color: #666;
  730. border-right: 1rpx solid #eee;
  731. }
  732. .btn-confirm {
  733. color: #2196f3;
  734. }
  735. </style>