You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

990 lines
21 KiB

  1. <!-- 自选股页面 -->
  2. <template>
  3. <view class="container">
  4. <!-- 自定义导航栏 -->
  5. <view class="custom-navbar">
  6. <view class="navbar-content">
  7. <view class="navbar-left">
  8. <view class="back-btn" @click="goBack">
  9. <image class="back-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/e5c501fd23303533622fadce8dedd6a0.png" mode="aspectFit"></image>
  10. </view>
  11. </view>
  12. <view class="navbar-center">
  13. <text class="navbar-title">我的自选</text>
  14. </view>
  15. <view class="navbar-right">
  16. <image
  17. class="navbar-btn"
  18. src="https://d31zlh4on95l9h.cloudfront.net/images/ba5c8a2eda065274e868bcd9b2d7d914.png"
  19. @click="onFirstButtonClick"
  20. mode="aspectFit"
  21. ></image>
  22. <image
  23. class="navbar-btn"
  24. src="https://d31zlh4on95l9h.cloudfront.net/images/a4ae8952aeae90dac6d2b4c221c65fa9.png"
  25. @click="onSecondButtonClick"
  26. mode="aspectFit"
  27. ></image>
  28. </view>
  29. </view>
  30. </view>
  31. <!-- 页面内容 -->
  32. <view class="page-content">
  33. <!-- 分组标签 -->
  34. <view class="group-tabs" v-if="stockGroups.length > 0">
  35. <scroll-view class="tabs-scroll" scroll-x="true" show-scrollbar="false">
  36. <view class="tabs-container">
  37. <view
  38. v-for="group in stockGroups"
  39. :key="group.id"
  40. :class="['tab-item', { 'active': currentGroupId === group.id }]"
  41. @click="switchGroup(group.id)"
  42. >
  43. <text class="tab-text">{{ group.name }}</text>
  44. </view>
  45. </view>
  46. </scroll-view>
  47. </view>
  48. <!-- 股票列表 -->
  49. <view class="stock-list">
  50. <view v-if="loading" class="loading-container">
  51. <text class="loading-text">加载中...</text>
  52. </view>
  53. <view v-else-if="stockList.length === 0" class="empty-container">
  54. <image
  55. class="empty-image"
  56. src="https://d31zlh4on95l9h.cloudfront.net/images/f5a9bd32c81bc7cca47252b51357c12f.png"
  57. mode="aspectFit"
  58. ></image>
  59. <text class="empty-text">暂无数据~</text>
  60. </view>
  61. <view v-else>
  62. <view
  63. v-for="stock in stockList"
  64. :key="stock.id"
  65. class="stock-item"
  66. @click="handleStockClick(stock)"
  67. >
  68. <!-- 多选模式下显示复选框 -->
  69. <view v-if="isMultiSelectMode" class="checkbox-container">
  70. <view
  71. :class="['checkbox', selectedStockIds.includes(stock.id) ? 'checked' : '']"
  72. @click.stop="toggleStockSelection(stock.id)"
  73. >
  74. <text v-if="selectedStockIds.includes(stock.id)" class="checkbox-icon"></text>
  75. </view>
  76. </view>
  77. <view class="stock-info">
  78. <text class="stock-name">{{ stock.name || stock.code }}</text>
  79. <text class="stock-code">{{ stock.code }}</text>
  80. </view>
  81. <view class="stock-price">
  82. <text class="price">{{ stock.price || '--' }}</text>
  83. <text :class="['change', stock.change >= 0 ? 'up' : 'down']">
  84. {{ stock.change >= 0 ? '+' : '-' }}{{ stock.change || '--' }}
  85. </text>
  86. </view>
  87. </view>
  88. </view>
  89. </view>
  90. </view>
  91. <!-- 多选模式下的底部操作栏 -->
  92. <view v-if="isMultiSelectMode" class="bottom-toolbar">
  93. <view class="toolbar-left">
  94. <text class="selected-count">已选择 {{ selectedStockIds.length }} 只股票</text>
  95. <text class="select-all-btn" @click="toggleSelectAll">
  96. {{ isAllSelected ? '取消全选' : '全选' }}
  97. </text>
  98. </view>
  99. <view class="toolbar-right">
  100. <button
  101. class="delete-btn"
  102. :disabled="selectedStockIds.length === 0"
  103. @click="deleteSelectedStocks"
  104. >
  105. 删除
  106. </button>
  107. <button
  108. class="add-to-group-btn"
  109. :disabled="selectedStockIds.length === 0"
  110. @click="showGroupSelectModal = true"
  111. >
  112. 添加至分组
  113. </button>
  114. </view>
  115. </view>
  116. <!-- 分组选择弹窗 -->
  117. <view v-if="showGroupSelectModal" class="modal-overlay" @click="closeGroupSelectModal">
  118. <view class="group-select-modal" @click.stop>
  119. <view class="modal-header">
  120. <text class="modal-title">编辑分组</text>
  121. <text class="modal-close" @click="closeGroupSelectModal"></text>
  122. </view>
  123. <view class="modal-content">
  124. <view class="group-grid">
  125. <view
  126. v-for="group in stockGroups"
  127. :key="group.id"
  128. :class="['group-item', group.id === currentGroupId ? 'current-group' : '']"
  129. @click="selectTargetGroup(group)"
  130. >
  131. <text class="group-name">{{ group.name }}</text>
  132. </view>
  133. <view class="group-item new-group" @click="createNewGroupInModal">
  134. <text class="new-group-text">+ 新建分组</text>
  135. </view>
  136. </view>
  137. </view>
  138. <view class="modal-footer">
  139. <button class="confirm-btn" @click="confirmMoveToGroup">确认</button>
  140. </view>
  141. </view>
  142. </view>
  143. </view>
  144. </template>
  145. <script>
  146. import { getUserStockGroupList, addUserStockGroup, getUserStockList, updateUserStockGroup, deleteUserStock } from '@/api/home/mySelections.js'
  147. export default {
  148. data() {
  149. return {
  150. // 分组数据
  151. stockGroups: [],
  152. // 当前选中的分组ID
  153. currentGroupId: null,
  154. // 当前分组下的股票列表
  155. stockList: [],
  156. // 加载状态
  157. loading: false,
  158. // 多选模式状态
  159. isMultiSelectMode: false,
  160. // 选中的股票ID列表
  161. selectedStockIds: [],
  162. // 显示分组选择弹窗
  163. showGroupSelectModal: false,
  164. // 选中的目标分组
  165. selectedTargetGroup: null
  166. }
  167. },
  168. computed: {
  169. // 是否全选
  170. isAllSelected() {
  171. return this.stockList.length > 0 && this.selectedStockIds.length === this.stockList.length
  172. }
  173. },
  174. onLoad() {
  175. this.loadStockGroups()
  176. },
  177. methods: {
  178. // 加载股票分组
  179. async loadStockGroups() {
  180. this.loading = true
  181. try {
  182. getUserStockGroupList(
  183. (response) => {
  184. console.log('获取分组成功:', response)
  185. if (response.code === 200 && response.data) {
  186. // 按ID排序,ID小的排在前面
  187. this.stockGroups = response.data.sort((a, b) => a.id - b.id)
  188. // 如果有分组,默认选中第一个
  189. if (this.stockGroups.length > 0) {
  190. this.currentGroupId = this.stockGroups[0].id
  191. this.loadStocksByGroup(this.currentGroupId)
  192. } else {
  193. // 如果没有分组,创建默认分组
  194. this.createDefaultGroup()
  195. }
  196. }
  197. },
  198. (error) => {
  199. console.error('获取分组失败:', error)
  200. // 如果获取失败,也尝试创建默认分组
  201. this.createDefaultGroup()
  202. }
  203. )
  204. } catch (error) {
  205. console.error('加载分组异常:', error)
  206. } finally {
  207. this.loading = false
  208. }
  209. },
  210. // 创建默认分组
  211. createDefaultGroup() {
  212. addUserStockGroup(
  213. (response) => {
  214. console.log('创建默认分组成功:', response)
  215. // 重新加载分组列表
  216. this.loadStockGroups()
  217. },
  218. (error) => {
  219. console.error('创建默认分组失败:', error)
  220. },
  221. { name: '默认分组' }
  222. )
  223. },
  224. // 根据分组ID加载股票列表
  225. loadStocksByGroup(groupId) {
  226. if (!groupId) return
  227. getUserStockList(
  228. (response) => {
  229. console.log('获取股票列表成功:', response)
  230. if (response.code === 200 && response.data && response.data.records) {
  231. // 股票列表在data.records中,根据groupId过滤
  232. this.stockList = response.data.records.filter(stock => stock.groupId === groupId)
  233. } else {
  234. this.stockList = []
  235. }
  236. },
  237. (error) => {
  238. console.error('获取股票列表失败:', error)
  239. this.stockList = []
  240. },
  241. { groupId: groupId }
  242. )
  243. },
  244. // 切换分组
  245. switchGroup(groupId) {
  246. if (this.currentGroupId === groupId) return
  247. this.currentGroupId = groupId
  248. this.loadStocksByGroup(groupId)
  249. },
  250. // 创建新分组
  251. async createNewGroup(groupName) {
  252. if (!groupName) {
  253. uni.showToast({
  254. title: '分组名称不能为空',
  255. icon: 'none'
  256. })
  257. return
  258. }
  259. uni.showLoading({
  260. title: '创建中...'
  261. })
  262. console.log('开始请求创建分组接口')
  263. try {
  264. const response = await addUserStockGroup(null, null, {
  265. name: groupName
  266. })
  267. console.log('创建分组接口返回:', response)
  268. if (response.code === 200) {
  269. uni.showToast({
  270. title: '创建成功',
  271. icon: 'success'
  272. })
  273. // 重新加载分组列表
  274. await this.loadStockGroups()
  275. // 切换到新创建的分组
  276. if (response.data && response.data.id) {
  277. this.switchGroup(response.data.id)
  278. }
  279. } else {
  280. uni.showToast({
  281. title: response.message || '创建失败',
  282. icon: 'none'
  283. })
  284. }
  285. } catch (error) {
  286. console.error('创建分组失败:', error)
  287. uni.showToast({
  288. title: '创建失败,请重试',
  289. icon: 'none'
  290. })
  291. } finally {
  292. // 确保在所有情况下都隐藏加载状态
  293. uni.hideLoading()
  294. }
  295. },
  296. // 返回上一页
  297. goBack() {
  298. uni.navigateBack()
  299. },
  300. // 第一个按钮点击事件 - 创建分组
  301. onFirstButtonClick() {
  302. uni.showModal({
  303. title: '创建分组',
  304. content: '请输入分组名称',
  305. editable: true,
  306. placeholderText: '请输入分组名称',
  307. success: (res) => {
  308. if (res.confirm && res.content) {
  309. this.createNewGroup(res.content.trim())
  310. }
  311. }
  312. })
  313. },
  314. // 第二个按钮点击事件 - 切换多选模式
  315. onSecondButtonClick() {
  316. this.isMultiSelectMode = !this.isMultiSelectMode
  317. // 退出多选模式时清空选中状态
  318. if (!this.isMultiSelectMode) {
  319. this.selectedStockIds = []
  320. }
  321. console.log('多选模式:', this.isMultiSelectMode)
  322. },
  323. // 处理股票点击事件
  324. handleStockClick(stock) {
  325. if (this.isMultiSelectMode) {
  326. // 多选模式下切换选中状态
  327. this.toggleStockSelection(stock.id)
  328. } else {
  329. // 普通模式下可以添加其他逻辑,比如跳转到股票详情
  330. console.log('点击股票:', stock)
  331. }
  332. },
  333. // 切换股票选中状态
  334. toggleStockSelection(stockId) {
  335. const index = this.selectedStockIds.indexOf(stockId)
  336. if (index > -1) {
  337. // 已选中,取消选中
  338. this.selectedStockIds.splice(index, 1)
  339. } else {
  340. // 未选中,添加到选中列表
  341. this.selectedStockIds.push(stockId)
  342. }
  343. },
  344. // 全选/取消全选
  345. toggleSelectAll() {
  346. if (this.isAllSelected) {
  347. // 取消全选
  348. this.selectedStockIds = []
  349. } else {
  350. // 全选
  351. this.selectedStockIds = this.stockList.map(stock => stock.id)
  352. }
  353. },
  354. // 关闭分组选择弹窗
  355. closeGroupSelectModal() {
  356. this.showGroupSelectModal = false
  357. this.selectedTargetGroup = null
  358. },
  359. // 选择目标分组
  360. selectTargetGroup(group) {
  361. this.selectedTargetGroup = group
  362. },
  363. // 在弹窗中创建新分组
  364. createNewGroupInModal() {
  365. uni.showModal({
  366. title: '创建分组',
  367. content: '请输入分组名称',
  368. editable: true,
  369. placeholderText: '请输入分组名称',
  370. success: (res) => {
  371. if (res.confirm && res.content) {
  372. this.createNewGroupAndSelect(res.content.trim())
  373. }
  374. }
  375. })
  376. },
  377. // 创建新分组并选中
  378. async createNewGroupAndSelect(groupName) {
  379. try {
  380. uni.showLoading({
  381. title: '创建中...'
  382. })
  383. const response = await addUserStockGroup(null, null, {
  384. name: groupName
  385. })
  386. if (response.code === 200) {
  387. uni.showToast({
  388. title: '创建成功',
  389. icon: 'success'
  390. })
  391. // 重新加载分组列表
  392. await this.loadStockGroups()
  393. // 选中新创建的分组作为目标分组
  394. if (response.data && response.data.id) {
  395. this.selectedTargetGroup = this.stockGroups.find(g => g.id === response.data.id)
  396. }
  397. } else {
  398. uni.showToast({
  399. title: response.message || '创建失败',
  400. icon: 'none'
  401. })
  402. }
  403. } catch (error) {
  404. console.error('创建分组失败:', error)
  405. uni.showToast({
  406. title: '创建失败,请重试',
  407. icon: 'none'
  408. })
  409. } finally {
  410. uni.hideLoading()
  411. }
  412. },
  413. // 确认移动到分组
  414. confirmMoveToGroup() {
  415. if (!this.selectedTargetGroup) {
  416. uni.showToast({
  417. title: '请选择目标分组',
  418. icon: 'none'
  419. })
  420. return
  421. }
  422. if (this.selectedStockIds.length === 0) {
  423. uni.showToast({
  424. title: '请选择要移动的股票',
  425. icon: 'none'
  426. })
  427. return
  428. }
  429. // 调用移动股票的API
  430. this.moveStocksToGroup(this.selectedTargetGroup.id)
  431. },
  432. // 移动股票到指定分组
  433. async moveStocksToGroup(targetGroupId) {
  434. try {
  435. uni.showLoading({
  436. title: '移动中...'
  437. })
  438. // 调用API来更新股票的分组ID
  439. const promises = this.selectedStockIds.map(stockId => {
  440. return updateUserStockGroup(null, null, {
  441. stockId: stockId,
  442. groupId: targetGroupId
  443. })
  444. })
  445. await Promise.all(promises)
  446. uni.showToast({
  447. title: '移动成功',
  448. icon: 'success'
  449. })
  450. // 关闭弹窗
  451. this.closeGroupSelectModal()
  452. // 退出多选模式
  453. this.isMultiSelectMode = false
  454. this.selectedStockIds = []
  455. // 重新加载当前分组的股票列表
  456. this.loadStocksByGroup(this.currentGroupId)
  457. } catch (error) {
  458. console.error('移动股票失败:', error)
  459. uni.showToast({
  460. title: '移动失败,请重试',
  461. icon: 'none'
  462. })
  463. } finally {
  464. uni.hideLoading()
  465. }
  466. },
  467. // 删除选中的股票
  468. async deleteSelectedStocks() {
  469. // 检查是否有选中的股票
  470. if (this.selectedStockIds.length === 0) {
  471. uni.showToast({
  472. title: '请先选择要删除的股票',
  473. icon: 'none'
  474. })
  475. return
  476. }
  477. // 显示确认删除弹窗
  478. uni.showModal({
  479. title: '确认删除',
  480. content: `确定要删除选中的 ${this.selectedStockIds.length} 只股票吗?`,
  481. success: async (res) => {
  482. if (res.confirm) {
  483. await this.performDeleteStocks()
  484. }
  485. }
  486. })
  487. },
  488. // 执行删除股票操作
  489. async performDeleteStocks() {
  490. try {
  491. uni.showLoading({
  492. title: '删除中...'
  493. })
  494. // 调用API删除每只选中的股票
  495. const deletePromises = this.selectedStockIds.map(stockId => {
  496. return deleteUserStock(null, null, {
  497. groupId: this.currentGroupId,
  498. id: stockId
  499. })
  500. })
  501. await Promise.all(deletePromises)
  502. uni.showToast({
  503. title: '删除成功',
  504. icon: 'success'
  505. })
  506. // 退出多选模式
  507. this.isMultiSelectMode = false
  508. this.selectedStockIds = []
  509. // 重新加载当前分组的股票列表
  510. this.loadStocksByGroup(this.currentGroupId)
  511. } catch (error) {
  512. console.error('删除股票失败:', error)
  513. uni.showToast({
  514. title: '删除失败,请重试',
  515. icon: 'none'
  516. })
  517. } finally {
  518. uni.hideLoading()
  519. }
  520. }
  521. }
  522. }
  523. </script>
  524. <style>
  525. .container {
  526. width: 100%;
  527. height: 100vh;
  528. background-color: #f5f5f5;
  529. }
  530. /* 自定义导航栏 */
  531. .custom-navbar {
  532. position: fixed;
  533. top: 0;
  534. left: 0;
  535. right: 0;
  536. z-index: 999;
  537. background-color: #ffffff;
  538. border-bottom: 1px solid #e5e5e5;
  539. }
  540. .navbar-content {
  541. display: flex;
  542. align-items: center;
  543. justify-content: space-between;
  544. height: 44px;
  545. padding: 0 15px;
  546. /* 适配状态栏高度 */
  547. padding-top: var(--status-bar-height, 20px);
  548. min-height: calc(44px + var(--status-bar-height, 20px));
  549. }
  550. .navbar-left {
  551. flex: 0 0 auto;
  552. display: flex;
  553. align-items: center;
  554. }
  555. .back-btn {
  556. width: 40px;
  557. height: 40px;
  558. display: flex;
  559. align-items: center;
  560. justify-content: center;
  561. }
  562. .back-icon {
  563. width: 24px;
  564. height: 24px;
  565. }
  566. .navbar-center {
  567. flex: 1;
  568. display: flex;
  569. align-items: center;
  570. justify-content: center;
  571. }
  572. .navbar-title {
  573. font-size: 18px;
  574. font-weight: 500;
  575. color: #333333;
  576. }
  577. .navbar-right {
  578. flex: 0 0 auto;
  579. display: flex;
  580. align-items: center;
  581. gap: 10px;
  582. }
  583. .navbar-btn {
  584. width: 24px;
  585. height: 24px;
  586. }
  587. /* 页面内容 */
  588. .page-content {
  589. padding-top: calc(44px + var(--status-bar-height, 20px) + 20px);
  590. min-height: calc(100vh - 44px - var(--status-bar-height, 20px) - 20px);
  591. }
  592. /* 分组标签样式 */
  593. .group-tabs {
  594. background-color: #ffffff;
  595. padding: 10px 0;
  596. }
  597. .tabs-scroll {
  598. width: 100%;
  599. }
  600. .tabs-container {
  601. display: flex;
  602. padding: 0 15px;
  603. white-space: nowrap;
  604. }
  605. .tab-item {
  606. flex-shrink: 0;
  607. padding: 8px 16px;
  608. margin-right: 10px;
  609. border-radius: 20px;
  610. background-color: #f5f5f5;
  611. border: 1px solid #e0e0e0;
  612. transition: all 0.3s ease;
  613. cursor: pointer;
  614. }
  615. .tab-item:hover {
  616. background-color: #e8e8e8;
  617. }
  618. .tab-item.active {
  619. background-color: #ff3b30;
  620. border-color: #ff3b30;
  621. box-shadow: 0 2px 8px rgba(255, 59, 48, 0.3);
  622. }
  623. .tab-text {
  624. font-size: 14px;
  625. color: #666666;
  626. white-space: nowrap;
  627. font-weight: 400;
  628. transition: all 0.3s ease;
  629. }
  630. .tab-item.active .tab-text {
  631. color: #ffffff;
  632. font-weight: 500;
  633. }
  634. /* 股票列表样式 */
  635. .stock-list {
  636. flex: 1;
  637. padding: 15px;
  638. }
  639. .loading-container,
  640. .empty-container {
  641. display: flex;
  642. flex-direction: column;
  643. align-items: center;
  644. justify-content: center;
  645. padding: 60px 20px;
  646. }
  647. .loading-text {
  648. font-size: 16px;
  649. color: #666666;
  650. }
  651. .empty-image {
  652. width: 120px;
  653. height: 120px;
  654. margin-bottom: 20px;
  655. }
  656. .empty-text {
  657. font-size: 16px;
  658. color: #999999;
  659. }
  660. .stock-item {
  661. display: flex;
  662. align-items: center;
  663. justify-content: space-between;
  664. padding: 15px 0;
  665. border-bottom: 1px solid #f0f0f0;
  666. background-color: #ffffff;
  667. margin-bottom: 8px;
  668. border-radius: 8px;
  669. padding: 15px;
  670. }
  671. .stock-item:last-child {
  672. margin-bottom: 0;
  673. }
  674. .stock-info {
  675. flex: 1;
  676. display: flex;
  677. flex-direction: column;
  678. }
  679. .stock-name {
  680. font-size: 16px;
  681. font-weight: 500;
  682. color: #333333;
  683. margin-bottom: 4px;
  684. }
  685. .stock-code {
  686. font-size: 12px;
  687. color: #999999;
  688. }
  689. .stock-price {
  690. display: flex;
  691. flex-direction: row;
  692. align-items: center;
  693. gap: 8px;
  694. }
  695. .price {
  696. font-size: 16px;
  697. font-weight: 500;
  698. color: #333333;
  699. }
  700. .change {
  701. font-size: 12px;
  702. font-weight: 500;
  703. }
  704. .change.up {
  705. color: #ff3b30;
  706. }
  707. .change.down {
  708. color: #34c759;
  709. }
  710. /* 复选框样式 */
  711. .checkbox-container {
  712. margin-right: 12px;
  713. }
  714. .checkbox {
  715. width: 20px;
  716. height: 20px;
  717. border: 2px solid #ddd;
  718. border-radius: 4px;
  719. display: flex;
  720. align-items: center;
  721. justify-content: center;
  722. background-color: #fff;
  723. }
  724. .checkbox.checked {
  725. background-color: #ff3b30;
  726. border-color: #ff3b30;
  727. }
  728. .checkbox-icon {
  729. color: #fff;
  730. font-size: 12px;
  731. font-weight: bold;
  732. }
  733. /* 底部操作栏样式 */
  734. .bottom-toolbar {
  735. position: fixed;
  736. bottom: 0;
  737. left: 0;
  738. right: 0;
  739. background-color: #fff;
  740. border-top: 1px solid #f0f0f0;
  741. padding: 12px 16px;
  742. display: flex;
  743. align-items: center;
  744. justify-content: space-between;
  745. z-index: 1000;
  746. }
  747. .toolbar-left {
  748. display: flex;
  749. align-items: center;
  750. gap: 16px;
  751. }
  752. .selected-count {
  753. font-size: 14px;
  754. color: #333;
  755. }
  756. .select-all-btn {
  757. font-size: 14px;
  758. color: #ff3b30;
  759. padding: 4px 8px;
  760. }
  761. .toolbar-right {
  762. display: flex;
  763. align-items: center;
  764. gap: 12px;
  765. }
  766. .delete-btn {
  767. background-color: #ff6b6b;
  768. color: #fff;
  769. border: none;
  770. border-radius: 6px;
  771. padding: 8px 16px;
  772. font-size: 14px;
  773. }
  774. .delete-btn:disabled {
  775. background-color: #ccc;
  776. color: #999;
  777. }
  778. .add-to-group-btn {
  779. background-color: #ff3b30;
  780. color: #fff;
  781. border: none;
  782. border-radius: 6px;
  783. padding: 8px 16px;
  784. font-size: 14px;
  785. }
  786. .add-to-group-btn:disabled {
  787. background-color: #ccc;
  788. color: #999;
  789. }
  790. /* 弹窗样式 */
  791. .modal-overlay {
  792. position: fixed;
  793. top: 0;
  794. left: 0;
  795. right: 0;
  796. bottom: 0;
  797. background-color: rgba(0, 0, 0, 0.5);
  798. display: flex;
  799. align-items: flex-end;
  800. z-index: 1000;
  801. }
  802. .group-select-modal {
  803. background-color: white;
  804. border-radius: 20rpx 20rpx 0 0;
  805. width: 100%;
  806. max-height: 80vh;
  807. overflow: hidden;
  808. }
  809. .modal-header {
  810. display: flex;
  811. justify-content: space-between;
  812. align-items: center;
  813. padding: 30rpx 40rpx;
  814. border-bottom: 1px solid #f0f0f0;
  815. }
  816. .modal-title {
  817. font-size: 36rpx;
  818. font-weight: bold;
  819. color: #333333;
  820. }
  821. .modal-close {
  822. font-size: 40rpx;
  823. color: #999999;
  824. padding: 10rpx;
  825. }
  826. .modal-content {
  827. padding: 40rpx;
  828. max-height: 60vh;
  829. overflow-y: auto;
  830. }
  831. .group-grid {
  832. display: grid;
  833. grid-template-columns: repeat(2, 1fr);
  834. gap: 20rpx;
  835. }
  836. .group-item {
  837. background-color: #f8f8f8;
  838. border-radius: 16rpx;
  839. padding: 30rpx 20rpx;
  840. text-align: center;
  841. border: 2rpx solid transparent;
  842. transition: all 0.3s ease;
  843. }
  844. .group-item.current-group {
  845. background-color: #fff2f0;
  846. border-color: #ff4d4f;
  847. }
  848. .group-item:active {
  849. background-color: #e6f7ff;
  850. border-color: #1890ff;
  851. }
  852. .group-name {
  853. font-size: 28rpx;
  854. color: #333333;
  855. font-weight: 500;
  856. }
  857. .group-item.new-group {
  858. background-color: #fff;
  859. border: 2rpx dashed #d9d9d9;
  860. }
  861. .new-group-text {
  862. font-size: 28rpx;
  863. color: #ff4d4f;
  864. font-weight: 500;
  865. }
  866. .modal-footer {
  867. padding: 30rpx 40rpx;
  868. border-top: 1px solid #f0f0f0;
  869. }
  870. .confirm-btn {
  871. width: 100%;
  872. height: 88rpx;
  873. background-color: #ff4d4f;
  874. color: white;
  875. border: none;
  876. border-radius: 44rpx;
  877. font-size: 32rpx;
  878. font-weight: 500;
  879. }
  880. </style>