tasks.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. <template>
  2. <view class="container">
  3. <!-- 顶部 Tab 切换 -->
  4. <view class="tab-bar">
  5. <view class="tab-item" :class="{ active: activeTab === 'pending' }" @click="switchTab('pending')">
  6. <text class="tab-text">待接受</text>
  7. <text class="tab-badge" v-if="pendingTasks.length > 0">{{ pendingTasks.length }}</text>
  8. </view>
  9. <view class="tab-item" :class="{ active: activeTab === 'today' }" @click="switchTab('today')">
  10. <text class="tab-text">今日任务</text>
  11. </view>
  12. <view class="tab-item" :class="{ active: activeTab === 'created' }" @click="switchTab('created')">
  13. <text class="tab-text">我创建的</text>
  14. </view>
  15. </view>
  16. <!-- 待接受任务 -->
  17. <view class="task-list" v-if="activeTab === 'pending'">
  18. <view class="task-item assigned" v-for="task in pendingTasks" :key="task.id">
  19. <view class="task-left">
  20. <view class="task-title">
  21. {{ task.title }}
  22. <text v-if="task.memberOnly === 1" class="member-badge">会员任务</text>
  23. </view>
  24. <view class="task-meta">
  25. <text class="points">+{{ task.points }}分</text>
  26. <text class="creator">创建人:{{ task.creatorName || '家人' }}</text>
  27. <text class="deadline">截止 {{ formatTime(task.deadline) }}</text>
  28. </view>
  29. </view>
  30. <view class="task-actions">
  31. <button class="btn-accept" @click="acceptTask(task)">接受</button>
  32. <button class="btn-reject" @click="showRejectModal(task)">拒绝</button>
  33. </view>
  34. </view>
  35. <view class="empty" v-if="!loading && pendingTasks.length === 0">
  36. <text>暂无待接受任务</text>
  37. </view>
  38. </view>
  39. <!-- 今日任务 -->
  40. <view class="task-list" v-if="activeTab === 'today'">
  41. <view class="task-item" v-for="task in todayTasks" :key="task.id" :class="{ completed: task.status === 'completed' }">
  42. <view class="task-left">
  43. <view class="task-title">
  44. {{ task.title }}
  45. <text v-if="task.memberOnly === 1" class="member-badge">会员任务</text>
  46. <text v-if="task.category === '小游戏类'" class="minigame-badge">🎮</text>
  47. </view>
  48. <view class="task-meta">
  49. <text class="points">+{{ task.points }}分</text>
  50. <text class="deadline">截止 {{ formatTime(task.deadline) }}</text>
  51. </view>
  52. </view>
  53. <view class="task-right" v-if="task.status === 'pending'">
  54. <button class="btn-complete" @click="completeTask(task)">完成</button>
  55. </view>
  56. <view class="completed-badge" v-else>已完成</view>
  57. </view>
  58. <view class="empty" v-if="!loading && todayTasks.length === 0">
  59. <text>暂无今日任务</text>
  60. </view>
  61. </view>
  62. <!-- 我创建的任务 -->
  63. <view class="task-list" v-if="activeTab === 'created'">
  64. <view class="task-item" v-for="task in createdTasks" :key="task.id">
  65. <view class="task-left">
  66. <view class="task-title">
  67. {{ task.title }}
  68. <text v-if="task.memberOnly === 1" class="member-badge">会员任务</text>
  69. </view>
  70. <view class="task-meta">
  71. <text class="points">+{{ task.points }}分</text>
  72. <text class="assignee">{{ task.executorName || '未分配' }}</text>
  73. <text class="status-tag" :class="'status-' + task.status">{{ statusLabel(task.status) }}</text>
  74. </view>
  75. </view>
  76. </view>
  77. <view class="empty" v-if="!loading && createdTasks.length === 0">
  78. <text>暂无创建的任务</text>
  79. </view>
  80. </view>
  81. <!-- 添加任务按钮 -->
  82. <button v-if="activeTab === 'today' && isParent" class="add-btn" @click="showAddModal = true">
  83. + 添加任务
  84. </button>
  85. <!-- 拒绝确认弹窗 -->
  86. <view class="modal-mask" v-if="showRejectModalFlag" @click="showRejectModalFlag = false">
  87. <view class="modal" @click.stop>
  88. <view class="modal-title">确认拒绝</view>
  89. <view class="modal-content">
  90. <text>拒绝后,创建人对你的信任度和亲密度将各减1分,沟通质量各加1分。</text>
  91. </view>
  92. <view class="modal-btns">
  93. <button @click="showRejectModalFlag = false">取消</button>
  94. <button class="btn-primary" @click="confirmReject">确认拒绝</button>
  95. </view>
  96. </view>
  97. </view>
  98. <!-- 创建任务弹窗 -->
  99. <view class="modal-mask" v-if="showAddModal" @click="showAddModal = false">
  100. <view class="modal modal-large" @click.stop>
  101. <view class="modal-title">创建任务</view>
  102. <view class="form-item">
  103. <view class="form-label">任务标题 <text class="required">*</text></view>
  104. <input v-model="newTask.title" placeholder="请输入任务标题" />
  105. </view>
  106. <view class="form-item">
  107. <view class="form-label">任务内容</view>
  108. <textarea v-model="newTask.description" placeholder="请输入任务描述" class="textarea" />
  109. </view>
  110. <view class="form-item">
  111. <view class="form-label">类型 <text class="required">*</text></view>
  112. <picker mode="selector" :range="categories" @change="onCategoryChange">
  113. <view class="picker">{{ newTask.category || '请选择类型' }}</view>
  114. </picker>
  115. </view>
  116. <view class="form-item">
  117. <view class="form-label">积分 <text class="required">*</text></view>
  118. <input v-model="newTask.points" type="number" placeholder="1-10" />
  119. </view>
  120. <view class="form-item">
  121. <view class="form-label">截止时间 <text class="required">*</text></view>
  122. <picker mode="datetime" :value="newTask.deadline" @change="onDeadlineChange">
  123. <view class="picker">{{ newTask.deadline || '选择截止时间' }}</view>
  124. </picker>
  125. </view>
  126. <view class="form-item">
  127. <view class="form-label">分配给 <text class="required">*</text></view>
  128. <checkbox-group @change="onFamilyMemberSelectChange">
  129. <label v-for="member in familyMembers" :key="member.id" :class="{ 'label-checked': newTask.memberIds.includes(String(member.id)) }">
  130. <checkbox :value="String(member.id)" :checked="newTask.memberIds.includes(String(member.id))" />
  131. <text>{{ member.nickname }}</text>
  132. </label>
  133. </checkbox-group>
  134. </view>
  135. <view class="modal-btns">
  136. <button @click="showAddModal = false">取消</button>
  137. <button class="btn-primary" @click="createTask">创建</button>
  138. </view>
  139. </view>
  140. </view>
  141. </view>
  142. </template>
  143. <script>
  144. import { getMyPendingTasks, acceptTask as acceptTaskApi, rejectTask as rejectTaskApi, getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getFamilyMemberList, getMyMembership } from '../../utils/api.js'
  145. import { parseDate } from '../../utils/format.js'
  146. export default {
  147. data() {
  148. return {
  149. activeTab: 'today',
  150. loading: false,
  151. pendingTasks: [],
  152. todayTasks: [],
  153. createdTasks: [],
  154. isParent: false,
  155. isMember: false,
  156. currentMemberId: null,
  157. currentUserId: null,
  158. familyMembers: [],
  159. showAddModal: false,
  160. showRejectModalFlag: false,
  161. rejectTaskId: null,
  162. newTask: {
  163. title: '',
  164. description: '',
  165. category: '',
  166. points: 2,
  167. deadline: '',
  168. memberIds: []
  169. },
  170. categories: ['学习类', '生活类', '运动类', '其他']
  171. }
  172. },
  173. onShow() {
  174. const role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
  175. this.isParent = role === 'parent'
  176. this.currentUserId = uni.getStorageSync('userId')
  177. this.currentMemberId = uni.getStorageSync('currentChildId') || this.currentUserId
  178. this.checkMembership()
  179. this.loadInitialData()
  180. },
  181. methods: {
  182. checkMembership() {
  183. getMyMembership().then(res => {
  184. const m = res.data || {}
  185. this.isMember = !!(m.levelCode && m.levelCode !== 'FREE')
  186. }).catch(() => {})
  187. },
  188. switchTab(tab) {
  189. this.activeTab = tab
  190. if (tab === 'pending') this.loadPendingTasks()
  191. else if (tab === 'today') this.loadTodayTasks()
  192. else if (tab === 'created') this.loadCreatedTasks()
  193. },
  194. async loadInitialData() {
  195. this.loading = true
  196. try {
  197. const membersRes = await getFamilyMemberList({ visibleOnly: true })
  198. this.familyMembers = membersRes.data || []
  199. this.loadTodayTasks()
  200. this.loadPendingTasks()
  201. this.loadCreatedTasks()
  202. } catch (e) {
  203. console.error('加载数据失败', e)
  204. } finally {
  205. this.loading = false
  206. }
  207. },
  208. async loadPendingTasks() {
  209. try {
  210. const res = await getMyPendingTasks(this.currentMemberId)
  211. this.pendingTasks = res.data || []
  212. } catch (e) {
  213. console.error('加载待接受任务失败', e)
  214. }
  215. },
  216. async loadTodayTasks() {
  217. try {
  218. const res = await getTodayTasks(this.currentMemberId)
  219. this.todayTasks = res.data || []
  220. } catch (e) {
  221. console.error('加载今日任务失败', e)
  222. }
  223. },
  224. async loadCreatedTasks() {
  225. try {
  226. const res = await getTodayTasks(this.currentUserId)
  227. this.createdTasks = res.data || []
  228. } catch (e) {
  229. console.error('加载创建的任务失败', e)
  230. }
  231. },
  232. async completeTask(task) {
  233. if (task.memberOnly === 1 && !this.isMember) {
  234. uni.showModal({
  235. title: '会员任务',
  236. content: '该任务是会员专属任务,开通会员后即可完成',
  237. confirmText: '去开通',
  238. success: (res) => {
  239. if (res.confirm) {
  240. uni.navigateTo({ url: '/pages/membership/index' })
  241. }
  242. }
  243. })
  244. return
  245. }
  246. try {
  247. const res = await completeTaskApi(task.id, this.currentMemberId, '')
  248. uni.showToast({ title: `获得 ${res.data.pointsEarned} 积分`, icon: 'success' })
  249. this.loadTodayTasks()
  250. } catch (e) {
  251. console.error('完成任务失败', e)
  252. }
  253. },
  254. acceptTask(task) {
  255. if (task.memberOnly === 1 && !this.isMember) {
  256. uni.showModal({
  257. title: '会员任务',
  258. content: '该任务是会员专属任务,开通会员后即可接受',
  259. confirmText: '去开通',
  260. success: (res) => {
  261. if (res.confirm) {
  262. uni.navigateTo({ url: '/pages/membership/index' })
  263. }
  264. }
  265. })
  266. return
  267. }
  268. uni.showLoading({ title: '接受中...' })
  269. acceptTaskApi(task.id, this.currentMemberId).then(res => {
  270. uni.hideLoading()
  271. if (res.data) {
  272. uni.showToast({ title: '已接受任务', icon: 'success' })
  273. this.loadPendingTasks()
  274. this.loadTodayTasks()
  275. } else {
  276. uni.showToast({ title: '接受失败', icon: 'none' })
  277. }
  278. }).catch(e => {
  279. uni.hideLoading()
  280. uni.showToast({ title: '接受失败', icon: 'none' })
  281. })
  282. },
  283. showRejectModal(task) {
  284. this.rejectTaskId = task.id
  285. this.showRejectModalFlag = true
  286. },
  287. confirmReject() {
  288. uni.showLoading({ title: '处理中...' })
  289. rejectTaskApi(this.rejectTaskId, this.currentMemberId).then(res => {
  290. uni.hideLoading()
  291. if (res.data) {
  292. uni.showToast({ title: '已拒绝', icon: 'success' })
  293. this.loadPendingTasks()
  294. } else {
  295. uni.showToast({ title: '操作失败', icon: 'none' })
  296. }
  297. }).catch(e => {
  298. uni.hideLoading()
  299. uni.showToast({ title: '操作失败', icon: 'none' })
  300. })
  301. this.showRejectModalFlag = false
  302. },
  303. async createTask() {
  304. if (!this.newTask.title) {
  305. uni.showToast({ title: '请输入任务标题', icon: 'none' })
  306. return
  307. }
  308. if (!this.newTask.category) {
  309. uni.showToast({ title: '请选择类型', icon: 'none' })
  310. return
  311. }
  312. if (this.newTask.memberIds.length === 0) {
  313. uni.showToast({ title: '请选择分配对象', icon: 'none' })
  314. return
  315. }
  316. try {
  317. await createTaskApi({
  318. title: this.newTask.title,
  319. description: this.newTask.description,
  320. category: this.newTask.category,
  321. points: parseInt(this.newTask.points) || 2,
  322. deadline: this.newTask.deadline ? new Date(this.newTask.deadline).getTime() : null,
  323. executorType: 'member',
  324. memberIds: this.newTask.memberIds.map(id => Number(id))
  325. })
  326. uni.showToast({ title: '创建成功', icon: 'success' })
  327. this.showAddModal = false
  328. this.resetNewTask()
  329. this.loadCreatedTasks()
  330. this.loadPendingTasks()
  331. } catch (e) {
  332. console.error('创建任务失败', e)
  333. uni.showToast({ title: '创建失败', icon: 'none' })
  334. }
  335. },
  336. resetNewTask() {
  337. this.newTask = {
  338. title: '',
  339. description: '',
  340. category: '',
  341. points: 2,
  342. deadline: '',
  343. memberIds: []
  344. }
  345. },
  346. onCategoryChange(e) {
  347. this.newTask.category = this.categories[e.detail.value]
  348. },
  349. onDeadlineChange(e) {
  350. this.newTask.deadline = e.detail.value
  351. },
  352. onFamilyMemberSelectChange(e) {
  353. this.newTask.memberIds = e.detail.value
  354. },
  355. formatTime(dateStr) {
  356. if (!dateStr) return ''
  357. const date = parseDate(dateStr)
  358. if (!date) return ''
  359. return `${date.getMonth()+1}月${date.getDate()}日 ${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}`
  360. },
  361. statusLabel(status) {
  362. const map = {
  363. 'pending': '待审核',
  364. 'assigned': '待接受',
  365. 'in_progress': '进行中',
  366. 'completed': '已完成',
  367. 'rejected': '已拒绝',
  368. 'overdue': '已过期',
  369. 'cancelled': '已取消'
  370. }
  371. return map[status] || status
  372. }
  373. }
  374. }
  375. </script>
  376. <style scoped>
  377. .container {
  378. padding: 30rpx;
  379. padding-bottom: 120rpx;
  380. background: #f5f7fa;
  381. min-height: 100vh;
  382. }
  383. .tab-bar {
  384. display: flex;
  385. background: #fff;
  386. border-radius: 16rpx;
  387. padding: 10rpx;
  388. margin-bottom: 30rpx;
  389. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  390. }
  391. .tab-item {
  392. flex: 1;
  393. display: flex;
  394. align-items: center;
  395. justify-content: center;
  396. padding: 20rpx 0;
  397. border-radius: 12rpx;
  398. position: relative;
  399. }
  400. .tab-item.active {
  401. background: linear-gradient(135deg, #F97316, #FB923C);
  402. }
  403. .tab-text {
  404. font-size: 28rpx;
  405. color: #666;
  406. font-weight: 500;
  407. }
  408. .tab-item.active .tab-text {
  409. color: #fff;
  410. }
  411. .tab-badge {
  412. position: absolute;
  413. top: 8rpx;
  414. right: 30rpx;
  415. background: #FF4D4F;
  416. color: #fff;
  417. font-size: 20rpx;
  418. padding: 2rpx 10rpx;
  419. border-radius: 20rpx;
  420. min-width: 32rpx;
  421. text-align: center;
  422. }
  423. .task-list {
  424. display: flex;
  425. flex-direction: column;
  426. gap: 20rpx;
  427. }
  428. .task-item {
  429. background: #fff;
  430. border-radius: 16rpx;
  431. padding: 30rpx;
  432. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  433. display: flex;
  434. justify-content: space-between;
  435. align-items: center;
  436. }
  437. .task-item.assigned {
  438. border: 2rpx solid #F97316;
  439. }
  440. .task-item.completed {
  441. opacity: 0.6;
  442. }
  443. .task-left {
  444. flex: 1;
  445. }
  446. .task-title {
  447. font-size: 30rpx;
  448. font-weight: bold;
  449. color: #333;
  450. margin-bottom: 10rpx;
  451. }
  452. .task-meta {
  453. display: flex;
  454. gap: 20rpx;
  455. flex-wrap: wrap;
  456. }
  457. .points {
  458. color: #F97316;
  459. font-size: 24rpx;
  460. font-weight: 500;
  461. }
  462. .creator, .assignee {
  463. color: #999;
  464. font-size: 24rpx;
  465. }
  466. .deadline {
  467. color: #999;
  468. font-size: 24rpx;
  469. }
  470. .status-tag {
  471. font-size: 22rpx;
  472. padding: 4rpx 12rpx;
  473. border-radius: 12rpx;
  474. background: #f0f0f0;
  475. color: #666;
  476. }
  477. .status-assigned {
  478. background: #FFF7ED;
  479. color: #F97316;
  480. }
  481. .status-in_progress {
  482. background: #ECFDF5;
  483. color: #10B981;
  484. }
  485. .status-rejected {
  486. background: #FEF2F2;
  487. color: #EF4444;
  488. }
  489. .task-right {
  490. margin-left: 20rpx;
  491. }
  492. .btn-complete, .btn-accept {
  493. background: linear-gradient(135deg, #F97316, #FB923C);
  494. color: #fff;
  495. font-size: 26rpx;
  496. padding: 12rpx 30rpx;
  497. border-radius: 30rpx;
  498. border: none;
  499. }
  500. .btn-reject {
  501. background: #fff;
  502. color: #EF4444;
  503. font-size: 26rpx;
  504. padding: 12rpx 30rpx;
  505. border-radius: 30rpx;
  506. border: 2rpx solid #EF4444;
  507. margin-left: 16rpx;
  508. }
  509. .task-actions {
  510. display: flex;
  511. align-items: center;
  512. }
  513. .completed-badge {
  514. color: #52C41A;
  515. font-size: 26rpx;
  516. }
  517. .minigame-badge {
  518. font-size: 28rpx;
  519. margin-left: 10rpx;
  520. }
  521. .member-badge {
  522. display: inline-block;
  523. font-size: 20rpx;
  524. color: #B45309;
  525. background: #FEF3C7;
  526. border: 1rpx solid #F59E0B;
  527. border-radius: 8rpx;
  528. padding: 2rpx 12rpx;
  529. margin-left: 12rpx;
  530. vertical-align: middle;
  531. }
  532. .empty {
  533. text-align: center;
  534. color: #999;
  535. padding: 100rpx 0;
  536. font-size: 28rpx;
  537. }
  538. .add-btn {
  539. position: fixed;
  540. bottom: 40rpx;
  541. left: 30rpx;
  542. right: 30rpx;
  543. background: linear-gradient(135deg, #F97316, #FB923C);
  544. color: #fff;
  545. font-size: 30rpx;
  546. font-weight: bold;
  547. padding: 24rpx 0;
  548. border-radius: 40rpx;
  549. border: none;
  550. box-shadow: 0 8rpx 24rpx rgba(249,115,22,0.3);
  551. }
  552. .modal-mask {
  553. position: fixed;
  554. top: 0;
  555. left: 0;
  556. right: 0;
  557. bottom: 0;
  558. background: rgba(0, 0, 0, 0.5);
  559. display: flex;
  560. align-items: center;
  561. justify-content: center;
  562. z-index: 1000;
  563. }
  564. .modal {
  565. background: #fff;
  566. border-radius: 28rpx;
  567. padding: 40rpx;
  568. width: 85%;
  569. max-height: 80vh;
  570. overflow-y: auto;
  571. box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.12);
  572. }
  573. .modal-large {
  574. width: 90%;
  575. }
  576. .modal-title {
  577. font-size: 34rpx;
  578. font-weight: bold;
  579. margin-bottom: 32rpx;
  580. text-align: center;
  581. color: #333;
  582. }
  583. .modal-content {
  584. font-size: 28rpx;
  585. color: #666;
  586. line-height: 1.6;
  587. margin-bottom: 30rpx;
  588. text-align: center;
  589. }
  590. .modal-btns {
  591. display: flex;
  592. gap: 20rpx;
  593. margin-top: 30rpx;
  594. }
  595. .modal-btns button {
  596. flex: 1;
  597. height: 88rpx;
  598. line-height: 88rpx;
  599. border-radius: 44rpx;
  600. font-size: 28rpx;
  601. border: none;
  602. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
  603. }
  604. .modal-btns button:first-child {
  605. background: #f5f5f5;
  606. color: #666;
  607. }
  608. .form-item {
  609. margin-bottom: 28rpx;
  610. }
  611. .form-label {
  612. font-size: 28rpx;
  613. color: #333;
  614. margin-bottom: 14rpx;
  615. font-weight: 500;
  616. }
  617. .form-label .required {
  618. color: #F97316;
  619. }
  620. .form-item checkbox-group {
  621. display: flex;
  622. flex-wrap: wrap;
  623. gap: 16rpx;
  624. }
  625. .form-item label {
  626. display: flex;
  627. align-items: center;
  628. gap: 10rpx;
  629. font-size: 28rpx;
  630. color: #333;
  631. padding: 18rpx 28rpx;
  632. border: 1rpx solid #ddd;
  633. border-radius: 16rpx;
  634. background: #fafafa;
  635. transition: all 0.25s ease;
  636. }
  637. .form-item label.label-checked {
  638. border-color: #F97316;
  639. background: #FFF7ED;
  640. color: #F97316;
  641. }
  642. .form-item label:active {
  643. transform: scale(0.97);
  644. }
  645. .form-item input, .form-item .picker {
  646. border: 1rpx solid #ddd;
  647. border-radius: 16rpx;
  648. padding: 24rpx 28rpx;
  649. font-size: 28rpx;
  650. width: 100%;
  651. box-sizing: border-box;
  652. background: #fafafa;
  653. min-height: 88rpx;
  654. transition: all 0.25s ease;
  655. }
  656. .form-item input:focus {
  657. border-color: #F97316;
  658. background: #fff;
  659. box-shadow: 0 0 0 4rpx rgba(249, 115, 22, 0.1);
  660. }
  661. .form-item .picker {
  662. color: #333;
  663. }
  664. .textarea {
  665. min-height: 160rpx;
  666. width: 100%;
  667. box-sizing: border-box;
  668. font-size: 28rpx;
  669. padding: 24rpx 28rpx;
  670. border: 1rpx solid #ddd;
  671. border-radius: 16rpx;
  672. background: #fafafa;
  673. transition: all 0.25s ease;
  674. }
  675. </style>