From 5a041ecaf5637b0ef7b87dd77694a1001660c61e Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 25 Nov 2025 11:50:02 +0800 Subject: [PATCH] init --- .gitignore | 14 + LICENSE | 21 + README.md | 145 + db/im-platform.sql | 112 + db/敏感词库初始化.sql | 470 + im-client/pom.xml | 21 + .../com/bx/imclient/IMAutoConfiguration.java | 13 + .../main/java/com/bx/imclient/IMClient.java | 92 + .../bx/imclient/annotation/IMListener.java | 18 + .../bx/imclient/listener/MessageListener.java | 12 + .../listener/MessageListenerMulticaster.java | 46 + .../java/com/bx/imclient/sender/IMSender.java | 262 + .../task/AbstractMessageResultTask.java | 21 + .../task/GroupMessageResultResultTask.java | 27 + .../task/PrivateMessageResultResultTask.java | 26 + .../task/SystemMessageResultResultTask.java | 25 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + im-common/pom.xml | 62 + .../com/bx/imcommon/contant/IMConstant.java | 18 + .../com/bx/imcommon/contant/IMRedisKey.java | 40 + .../java/com/bx/imcommon/enums/IMCmdType.java | 55 + .../com/bx/imcommon/enums/IMListenerType.java | 34 + .../com/bx/imcommon/enums/IMSendCode.java | 32 + .../com/bx/imcommon/enums/IMTerminalType.java | 51 + .../com/bx/imcommon/model/IMGroupMessage.java | 44 + .../bx/imcommon/model/IMHeartbeatInfo.java | 7 + .../com/bx/imcommon/model/IMLoginInfo.java | 9 + .../bx/imcommon/model/IMPrivateMessage.java | 44 + .../com/bx/imcommon/model/IMRecvInfo.java | 40 + .../com/bx/imcommon/model/IMSendInfo.java | 18 + .../com/bx/imcommon/model/IMSendResult.java | 28 + .../com/bx/imcommon/model/IMSessionInfo.java | 17 + .../bx/imcommon/model/IMSystemMessage.java | 34 + .../com/bx/imcommon/model/IMUserInfo.java | 28 + .../com/bx/imcommon/mq/RedisMQConfig.java | 32 + .../com/bx/imcommon/mq/RedisMQConsumer.java | 35 + .../com/bx/imcommon/mq/RedisMQListener.java | 29 + .../com/bx/imcommon/mq/RedisMQPullTask.java | 116 + .../com/bx/imcommon/mq/RedisMQTemplate.java | 44 + .../serializer/DateToLongSerializer.java | 28 + .../com/bx/imcommon/util/CommaTextUtils.java | 89 + .../java/com/bx/imcommon/util/JwtUtil.java | 89 + .../util/ThreadPoolExecutorFactory.java | 87 + im-platform/pom.xml | 111 + .../java/com/bx/implatform/IMPlatformApp.java | 23 + .../bx/implatform/annotation/OnlineCheck.java | 15 + .../bx/implatform/annotation/RedisLock.java | 37 + .../implatform/annotation/RepeatSubmit.java | 31 + .../implatform/aspect/OnlineCheckAspect.java | 36 + .../bx/implatform/aspect/RedisLockAspect.java | 85 + .../implatform/aspect/RepeatSubmitAspect.java | 100 + .../com/bx/implatform/config/ICEServer.java | 11 + .../implatform/config/MinIoClientConfig.java | 19 + .../com/bx/implatform/config/MvcConfig.java | 34 + .../com/bx/implatform/config/RedisConfig.java | 74 + .../bx/implatform/config/SwaggerConfig.java | 38 + .../config/TaskSchedulerConfig.java | 27 + .../bx/implatform/config/WebrtcConfig.java | 19 + .../config/props/JwtProperties.java | 22 + .../config/props/MinioProperties.java | 34 + .../com/bx/implatform/contant/Constant.java | 33 + .../com/bx/implatform/contant/RedisKey.java | 72 + .../implatform/controller/FileController.java | 38 + .../controller/FriendController.java | 64 + .../controller/GroupController.java | 113 + .../controller/GroupMessageController.java | 73 + .../controller/LoginController.java | 51 + .../controller/PrivateMessageController.java | 74 + .../controller/SystemController.java | 36 + .../implatform/controller/UserController.java | 65 + .../controller/WebrtcPrivateController.java | 76 + .../com/bx/implatform/dto/FriendDndDTO.java | 23 + .../com/bx/implatform/dto/GroupBanDTO.java | 21 + .../com/bx/implatform/dto/GroupDndDTO.java | 23 + .../com/bx/implatform/dto/GroupInviteDTO.java | 23 + .../implatform/dto/GroupMemberRemoveDTO.java | 28 + .../bx/implatform/dto/GroupMessageDTO.java | 38 + .../com/bx/implatform/dto/GroupUnbanDTO.java | 18 + .../java/com/bx/implatform/dto/LoginDTO.java | 28 + .../com/bx/implatform/dto/ModifyPwdDTO.java | 19 + .../bx/implatform/dto/PrivateMessageDTO.java | 29 + .../com/bx/implatform/dto/RegisterDTO.java | 27 + .../com/bx/implatform/dto/UserBanDTO.java | 21 + .../implatform/dto/WebrtcGroupAnswerDTO.java | 29 + .../dto/WebrtcGroupCandidateDTO.java | 29 + .../implatform/dto/WebrtcGroupDeviceDTO.java | 26 + .../implatform/dto/WebrtcGroupFailedDTO.java | 23 + .../implatform/dto/WebrtcGroupInviteDTO.java | 28 + .../bx/implatform/dto/WebrtcGroupJoinDTO.java | 20 + .../implatform/dto/WebrtcGroupOfferDTO.java | 29 + .../implatform/dto/WebrtcGroupSetupDTO.java | 28 + .../com/bx/implatform/entity/FileInfo.java | 67 + .../java/com/bx/implatform/entity/Friend.java | 63 + .../java/com/bx/implatform/entity/Group.java | 72 + .../com/bx/implatform/entity/GroupMember.java | 86 + .../bx/implatform/entity/GroupMessage.java | 88 + .../bx/implatform/entity/PrivateMessage.java | 63 + .../bx/implatform/entity/SensitiveWord.java | 50 + .../java/com/bx/implatform/entity/User.java | 87 + .../com/bx/implatform/enums/FileType.java | 32 + .../bx/implatform/enums/MessageStatus.java | 33 + .../com/bx/implatform/enums/MessageType.java | 74 + .../com/bx/implatform/enums/ResultCode.java | 50 + .../com/bx/implatform/enums/WebrtcMode.java | 27 + .../implatform/exception/GlobalException.java | 34 + .../exception/GlobalExceptionHandler.java | 100 + .../com/bx/implatform/filter/CacheFilter.java | 29 + .../CacheHttpServletRequestWrapper.java | 57 + .../interceptor/AuthInterceptor.java | 50 + .../interceptor/XssInterceptor.java | 50 + .../listener/GroupMessageListener.java | 32 + .../listener/PrivateMessageListener.java | 53 + .../listener/SystemMessageListener.java | 26 + .../bx/implatform/mapper/FileInfoMapper.java | 8 + .../bx/implatform/mapper/FriendMapper.java | 8 + .../com/bx/implatform/mapper/GroupMapper.java | 8 + .../implatform/mapper/GroupMemberMapper.java | 8 + .../implatform/mapper/GroupMessageMapper.java | 8 + .../mapper/PrivateMessageMapper.java | 8 + .../mapper/SensitiveWordMapper.java | 16 + .../com/bx/implatform/mapper/UserMapper.java | 8 + .../java/com/bx/implatform/result/Result.java | 14 + .../com/bx/implatform/result/ResultUtils.java | 63 + .../bx/implatform/service/FileService.java | 15 + .../bx/implatform/service/FriendService.java | 81 + .../service/GroupMemberService.java | 102 + .../service/GroupMessageService.java | 65 + .../bx/implatform/service/GroupService.java | 102 + .../service/PrivateMessageService.java | 67 + .../service/SensitiveWordService.java | 15 + .../bx/implatform/service/UserService.java | 86 + .../service/WebrtcPrivateService.java | 26 + .../service/impl/FileServiceImpl.java | 212 + .../service/impl/FriendServiceImpl.java | 289 + .../service/impl/GroupMemberServiceImpl.java | 131 + .../service/impl/GroupMessageServiceImpl.java | 470 + .../service/impl/GroupServiceImpl.java | 418 + .../impl/PrivateMessageServiceImpl.java | 271 + .../impl/SensitiveWordServiceImpl.java | 30 + .../service/impl/UserServiceImpl.java | 225 + .../impl/WebrtcPrivateServiceImpl.java | 337 + .../bx/implatform/session/SessionContext.java | 21 + .../bx/implatform/session/UserSession.java | 20 + .../session/WebrtcGroupSession.java | 33 + .../session/WebrtcPrivateSession.java | 39 + .../bx/implatform/session/WebrtcUserInfo.java | 28 + .../consumer/GroupBannedConsumerTask.java | 68 + .../task/consumer/GroupUnbanConsumerTask.java | 67 + .../task/consumer/UserBannedConsumerTask.java | 42 + .../task/schedule/FileExpireTask.java | 78 + .../schedule/ReloadSensitiveWordTask.java | 26 + .../implatform/thirdparty/MinioService.java | 156 + .../com/bx/implatform/util/BeanUtils.java | 45 + .../com/bx/implatform/util/DateTimeUtils.java | 35 + .../java/com/bx/implatform/util/FileUtil.java | 43 + .../com/bx/implatform/util/ImageUtil.java | 78 + .../implatform/util/SensitiveFilterUtil.java | 207 + .../bx/implatform/util/UserStateUtils.java | 44 + .../java/com/bx/implatform/util/XssUtil.java | 21 + .../java/com/bx/implatform/vo/FriendVO.java | 29 + .../com/bx/implatform/vo/GroupMemberVO.java | 34 + .../com/bx/implatform/vo/GroupMessageVO.java | 53 + .../java/com/bx/implatform/vo/GroupVO.java | 63 + .../java/com/bx/implatform/vo/LoginVO.java | 22 + .../bx/implatform/vo/OnlineTerminalVO.java | 24 + .../bx/implatform/vo/PrivateMessageVO.java | 38 + .../com/bx/implatform/vo/SystemConfigVO.java | 21 + .../com/bx/implatform/vo/SystemMessageVO.java | 26 + .../com/bx/implatform/vo/UploadImageVO.java | 21 + .../java/com/bx/implatform/vo/UserVO.java | 51 + .../bx/implatform/vo/WebrtcGroupFailedVO.java | 22 + .../bx/implatform/vo/WebrtcGroupInfoVO.java | 27 + .../src/main/resources/application-dev.yml | 28 + .../src/main/resources/application-prod.yml | 33 + .../src/main/resources/application-test.yml | 32 + .../src/main/resources/application.yml | 33 + im-platform/src/main/resources/logback.xml | 48 + .../src/main/resources/static/favicon.ico | Bin 0 -> 22710 bytes im-server/pom.xml | 43 + .../java/com/bx/imserver/IMServerApp.java | 19 + .../bx/imserver/constant/ChannelAttrKey.java | 21 + .../bx/imserver/netty/IMChannelHandler.java | 99 + .../java/com/bx/imserver/netty/IMServer.java | 10 + .../com/bx/imserver/netty/IMServerGroup.java | 55 + .../bx/imserver/netty/UserChannelCtxMap.java | 46 + .../processor/AbstractMessageProcessor.java | 18 + .../processor/GroupMessageProcessor.java | 71 + .../netty/processor/HeartbeatProcessor.java | 56 + .../netty/processor/LoginProcessor.java | 83 + .../processor/PrivateMessageProcessor.java | 67 + .../netty/processor/ProcessorFactory.java | 19 + .../processor/SystemMessageProcessor.java | 67 + .../imserver/netty/tcp/TcpSocketServer.java | 98 + .../tcp/endecode/MessageProtocolDecoder.java | 30 + .../tcp/endecode/MessageProtocolEncoder.java | 24 + .../bx/imserver/netty/ws/WebSocketServer.java | 107 + .../ws/endecode/MessageProtocolDecoder.java | 19 + .../ws/endecode/MessageProtocolEncoder.java | 20 + .../task/AbstractPullMessageTask.java | 23 + .../imserver/task/PullGroupMessageTask.java | 25 + .../imserver/task/PullPrivateMessageTask.java | 25 + .../imserver/task/PullSystemMessageTask.java | 28 + .../bx/imserver/util/SpringContextHolder.java | 39 + .../src/main/resources/application-dev.yml | 5 + .../src/main/resources/application-prod.yml | 6 + .../src/main/resources/application-test.yml | 6 + im-server/src/main/resources/application.yml | 17 + im-server/src/main/resources/logback.xml | 48 + im-uniapp/.env.js | 27 + im-uniapp/App.vue | 551 + im-uniapp/common/date.js | 66 + im-uniapp/common/emotion.js | 43 + im-uniapp/common/enums.js | 70 + im-uniapp/common/messageType.js | 40 + im-uniapp/common/recorder-app.js | 67 + im-uniapp/common/recorder-h5.js | 92 + im-uniapp/common/request.js | 93 + im-uniapp/common/str.js | 14 + im-uniapp/common/url.js | 23 + im-uniapp/common/wssocket.js | 154 + im-uniapp/components/bar/arrow-bar.vue | 64 + im-uniapp/components/bar/bar-group.vue | 17 + im-uniapp/components/bar/btn-bar.vue | 68 + im-uniapp/components/bar/switch-bar.vue | 61 + .../components/chat-at-box/chat-at-box.vue | 173 + .../chat-group-readed/chat-group-readed.vue | 133 + im-uniapp/components/chat-item/chat-item.vue | 200 + .../chat-message-item/chat-message-item.vue | 546 + .../components/chat-record/chat-record.vue | 243 + .../components/file-upload/file-upload.vue | 122 + .../components/friend-item/friend-item.vue | 82 + .../components/group-item/group-item.vue | 58 + .../group-member-selector.vue | 180 + .../group-rtc-join/group-rtc-join.vue | 88 + .../components/head-image/head-image.vue | 129 + .../components/image-upload/image-upload.vue | 100 + im-uniapp/components/loading/loading.vue | 71 + .../long-press-menu/long-press-menu.vue | 129 + im-uniapp/components/nav-bar/nav-bar.vue | 121 + .../virtual-scroller/virtual-scroller.vue | 60 + im-uniapp/hybrid/html/rtc-group/index.html | 23 + im-uniapp/hybrid/html/rtc-private/index.html | 23 + im-uniapp/im-var.scss | 64 + im-uniapp/im.scss | 258 + im-uniapp/index.html | 20 + im-uniapp/main.js | 65 + im-uniapp/manifest.json | 136 + im-uniapp/package.json | 12 + im-uniapp/pages.json | 114 + im-uniapp/pages/chat/chat-box.vue | 1358 + im-uniapp/pages/chat/chat-group-video.vue | 138 + im-uniapp/pages/chat/chat-private-video.vue | 100 + im-uniapp/pages/chat/chat.vue | 207 + im-uniapp/pages/common/external-link.vue | 20 + im-uniapp/pages/common/user-info.vue | 254 + im-uniapp/pages/friend/friend-add.vue | 146 + im-uniapp/pages/friend/friend.vue | 192 + im-uniapp/pages/group/group-edit.vue | 199 + im-uniapp/pages/group/group-info.vue | 377 + im-uniapp/pages/group/group-invite.vue | 176 + im-uniapp/pages/group/group-member.vue | 127 + im-uniapp/pages/group/group.vue | 119 + im-uniapp/pages/login/login.vue | 293 + im-uniapp/pages/mine/mine-edit.vue | 140 + im-uniapp/pages/mine/mine-password.vue | 101 + im-uniapp/pages/mine/mine.vue | 140 + im-uniapp/pages/register/register.vue | 351 + im-uniapp/pnpm-lock.yaml | 373 + im-uniapp/static/emoji/0.gif | Bin 0 -> 17131 bytes im-uniapp/static/emoji/1.gif | Bin 0 -> 14377 bytes im-uniapp/static/emoji/10.gif | Bin 0 -> 17811 bytes im-uniapp/static/emoji/11.gif | Bin 0 -> 21016 bytes im-uniapp/static/emoji/12.gif | Bin 0 -> 13938 bytes im-uniapp/static/emoji/13.gif | Bin 0 -> 18212 bytes im-uniapp/static/emoji/14.gif | Bin 0 -> 12888 bytes im-uniapp/static/emoji/15.gif | Bin 0 -> 5214 bytes im-uniapp/static/emoji/16.gif | Bin 0 -> 17080 bytes im-uniapp/static/emoji/17.gif | Bin 0 -> 3238 bytes im-uniapp/static/emoji/18.gif | Bin 0 -> 19485 bytes im-uniapp/static/emoji/19.gif | Bin 0 -> 13048 bytes im-uniapp/static/emoji/2.gif | Bin 0 -> 18762 bytes im-uniapp/static/emoji/20.gif | Bin 0 -> 19698 bytes im-uniapp/static/emoji/21.gif | Bin 0 -> 19411 bytes im-uniapp/static/emoji/22.gif | Bin 0 -> 21429 bytes im-uniapp/static/emoji/23.gif | Bin 0 -> 10257 bytes im-uniapp/static/emoji/24.gif | Bin 0 -> 12918 bytes im-uniapp/static/emoji/25.gif | Bin 0 -> 19279 bytes im-uniapp/static/emoji/26.gif | Bin 0 -> 15979 bytes im-uniapp/static/emoji/27.gif | Bin 0 -> 12412 bytes im-uniapp/static/emoji/28.gif | Bin 0 -> 16525 bytes im-uniapp/static/emoji/29.gif | Bin 0 -> 10416 bytes im-uniapp/static/emoji/3.gif | Bin 0 -> 20633 bytes im-uniapp/static/emoji/30.gif | Bin 0 -> 19953 bytes im-uniapp/static/emoji/31.gif | Bin 0 -> 8680 bytes im-uniapp/static/emoji/32.gif | Bin 0 -> 11106 bytes im-uniapp/static/emoji/33.gif | Bin 0 -> 9261 bytes im-uniapp/static/emoji/34.gif | Bin 0 -> 9212 bytes im-uniapp/static/emoji/35.gif | Bin 0 -> 8356 bytes im-uniapp/static/emoji/36.gif | Bin 0 -> 4093 bytes im-uniapp/static/emoji/37.gif | Bin 0 -> 17466 bytes im-uniapp/static/emoji/38.gif | Bin 0 -> 11035 bytes im-uniapp/static/emoji/39.gif | Bin 0 -> 10066 bytes im-uniapp/static/emoji/4.gif | Bin 0 -> 6169 bytes im-uniapp/static/emoji/40.gif | Bin 0 -> 18717 bytes im-uniapp/static/emoji/41.gif | Bin 0 -> 5518 bytes im-uniapp/static/emoji/42.gif | Bin 0 -> 17447 bytes im-uniapp/static/emoji/43.gif | Bin 0 -> 9220 bytes im-uniapp/static/emoji/44.gif | Bin 0 -> 7463 bytes im-uniapp/static/emoji/45.gif | Bin 0 -> 14311 bytes im-uniapp/static/emoji/46.gif | Bin 0 -> 13457 bytes im-uniapp/static/emoji/47.gif | Bin 0 -> 13299 bytes im-uniapp/static/emoji/48.gif | Bin 0 -> 13997 bytes im-uniapp/static/emoji/49.gif | Bin 0 -> 10110 bytes im-uniapp/static/emoji/5.gif | Bin 0 -> 20529 bytes im-uniapp/static/emoji/50.gif | Bin 0 -> 12480 bytes im-uniapp/static/emoji/51.gif | Bin 0 -> 12176 bytes im-uniapp/static/emoji/52.gif | Bin 0 -> 16495 bytes im-uniapp/static/emoji/53.gif | Bin 0 -> 13979 bytes im-uniapp/static/emoji/54.gif | Bin 0 -> 10885 bytes im-uniapp/static/emoji/55.gif | Bin 0 -> 7570 bytes im-uniapp/static/emoji/56.gif | Bin 0 -> 8827 bytes im-uniapp/static/emoji/6.gif | Bin 0 -> 16895 bytes im-uniapp/static/emoji/7.gif | Bin 0 -> 20608 bytes im-uniapp/static/emoji/8.gif | Bin 0 -> 8734 bytes im-uniapp/static/emoji/9.gif | Bin 0 -> 21128 bytes im-uniapp/static/icon/iconfont.css | 261 + im-uniapp/static/icon/iconfont.ttf | Bin 0 -> 17428 bytes im-uniapp/static/image/online_app.png | Bin 0 -> 2774 bytes im-uniapp/static/image/online_web.png | Bin 0 -> 3467 bytes im-uniapp/static/tab/chat.png | Bin 0 -> 6356 bytes im-uniapp/static/tab/chat_active.png | Bin 0 -> 5647 bytes im-uniapp/static/tab/friend.png | Bin 0 -> 5721 bytes im-uniapp/static/tab/friend_active.png | Bin 0 -> 5124 bytes im-uniapp/static/tab/group.png | Bin 0 -> 6880 bytes im-uniapp/static/tab/group_active.png | Bin 0 -> 7803 bytes im-uniapp/static/tab/mine.png | Bin 0 -> 6342 bytes im-uniapp/static/tab/mine_active.png | Bin 0 -> 5902 bytes im-uniapp/store/chatStore.js | 546 + im-uniapp/store/configStore.js | 36 + im-uniapp/store/friendStore.js | 96 + im-uniapp/store/groupStore.js | 56 + im-uniapp/store/userStore.js | 31 + im-uniapp/uni.promisify.adaptor.js | 10 + im-uniapp/uni.scss | 107 + im-uniapp/uni_modules/lsj-upload/changelog.md | 120 + .../components/lsj-upload/LsjFile.js | 414 + .../components/lsj-upload/lsj-upload.vue | 338 + .../lsj-upload/hybrid/html/js/vue.min.js | 8 + .../lsj-upload/hybrid/html/uploadFile.html | 213 + im-uniapp/uni_modules/lsj-upload/package.json | 80 + im-uniapp/uni_modules/uni-badge/changelog.md | 33 + .../components/uni-badge/uni-badge.vue | 268 + im-uniapp/uni_modules/uni-badge/package.json | 85 + im-uniapp/uni_modules/uni-badge/readme.md | 10 + .../uni_modules/uni-calendar/changelog.md | 26 + .../components/uni-calendar/calendar.js | 546 + .../components/uni-calendar/i18n/en.json | 12 + .../components/uni-calendar/i18n/index.js | 8 + .../components/uni-calendar/i18n/zh-Hans.json | 12 + .../components/uni-calendar/i18n/zh-Hant.json | 12 + .../uni-calendar/uni-calendar-item.vue | 187 + .../components/uni-calendar/uni-calendar.vue | 566 + .../components/uni-calendar/util.js | 360 + .../uni_modules/uni-calendar/package.json | 85 + im-uniapp/uni_modules/uni-calendar/readme.md | 103 + im-uniapp/uni_modules/uni-card/changelog.md | 26 + .../uni-card/components/uni-card/uni-card.vue | 270 + im-uniapp/uni_modules/uni-card/package.json | 90 + im-uniapp/uni_modules/uni-card/readme.md | 12 + .../uni_modules/uni-collapse/changelog.md | 36 + .../uni-collapse-item/uni-collapse-item.vue | 402 + .../components/uni-collapse/uni-collapse.vue | 147 + .../uni_modules/uni-collapse/package.json | 89 + im-uniapp/uni_modules/uni-collapse/readme.md | 12 + im-uniapp/uni_modules/uni-combox/changelog.md | 15 + .../components/uni-combox/uni-combox.vue | 275 + im-uniapp/uni_modules/uni-combox/package.json | 90 + im-uniapp/uni_modules/uni-combox/readme.md | 11 + .../uni_modules/uni-countdown/changelog.md | 24 + .../components/uni-countdown/i18n/en.json | 6 + .../components/uni-countdown/i18n/index.js | 8 + .../uni-countdown/i18n/zh-Hans.json | 6 + .../uni-countdown/i18n/zh-Hant.json | 6 + .../uni-countdown/uni-countdown.vue | 271 + .../uni_modules/uni-countdown/package.json | 86 + im-uniapp/uni_modules/uni-countdown/readme.md | 10 + .../uni-data-checkbox/changelog.md | 45 + .../uni-data-checkbox/uni-data-checkbox.vue | 821 + .../uni-data-checkbox/package.json | 84 + .../uni_modules/uni-data-checkbox/readme.md | 18 + .../uni_modules/uni-data-picker/changelog.md | 75 + .../components/uni-data-picker/keypress.js | 45 + .../uni-data-picker/uni-data-picker.vue | 551 + .../uni-data-pickerview/uni-data-picker.js | 622 + .../uni-data-pickerview.vue | 323 + .../uni_modules/uni-data-picker/package.json | 90 + .../uni_modules/uni-data-picker/readme.md | 22 + .../uni_modules/uni-data-select/changelog.md | 35 + .../uni-data-select/uni-data-select.vue | 517 + .../uni_modules/uni-data-select/package.json | 85 + .../uni_modules/uni-data-select/readme.md | 8 + .../uni_modules/uni-dateformat/changelog.md | 10 + .../components/uni-dateformat/date-format.js | 200 + .../uni-dateformat/uni-dateformat.vue | 88 + .../uni_modules/uni-dateformat/package.json | 88 + .../uni_modules/uni-dateformat/readme.md | 11 + .../uni-datetime-picker/changelog.md | 140 + .../uni-datetime-picker/calendar-item.vue | 177 + .../uni-datetime-picker/calendar.vue | 929 + .../uni-datetime-picker/i18n/en.json | 22 + .../uni-datetime-picker/i18n/index.js | 8 + .../uni-datetime-picker/i18n/zh-Hans.json | 22 + .../uni-datetime-picker/i18n/zh-Hant.json | 22 + .../uni-datetime-picker/time-picker.vue | 934 + .../uni-datetime-picker.vue | 1032 + .../components/uni-datetime-picker/util.js | 453 + .../uni-datetime-picker/package.json | 87 + .../uni_modules/uni-datetime-picker/readme.md | 21 + im-uniapp/uni_modules/uni-drawer/changelog.md | 13 + .../components/uni-drawer/keypress.js | 45 + .../components/uni-drawer/uni-drawer.vue | 183 + im-uniapp/uni_modules/uni-drawer/package.json | 87 + im-uniapp/uni_modules/uni-drawer/readme.md | 10 + .../uni_modules/uni-easyinput/changelog.md | 97 + .../components/uni-easyinput/common.js | 56 + .../uni-easyinput/uni-easyinput.vue | 657 + .../uni_modules/uni-easyinput/package.json | 87 + im-uniapp/uni_modules/uni-easyinput/readme.md | 11 + im-uniapp/uni_modules/uni-fab/changelog.md | 23 + .../uni-fab/components/uni-fab/uni-fab.vue | 491 + im-uniapp/uni_modules/uni-fab/package.json | 84 + im-uniapp/uni_modules/uni-fab/readme.md | 9 + im-uniapp/uni_modules/uni-fav/changelog.md | 19 + .../uni-fav/components/uni-fav/i18n/en.json | 4 + .../uni-fav/components/uni-fav/i18n/index.js | 8 + .../components/uni-fav/i18n/zh-Hans.json | 4 + .../components/uni-fav/i18n/zh-Hant.json | 4 + .../uni-fav/components/uni-fav/uni-fav.vue | 161 + im-uniapp/uni_modules/uni-fav/package.json | 89 + im-uniapp/uni_modules/uni-fav/readme.md | 10 + .../uni_modules/uni-file-picker/changelog.md | 67 + .../uni-file-picker/choose-and-upload-file.js | 224 + .../uni-file-picker/uni-file-picker.vue | 667 + .../uni-file-picker/upload-file.vue | 325 + .../uni-file-picker/upload-image.vue | 292 + .../components/uni-file-picker/utils.js | 109 + .../uni_modules/uni-file-picker/package.json | 83 + .../uni_modules/uni-file-picker/readme.md | 11 + im-uniapp/uni_modules/uni-forms/changelog.md | 92 + .../uni-forms-item/uni-forms-item.vue | 627 + .../components/uni-forms/uni-forms.vue | 397 + .../uni-forms/components/uni-forms/utils.js | 293 + .../components/uni-forms/validate.js | 486 + im-uniapp/uni_modules/uni-forms/package.json | 88 + im-uniapp/uni_modules/uni-forms/readme.md | 23 + .../uni_modules/uni-goods-nav/changelog.md | 18 + .../components/uni-goods-nav/i18n/en.json | 6 + .../components/uni-goods-nav/i18n/index.js | 8 + .../uni-goods-nav/i18n/zh-Hans.json | 6 + .../uni-goods-nav/i18n/zh-Hant.json | 6 + .../uni-goods-nav/uni-goods-nav.vue | 229 + .../uni_modules/uni-goods-nav/package.json | 88 + im-uniapp/uni_modules/uni-goods-nav/readme.md | 10 + im-uniapp/uni_modules/uni-grid/changelog.md | 13 + .../uni-grid-item/uni-grid-item.vue | 127 + .../uni-grid/components/uni-grid/uni-grid.vue | 142 + im-uniapp/uni_modules/uni-grid/package.json | 86 + im-uniapp/uni_modules/uni-grid/readme.md | 11 + im-uniapp/uni_modules/uni-group/changelog.md | 16 + .../components/uni-group/uni-group.vue | 134 + im-uniapp/uni_modules/uni-group/package.json | 87 + im-uniapp/uni_modules/uni-group/readme.md | 9 + im-uniapp/uni_modules/uni-icons/changelog.md | 22 + .../uni-icons/components/uni-icons/icons.js | 1169 + .../components/uni-icons/uni-icons.vue | 96 + .../components/uni-icons/uniicons.css | 663 + .../components/uni-icons/uniicons.ttf | Bin 0 -> 35760 bytes im-uniapp/uni_modules/uni-icons/package.json | 86 + im-uniapp/uni_modules/uni-icons/readme.md | 8 + .../uni_modules/uni-indexed-list/changelog.md | 17 + .../uni-indexed-list-item.vue | 144 + .../uni-indexed-list/uni-indexed-list.vue | 367 + .../uni_modules/uni-indexed-list/package.json | 89 + .../uni_modules/uni-indexed-list/readme.md | 11 + im-uniapp/uni_modules/uni-link/changelog.md | 17 + .../uni-link/components/uni-link/uni-link.vue | 128 + im-uniapp/uni_modules/uni-link/package.json | 87 + im-uniapp/uni_modules/uni-link/readme.md | 11 + im-uniapp/uni_modules/uni-list/changelog.md | 46 + .../components/uni-list-ad/uni-list-ad.vue | 107 + .../uni-list-chat/uni-list-chat.scss | 58 + .../uni-list-chat/uni-list-chat.vue | 593 + .../uni-list-item/uni-list-item.vue | 534 + .../uni-list/components/uni-list/uni-list.vue | 123 + .../components/uni-list/uni-refresh.vue | 65 + .../components/uni-list/uni-refresh.wxs | 87 + im-uniapp/uni_modules/uni-list/package.json | 88 + im-uniapp/uni_modules/uni-list/readme.md | 346 + .../uni_modules/uni-load-more/changelog.md | 19 + .../components/uni-load-more/i18n/en.json | 5 + .../components/uni-load-more/i18n/index.js | 8 + .../uni-load-more/i18n/zh-Hans.json | 5 + .../uni-load-more/i18n/zh-Hant.json | 5 + .../uni-load-more/uni-load-more.vue | 399 + .../uni_modules/uni-load-more/package.json | 86 + im-uniapp/uni_modules/uni-load-more/readme.md | 14 + .../uni_modules/uni-nav-bar/changelog.md | 51 + .../components/uni-nav-bar/uni-nav-bar.vue | 357 + .../components/uni-nav-bar/uni-status-bar.vue | 24 + .../uni_modules/uni-nav-bar/package.json | 86 + im-uniapp/uni_modules/uni-nav-bar/readme.md | 15 + .../uni_modules/uni-notice-bar/changelog.md | 18 + .../uni-notice-bar/uni-notice-bar.vue | 426 + .../uni_modules/uni-notice-bar/package.json | 87 + .../uni_modules/uni-notice-bar/readme.md | 13 + .../uni_modules/uni-number-box/changelog.md | 29 + .../uni-number-box/uni-number-box.vue | 221 + .../uni_modules/uni-number-box/package.json | 82 + .../uni_modules/uni-number-box/readme.md | 13 + .../uni_modules/uni-pagination/changelog.md | 27 + .../components/uni-pagination/i18n/en.json | 5 + .../components/uni-pagination/i18n/es.json | 5 + .../components/uni-pagination/i18n/fr.json | 5 + .../components/uni-pagination/i18n/index.js | 12 + .../uni-pagination/i18n/zh-Hans.json | 5 + .../uni-pagination/i18n/zh-Hant.json | 5 + .../uni-pagination/uni-pagination.vue | 465 + .../uni_modules/uni-pagination/package.json | 83 + .../uni_modules/uni-pagination/readme.md | 11 + im-uniapp/uni_modules/uni-popup/changelog.md | 68 + .../components/uni-popup-dialog/keypress.js | 45 + .../uni-popup-dialog/uni-popup-dialog.vue | 275 + .../uni-popup-message/uni-popup-message.vue | 143 + .../uni-popup-share/uni-popup-share.vue | 187 + .../components/uni-popup/i18n/en.json | 7 + .../components/uni-popup/i18n/index.js | 8 + .../components/uni-popup/i18n/zh-Hans.json | 7 + .../components/uni-popup/i18n/zh-Hant.json | 7 + .../components/uni-popup/keypress.js | 45 + .../uni-popup/components/uni-popup/popup.js | 26 + .../components/uni-popup/uni-popup.vue | 473 + im-uniapp/uni_modules/uni-popup/package.json | 87 + im-uniapp/uni_modules/uni-popup/readme.md | 17 + im-uniapp/uni_modules/uni-rate/changelog.md | 25 + .../uni-rate/components/uni-rate/uni-rate.vue | 361 + im-uniapp/uni_modules/uni-rate/package.json | 88 + im-uniapp/uni_modules/uni-rate/readme.md | 12 + im-uniapp/uni_modules/uni-row/changelog.md | 10 + .../uni-row/components/uni-col/uni-col.vue | 317 + .../uni-row/components/uni-row/uni-row.vue | 190 + im-uniapp/uni_modules/uni-row/package.json | 87 + im-uniapp/uni_modules/uni-row/readme.md | 10 + im-uniapp/uni_modules/uni-scss/changelog.md | 8 + im-uniapp/uni_modules/uni-scss/index.scss | 1 + im-uniapp/uni_modules/uni-scss/package.json | 82 + im-uniapp/uni_modules/uni-scss/readme.md | 4 + .../uni_modules/uni-scss/styles/index.scss | 7 + .../uni-scss/styles/setting/_border.scss | 3 + .../uni-scss/styles/setting/_color.scss | 66 + .../uni-scss/styles/setting/_radius.scss | 55 + .../uni-scss/styles/setting/_space.scss | 56 + .../uni-scss/styles/setting/_styles.scss | 167 + .../uni-scss/styles/setting/_text.scss | 24 + .../uni-scss/styles/setting/_variables.scss | 146 + .../uni-scss/styles/tools/functions.scss | 19 + im-uniapp/uni_modules/uni-scss/theme.scss | 31 + im-uniapp/uni_modules/uni-scss/variables.scss | 62 + .../uni_modules/uni-search-bar/changelog.md | 35 + .../components/uni-search-bar/i18n/en.json | 4 + .../components/uni-search-bar/i18n/index.js | 8 + .../uni-search-bar/i18n/zh-Hans.json | 4 + .../uni-search-bar/i18n/zh-Hant.json | 4 + .../uni-search-bar/uni-search-bar.vue | 298 + .../uni_modules/uni-search-bar/package.json | 86 + .../uni_modules/uni-search-bar/readme.md | 14 + .../uni_modules/uni-section/changelog.md | 2 + .../components/uni-section/uni-section.vue | 167 + .../uni_modules/uni-section/package.json | 87 + im-uniapp/uni_modules/uni-section/readme.md | 8 + .../uni-segmented-control/changelog.md | 9 + .../uni-segmented-control.vue | 145 + .../uni-segmented-control/package.json | 87 + .../uni-segmented-control/readme.md | 13 + im-uniapp/uni_modules/uni-steps/changelog.md | 16 + .../components/uni-steps/uni-steps.vue | 269 + im-uniapp/uni_modules/uni-steps/package.json | 89 + im-uniapp/uni_modules/uni-steps/readme.md | 13 + .../uni_modules/uni-swipe-action/changelog.md | 43 + .../uni-swipe-action-item/bindingx.js | 302 + .../components/uni-swipe-action-item/isPC.js | 12 + .../uni-swipe-action-item/mpalipay.js | 195 + .../uni-swipe-action-item/mpother.js | 260 + .../components/uni-swipe-action-item/mpwxs.js | 84 + .../uni-swipe-action-item/render.js | 270 + .../uni-swipe-action-item.vue | 347 + .../components/uni-swipe-action-item/wx.wxs | 341 + .../uni-swipe-action/uni-swipe-action.vue | 60 + .../uni_modules/uni-swipe-action/package.json | 84 + .../uni_modules/uni-swipe-action/readme.md | 11 + .../uni_modules/uni-swiper-dot/changelog.md | 12 + .../uni-swiper-dot/uni-swiper-dot.vue | 218 + .../uni_modules/uni-swiper-dot/package.json | 87 + .../uni_modules/uni-swiper-dot/readme.md | 11 + im-uniapp/uni_modules/uni-table/changelog.md | 27 + .../components/uni-table/uni-table.vue | 455 + .../components/uni-tbody/uni-tbody.vue | 29 + .../uni-table/components/uni-td/uni-td.vue | 90 + .../components/uni-th/filter-dropdown.vue | 511 + .../uni-table/components/uni-th/uni-th.vue | 285 + .../components/uni-thead/uni-thead.vue | 129 + .../components/uni-tr/table-checkbox.vue | 179 + .../uni-table/components/uni-tr/uni-tr.vue | 171 + im-uniapp/uni_modules/uni-table/i18n/en.json | 9 + im-uniapp/uni_modules/uni-table/i18n/es.json | 9 + im-uniapp/uni_modules/uni-table/i18n/fr.json | 9 + im-uniapp/uni_modules/uni-table/i18n/index.js | 12 + .../uni_modules/uni-table/i18n/zh-Hans.json | 9 + .../uni_modules/uni-table/i18n/zh-Hant.json | 9 + im-uniapp/uni_modules/uni-table/package.json | 83 + im-uniapp/uni_modules/uni-table/readme.md | 13 + im-uniapp/uni_modules/uni-tag/changelog.md | 21 + .../uni-tag/components/uni-tag/uni-tag.vue | 252 + im-uniapp/uni_modules/uni-tag/package.json | 87 + im-uniapp/uni_modules/uni-tag/readme.md | 13 + im-uniapp/uni_modules/uni-title/changelog.md | 10 + .../components/uni-title/uni-title.vue | 171 + im-uniapp/uni_modules/uni-title/package.json | 88 + im-uniapp/uni_modules/uni-title/readme.md | 14 + .../uni_modules/uni-tooltip/changelog.md | 10 + .../components/uni-tooltip/uni-tooltip.vue | 68 + .../uni_modules/uni-tooltip/package.json | 83 + im-uniapp/uni_modules/uni-tooltip/readme.md | 8 + .../uni_modules/uni-transition/changelog.md | 22 + .../uni-transition/createAnimation.js | 131 + .../uni-transition/uni-transition.vue | 286 + .../uni_modules/uni-transition/package.json | 84 + .../uni_modules/uni-transition/readme.md | 11 + im-uniapp/uni_modules/uni-ui/changelog.md | 541 + .../uni-ui/components/uni-ui/uni-ui.vue | 7 + im-uniapp/uni_modules/uni-ui/package.json | 129 + im-uniapp/uni_modules/uni-ui/readme.md | 247 + im-uniapp/uni_modules/uview-plus/LICENSE | 21 + im-uniapp/uni_modules/uview-plus/README.md | 64 + im-uniapp/uni_modules/uview-plus/changelog.md | 242 + .../uview-plus/components/u--form/u--form.vue | 80 + .../components/u--image/u--image.vue | 50 + .../components/u--input/u--input.vue | 74 + .../uview-plus/components/u--text/u--text.vue | 45 + .../components/u--textarea/u--textarea.vue | 47 + .../components/u-action-sheet/props.js | 57 + .../u-action-sheet/u-action-sheet.vue | 281 + .../uview-plus/components/u-album/props.js | 61 + .../uview-plus/components/u-album/u-album.vue | 264 + .../uview-plus/components/u-alert/props.js | 46 + .../uview-plus/components/u-alert/u-alert.vue | 249 + .../components/u-avatar-group/props.js | 54 + .../u-avatar-group/u-avatar-group.vue | 110 + .../uview-plus/components/u-avatar/props.js | 81 + .../components/u-avatar/u-avatar.vue | 180 + .../uview-plus/components/u-back-top/props.js | 56 + .../components/u-back-top/u-back-top.vue | 133 + .../uview-plus/components/u-badge/props.js | 79 + .../uview-plus/components/u-badge/u-badge.vue | 177 + .../uview-plus/components/u-button/nvue.scss | 46 + .../uview-plus/components/u-button/props.js | 154 + .../components/u-button/u-button.vue | 503 + .../uview-plus/components/u-button/vue.scss | 81 + .../components/u-calendar/header.vue | 101 + .../components/u-calendar/month.vue | 585 + .../uview-plus/components/u-calendar/props.js | 147 + .../components/u-calendar/u-calendar.vue | 409 + .../uview-plus/components/u-calendar/util.js | 86 + .../components/u-car-keyboard/props.js | 17 + .../u-car-keyboard/u-car-keyboard.vue | 315 + .../components/u-cell-group/props.js | 16 + .../components/u-cell-group/u-cell-group.vue | 67 + .../uview-plus/components/u-cell/props.js | 112 + .../uview-plus/components/u-cell/u-cell.vue | 249 + .../components/u-checkbox-group/props.js | 93 + .../u-checkbox-group/u-checkbox-group.vue | 133 + .../uview-plus/components/u-checkbox/props.js | 76 + .../components/u-checkbox/u-checkbox.vue | 376 + .../components/u-circle-progress/props.js | 10 + .../u-circle-progress/u-circle-progress.vue | 201 + .../components/u-code-input/props.js | 90 + .../components/u-code-input/u-code-input.vue | 266 + .../uview-plus/components/u-code/props.js | 36 + .../uview-plus/components/u-code/u-code.vue | 137 + .../uview-plus/components/u-col/props.js | 31 + .../uview-plus/components/u-col/u-col.vue | 170 + .../components/u-collapse-item/props.js | 61 + .../u-collapse-item/u-collapse-item.vue | 240 + .../uview-plus/components/u-collapse/props.js | 21 + .../components/u-collapse/u-collapse.vue | 91 + .../components/u-column-notice/props.js | 57 + .../u-column-notice/u-column-notice.vue | 165 + .../uview-plus/components/u-copy/u-copy.vue | 70 + .../components/u-count-down/props.js | 26 + .../components/u-count-down/u-count-down.vue | 171 + .../components/u-count-down/utils.js | 62 + .../uview-plus/components/u-count-to/props.js | 61 + .../components/u-count-to/u-count-to.vue | 189 + .../components/u-datetime-picker/props.js | 145 + .../u-datetime-picker/u-datetime-picker.vue | 455 + .../uview-plus/components/u-divider/props.js | 46 + .../components/u-divider/u-divider.vue | 121 + .../components/u-dropdown-item/props.js | 47 + .../u-dropdown-item/u-dropdown-item.vue | 121 + .../uview-plus/components/u-dropdown/props.js | 61 + .../components/u-dropdown/u-dropdown.vue | 254 + .../uview-plus/components/u-empty/props.js | 61 + .../uview-plus/components/u-empty/u-empty.vue | 133 + .../components/u-form-item/props.js | 55 + .../components/u-form-item/u-form-item.vue | 245 + .../uview-plus/components/u-form/props.js | 47 + .../uview-plus/components/u-form/u-form.vue | 218 + .../uview-plus/components/u-gap/props.js | 26 + .../uview-plus/components/u-gap/u-gap.vue | 41 + .../components/u-grid-item/props.js | 16 + .../components/u-grid-item/u-grid-item.vue | 232 + .../uview-plus/components/u-grid/props.js | 21 + .../uview-plus/components/u-grid/u-grid.vue | 113 + .../uview-plus/components/u-icon/icons.js | 214 + .../uview-plus/components/u-icon/props.js | 91 + .../uview-plus/components/u-icon/u-icon.vue | 242 + .../uview-plus/components/u-image/props.js | 86 + .../uview-plus/components/u-image/u-image.vue | 237 + .../components/u-index-anchor/props.js | 31 + .../u-index-anchor/u-index-anchor.vue | 95 + .../components/u-index-item/props.js | 8 + .../components/u-index-item/u-index-item.vue | 90 + .../components/u-index-list/props.js | 31 + .../components/u-index-list/u-index-list.vue | 447 + .../uview-plus/components/u-input/props.js | 198 + .../uview-plus/components/u-input/u-input.vue | 386 + .../uview-plus/components/u-keyboard/props.js | 86 + .../components/u-keyboard/u-keyboard.vue | 167 + .../components/u-lazy-load/u-lazy-load.vue | 251 + .../components/u-line-progress/props.js | 30 + .../u-line-progress/u-line-progress.vue | 149 + .../uview-plus/components/u-line/props.js | 35 + .../uview-plus/components/u-line/u-line.vue | 65 + .../uview-plus/components/u-link/props.js | 41 + .../uview-plus/components/u-link/u-link.vue | 87 + .../components/u-list-item/props.js | 11 + .../components/u-list-item/u-list-item.vue | 119 + .../uview-plus/components/u-list/props.js | 101 + .../uview-plus/components/u-list/u-list.vue | 183 + .../components/u-loading-icon/props.js | 61 + .../u-loading-icon/u-loading-icon.vue | 347 + .../components/u-loading-page/props.js | 52 + .../u-loading-page/u-loading-page.vue | 120 + .../uview-plus/components/u-loadmore/props.js | 96 + .../components/u-loadmore/u-loadmore.vue | 155 + .../u-message-input/u-message-input.vue | 318 + .../uview-plus/components/u-modal/props.js | 86 + .../uview-plus/components/u-modal/u-modal.vue | 234 + .../uview-plus/components/u-navbar/props.js | 87 + .../components/u-navbar/u-navbar.vue | 193 + .../components/u-no-network/props.js | 21 + .../components/u-no-network/u-no-network.vue | 223 + .../components/u-notice-bar/props.js | 72 + .../components/u-notice-bar/u-notice-bar.vue | 105 + .../uview-plus/components/u-notify/props.js | 51 + .../components/u-notify/u-notify.vue | 222 + .../components/u-number-box/props.js | 120 + .../components/u-number-box/u-number-box.vue | 465 + .../components/u-number-keyboard/props.js | 21 + .../u-number-keyboard/u-number-keyboard.vue | 198 + .../uview-plus/components/u-overlay/props.js | 26 + .../components/u-overlay/u-overlay.vue | 71 + .../components/u-parse/node/node.vue | 576 + .../uview-plus/components/u-parse/parser.js | 1333 + .../uview-plus/components/u-parse/props.js | 48 + .../uview-plus/components/u-parse/u-parse.vue | 505 + .../components/u-picker-column/props.js | 7 + .../u-picker-column/u-picker-column.vue | 29 + .../uview-plus/components/u-picker/props.js | 98 + .../components/u-picker/u-picker.vue | 336 + .../uview-plus/components/u-popup/props.js | 81 + .../uview-plus/components/u-popup/u-popup.vue | 308 + .../uview-plus/components/u-qrcode/qrcode.js | 1206 + .../components/u-qrcode/u-qrcode.vue | 199 + .../components/u-radio-group/props.js | 95 + .../u-radio-group/u-radio-group.vue | 128 + .../uview-plus/components/u-radio/props.js | 71 + .../uview-plus/components/u-radio/u-radio.vue | 348 + .../uview-plus/components/u-rate/props.js | 80 + .../uview-plus/components/u-rate/u-rate.vue | 328 + .../components/u-read-more/props.js | 63 + .../components/u-read-more/u-read-more.vue | 167 + .../components/u-row-notice/props.js | 41 + .../components/u-row-notice/u-row-notice.vue | 344 + .../uview-plus/components/u-row/props.js | 21 + .../uview-plus/components/u-row/u-row.vue | 97 + .../components/u-safe-bottom/props.js | 7 + .../u-safe-bottom/u-safe-bottom.vue | 59 + .../components/u-scroll-list/nvue.js | 31 + .../components/u-scroll-list/other.js | 0 .../components/u-scroll-list/props.js | 36 + .../components/u-scroll-list/scrollWxs.wxs | 50 + .../u-scroll-list/u-scroll-list.vue | 232 + .../uview-plus/components/u-search/props.js | 134 + .../components/u-search/u-search.vue | 331 + .../uview-plus/components/u-skeleton/props.js | 61 + .../components/u-skeleton/u-skeleton.vue | 249 + .../uview-plus/components/u-slider/mpother.js | 113 + .../uview-plus/components/u-slider/mpwxs.js | 43 + .../uview-plus/components/u-slider/mpwxs.wxs | 121 + .../components/u-slider/nvue - 副本.js | 180 + .../uview-plus/components/u-slider/nvue.js | 193 + .../uview-plus/components/u-slider/props.js | 65 + .../components/u-slider/u-slider.vue | 71 + .../components/u-status-bar/props.js | 10 + .../components/u-status-bar/u-status-bar.vue | 49 + .../components/u-steps-item/props.js | 26 + .../components/u-steps-item/u-steps-item.vue | 320 + .../uview-plus/components/u-steps/props.js | 41 + .../uview-plus/components/u-steps/u-steps.vue | 83 + .../uview-plus/components/u-sticky/props.js | 42 + .../components/u-sticky/u-sticky.vue | 221 + .../components/u-subsection/props.js | 51 + .../components/u-subsection/u-subsection.vue | 318 + .../components/u-swipe-action-item/alipay.sjs | 229 + .../u-swipe-action-item/index - backup.wxs | 256 + .../components/u-swipe-action-item/index.wxs | 229 + .../u-swipe-action-item/nvue - backup.js | 270 + .../components/u-swipe-action-item/nvue.js | 179 + .../components/u-swipe-action-item/other.js | 171 + .../components/u-swipe-action-item/props.js | 43 + .../u-swipe-action-item.vue | 229 + .../components/u-swipe-action-item/wxs.js | 15 + .../components/u-swipe-action/props.js | 11 + .../u-swipe-action/u-swipe-action.vue | 69 + .../components/u-swiper-indicator/props.js | 31 + .../u-swiper-indicator/u-swiper-indicator.vue | 115 + .../uview-plus/components/u-swiper/props.js | 127 + .../components/u-swiper/u-swiper.vue | 267 + .../uview-plus/components/u-switch/props.js | 64 + .../components/u-switch/u-switch.vue | 216 + .../components/u-tabbar-item/props.js | 37 + .../u-tabbar-item/u-tabbar-item.vue | 156 + .../uview-plus/components/u-tabbar/props.js | 47 + .../components/u-tabbar/u-tabbar.vue | 145 + .../uview-plus/components/u-table/props.js | 7 + .../uview-plus/components/u-table/u-table.vue | 31 + .../components/u-tabs-item/props.js | 7 + .../components/u-tabs-item/u-tabs-item.vue | 31 + .../uview-plus/components/u-tabs/props.js | 66 + .../uview-plus/components/u-tabs/u-tabs.vue | 362 + .../uview-plus/components/u-tag/props.js | 90 + .../uview-plus/components/u-tag/u-tag.vue | 368 + .../uview-plus/components/u-td/props.js | 7 + .../uview-plus/components/u-td/u-td.vue | 33 + .../uview-plus/components/u-text/props.js | 112 + .../uview-plus/components/u-text/u-text.vue | 228 + .../uview-plus/components/u-text/value.js | 87 + .../uview-plus/components/u-textarea/props.js | 127 + .../components/u-textarea/u-textarea.vue | 278 + .../uview-plus/components/u-toast/u-toast.vue | 301 + .../uview-plus/components/u-toolbar/props.js | 36 + .../components/u-toolbar/u-toolbar.vue | 105 + .../uview-plus/components/u-tooltip/props.js | 61 + .../components/u-tooltip/u-tooltip.vue | 342 + .../uview-plus/components/u-tr/props.js | 7 + .../uview-plus/components/u-tr/u-tr.vue | 33 + .../components/u-transition/nvue-ani-map.js | 68 + .../components/u-transition/props.js | 26 + .../components/u-transition/transition.js | 163 + .../components/u-transition/u-transition.vue | 96 + .../u-transition/vue.ani-style.scss | 113 + .../uview-plus/components/u-upload/mixin.js | 23 + .../uview-plus/components/u-upload/props.js | 130 + .../components/u-upload/u-upload.vue | 581 + .../uview-plus/components/u-upload/utils.js | 160 + .../components/u-waterfall/u-waterfall.vue | 225 + .../components/uview-plus/uview-plus.vue | 15 + im-uniapp/uni_modules/uview-plus/index.js | 94 + im-uniapp/uni_modules/uview-plus/index.scss | 24 + .../uview-plus/libs/config/color.js | 17 + .../uview-plus/libs/config/config.js | 33 + .../uview-plus/libs/config/props.js | 190 + .../libs/config/props/actionSheet.js | 25 + .../uview-plus/libs/config/props/album.js | 25 + .../uview-plus/libs/config/props/alert.js | 22 + .../uview-plus/libs/config/props/avatar.js | 28 + .../libs/config/props/avatarGroup.js | 23 + .../uview-plus/libs/config/props/backtop.js | 27 + .../uview-plus/libs/config/props/badge.js | 27 + .../uview-plus/libs/config/props/button.js | 42 + .../uview-plus/libs/config/props/calendar.js | 42 + .../libs/config/props/carKeyboard.js | 15 + .../uview-plus/libs/config/props/cell.js | 35 + .../uview-plus/libs/config/props/cellGroup.js | 17 + .../uview-plus/libs/config/props/checkbox.js | 27 + .../libs/config/props/checkboxGroup.js | 29 + .../libs/config/props/circleProgress.js | 15 + .../uview-plus/libs/config/props/code.js | 21 + .../uview-plus/libs/config/props/codeInput.js | 29 + .../uview-plus/libs/config/props/col.js | 19 + .../uview-plus/libs/config/props/collapse.js | 17 + .../libs/config/props/collapseItem.js | 25 + .../libs/config/props/columnNotice.js | 24 + .../uview-plus/libs/config/props/countDown.js | 18 + .../uview-plus/libs/config/props/countTo.js | 25 + .../libs/config/props/datetimePicker.js | 37 + .../uview-plus/libs/config/props/divider.js | 23 + .../uview-plus/libs/config/props/empty.js | 26 + .../uview-plus/libs/config/props/form.js | 22 + .../uview-plus/libs/config/props/formItem.js | 24 + .../uview-plus/libs/config/props/gap.js | 19 + .../uview-plus/libs/config/props/grid.js | 17 + .../uview-plus/libs/config/props/gridItem.js | 16 + .../uview-plus/libs/config/props/icon.js | 36 + .../uview-plus/libs/config/props/image.js | 30 + .../libs/config/props/indexAnchor.js | 19 + .../uview-plus/libs/config/props/indexList.js | 19 + .../uview-plus/libs/config/props/input.js | 48 + .../uview-plus/libs/config/props/keyboard.js | 30 + .../uview-plus/libs/config/props/line.js | 20 + .../libs/config/props/lineProgress.js | 19 + .../uview-plus/libs/config/props/link.js | 26 + .../uview-plus/libs/config/props/list.js | 28 + .../uview-plus/libs/config/props/listItem.js | 15 + .../libs/config/props/loadingIcon.js | 30 + .../libs/config/props/loadingPage.js | 23 + .../uview-plus/libs/config/props/loadmore.js | 32 + .../uview-plus/libs/config/props/modal.js | 30 + .../uview-plus/libs/config/props/navbar.js | 32 + .../uview-plus/libs/config/props/noNetwork.js | 18 + .../uview-plus/libs/config/props/noticeBar.js | 27 + .../uview-plus/libs/config/props/notify.js | 22 + .../uview-plus/libs/config/props/numberBox.js | 35 + .../libs/config/props/numberKeyboard.js | 17 + .../uview-plus/libs/config/props/overlay.js | 18 + .../uview-plus/libs/config/props/parse.js | 22 + .../uview-plus/libs/config/props/picker.js | 30 + .../uview-plus/libs/config/props/popup.js | 29 + .../uview-plus/libs/config/props/radio.js | 27 + .../libs/config/props/radioGroup.js | 30 + .../uview-plus/libs/config/props/rate.js | 26 + .../uview-plus/libs/config/props/readMore.js | 22 + .../uview-plus/libs/config/props/row.js | 17 + .../uview-plus/libs/config/props/rowNotice.js | 21 + .../libs/config/props/scrollList.js | 20 + .../uview-plus/libs/config/props/search.js | 37 + .../uview-plus/libs/config/props/section.js | 24 + .../uview-plus/libs/config/props/skeleton.js | 25 + .../uview-plus/libs/config/props/slider.js | 25 + .../uview-plus/libs/config/props/statusBar.js | 15 + .../uview-plus/libs/config/props/steps.js | 21 + .../uview-plus/libs/config/props/stepsItem.js | 18 + .../uview-plus/libs/config/props/sticky.js | 20 + .../libs/config/props/subsection.js | 23 + .../libs/config/props/swipeAction.js | 15 + .../libs/config/props/swipeActionItem.js | 21 + .../uview-plus/libs/config/props/swiper.js | 39 + .../libs/config/props/swipterIndicator.js | 19 + .../uview-plus/libs/config/props/switch.js | 24 + .../uview-plus/libs/config/props/tabbar.js | 22 + .../libs/config/props/tabbarItem.js | 20 + .../uview-plus/libs/config/props/tabs.js | 32 + .../uview-plus/libs/config/props/tag.js | 30 + .../uview-plus/libs/config/props/text.js | 38 + .../uview-plus/libs/config/props/textarea.js | 36 + .../uview-plus/libs/config/props/toast.js | 30 + .../uview-plus/libs/config/props/toolbar.js | 21 + .../uview-plus/libs/config/props/tooltip.js | 25 + .../libs/config/props/transition.js | 18 + .../uview-plus/libs/config/props/upload.js | 37 + .../uview-plus/libs/config/zIndex.js | 20 + .../uview-plus/libs/css/color.scss | 155 + .../uview-plus/libs/css/common.scss | 102 + .../uview-plus/libs/css/components.scss | 25 + .../uni_modules/uview-plus/libs/css/flex.scss | 299 + .../uni_modules/uview-plus/libs/css/h5.scss | 0 .../uview-plus/libs/css/mixin.scss | 8 + .../uni_modules/uview-plus/libs/css/mp.scss | 0 .../uni_modules/uview-plus/libs/css/nvue.scss | 0 .../uni_modules/uview-plus/libs/css/vue.scss | 27 + .../uview-plus/libs/function/colorGradient.js | 134 + .../uview-plus/libs/function/debounce.js | 29 + .../uview-plus/libs/function/digit.js | 167 + .../uview-plus/libs/function/index.js | 724 + .../uview-plus/libs/function/platform.js | 75 + .../uview-plus/libs/function/test.js | 288 + .../uview-plus/libs/function/throttle.js | 30 + .../libs/luch-request/adapters/index.js | 97 + .../luch-request/core/InterceptorManager.js | 50 + .../libs/luch-request/core/Request.js | 198 + .../libs/luch-request/core/buildFullPath.js | 20 + .../libs/luch-request/core/defaults.js | 29 + .../libs/luch-request/core/dispatchRequest.js | 3 + .../libs/luch-request/core/mergeConfig.js | 103 + .../libs/luch-request/core/settle.js | 16 + .../libs/luch-request/helpers/buildURL.js | 69 + .../libs/luch-request/helpers/combineURLs.js | 14 + .../luch-request/helpers/isAbsoluteURL.js | 14 + .../uview-plus/libs/luch-request/index.d.ts | 116 + .../uview-plus/libs/luch-request/index.js | 3 + .../uview-plus/libs/luch-request/utils.js | 131 + .../libs/luch-request/utils/clone.js | 264 + .../uview-plus/libs/mixin/button.js | 18 + .../uview-plus/libs/mixin/mixin.js | 176 + .../uview-plus/libs/mixin/mpMixin.js | 13 + .../uview-plus/libs/mixin/mpShare.js | 26 + .../uview-plus/libs/mixin/openType.js | 27 + .../uview-plus/libs/mixin/style.js | 249 + .../uview-plus/libs/mixin/touch.js | 61 + .../uview-plus/libs/util/async-validator.js | 1343 + .../uview-plus/libs/util/calendar.js | 546 + .../uview-plus/libs/util/emitter.js | 51 + .../uni_modules/uview-plus/libs/util/route.js | 124 + im-uniapp/uni_modules/uview-plus/libs/vue.js | 3 + im-uniapp/uni_modules/uview-plus/package.json | 93 + im-uniapp/uni_modules/uview-plus/theme.scss | 44 + .../uni_modules/uview-plus/types/comps.d.ts | 99 + .../uview-plus/types/comps/_common.d.ts | 9 + .../uview-plus/types/comps/actionSheet.d.ts | 121 + .../uview-plus/types/comps/album.d.ts | 72 + .../uview-plus/types/comps/alert.d.ts | 55 + .../uview-plus/types/comps/avatar.d.ts | 85 + .../uview-plus/types/comps/avatarGroup.d.ts | 62 + .../uview-plus/types/comps/backTop.d.ts | 74 + .../uview-plus/types/comps/badge.d.ts | 76 + .../uview-plus/types/comps/button.d.ts | 169 + .../uview-plus/types/comps/calendar.d.ts | 164 + .../uview-plus/types/comps/cell.d.ts | 133 + .../uview-plus/types/comps/cellGroup.d.ts | 27 + .../uview-plus/types/comps/checkbox.d.ts | 69 + .../uview-plus/types/comps/checkboxGroup.d.ts | 93 + .../uview-plus/types/comps/code.d.ts | 69 + .../uview-plus/types/comps/codeInput.d.ts | 98 + .../uview-plus/types/comps/col.d.ts | 43 + .../uview-plus/types/comps/collapse.d.ts | 52 + .../uview-plus/types/comps/collapseItem.d.ts | 83 + .../uview-plus/types/comps/countDown.d.ts | 60 + .../uview-plus/types/comps/countTo.d.ts | 88 + .../types/comps/datetimePicker.d.ts | 146 + .../uview-plus/types/comps/divider.d.ts | 58 + .../uview-plus/types/comps/empty.d.ts | 72 + .../uview-plus/types/comps/form.d.ts | 76 + .../uview-plus/types/comps/formItem.d.ts | 68 + .../uview-plus/types/comps/gap.d.ts | 35 + .../uview-plus/types/comps/grid.d.ts | 34 + .../uview-plus/types/comps/gridItem.d.ts | 28 + .../uview-plus/types/comps/icon.d.ts | 93 + .../uview-plus/types/comps/image.d.ts | 119 + .../uview-plus/types/comps/indexAnchor.d.ts | 38 + .../uview-plus/types/comps/indexItem.d.ts | 18 + .../uview-plus/types/comps/indexList.d.ts | 39 + .../uview-plus/types/comps/input.d.ts | 234 + .../uview-plus/types/comps/keyboard.d.ts | 125 + .../uview-plus/types/comps/line.d.ts | 44 + .../uview-plus/types/comps/lineProgress.d.ts | 51 + .../uview-plus/types/comps/link.d.ts | 51 + .../uview-plus/types/comps/list.d.ts | 92 + .../uview-plus/types/comps/listItem.d.ts | 18 + .../uview-plus/types/comps/loadMore.d.ts | 108 + .../uview-plus/types/comps/loadingIcon.d.ts | 68 + .../uview-plus/types/comps/loadingPage.d.ts | 58 + .../uview-plus/types/comps/modal.d.ts | 115 + .../uview-plus/types/comps/navbar.d.ts | 113 + .../uview-plus/types/comps/noNetwork.d.ts | 40 + .../uview-plus/types/comps/noticeBar.d.ts | 85 + .../uview-plus/types/comps/notify.d.ts | 73 + .../uview-plus/types/comps/numberBox.d.ts | 155 + .../uview-plus/types/comps/overlay.d.ts | 46 + .../uview-plus/types/comps/parse.d.ts | 101 + .../uview-plus/types/comps/picker.d.ts | 115 + .../uview-plus/types/comps/popup.d.ts | 103 + .../uview-plus/types/comps/radio.d.ts | 77 + .../uview-plus/types/comps/radioGroup.d.ts | 97 + .../uview-plus/types/comps/rate.d.ts | 85 + .../uview-plus/types/comps/readMore.d.ts | 80 + .../uview-plus/types/comps/row.d.ts | 34 + .../uview-plus/types/comps/scrollList.d.ts | 51 + .../uview-plus/types/comps/search.d.ts | 167 + .../uview-plus/types/comps/skeleton.d.ts | 70 + .../uview-plus/types/comps/slider.d.ts | 79 + .../uview-plus/types/comps/steps.d.ts | 48 + .../uview-plus/types/comps/stepsItem.d.ts | 41 + .../uview-plus/types/comps/sticky.d.ts | 48 + .../uview-plus/types/comps/subsection.d.ts | 64 + .../uview-plus/types/comps/swipeAction.d.ts | 24 + .../types/comps/swipeActionItem.d.ts | 58 + .../uview-plus/types/comps/swiper.d.ts | 142 + .../types/comps/swiperIndicator.d.ts | 37 + .../uview-plus/types/comps/switch.d.ts | 73 + .../uview-plus/types/comps/tabbar.d.ts | 54 + .../uview-plus/types/comps/tabbarItem.d.ts | 52 + .../uview-plus/types/comps/tabs.d.ts | 85 + .../uview-plus/types/comps/tag.d.ts | 93 + .../uview-plus/types/comps/text.d.ts | 110 + .../uview-plus/types/comps/textarea.d.ts | 158 + .../uview-plus/types/comps/toast.d.ts | 59 + .../uview-plus/types/comps/tooltip.d.ts | 71 + .../uview-plus/types/comps/transition.d.ts | 61 + .../uview-plus/types/comps/upload.d.ts | 163 + .../uni_modules/uview-plus/types/index.d.ts | 47 + .../uni_modules/uview-plus/types/package.json | 26 + .../dist/cache/.vite/deps/_metadata.json | 31 + .../dist/cache/.vite/deps/chunk-Y2F7D3TJ.js | 9 + .../cache/.vite/deps/chunk-Y2F7D3TJ.js.map | 7 + .../dist/cache/.vite/deps/package.json | 3 + .../dist/cache/.vite/deps/pinyin-pro.js | 24931 ++++++++++++++++ .../dist/cache/.vite/deps/pinyin-pro.js.map | 7 + .../cache/.vite/deps/quill-image-resize-mp.js | 988 + .../.vite/deps/quill-image-resize-mp.js.map | 7 + .../unpackage/dist/cache/.vite/deps/quill.js | 11733 ++++++++ .../dist/cache/.vite/deps/quill.js.map | 7 + im-uniapp/unpackage/res/icons/1024x1024.png | Bin 0 -> 210018 bytes im-uniapp/unpackage/res/icons/120x120.png | Bin 0 -> 7823 bytes im-uniapp/unpackage/res/icons/144x144.png | Bin 0 -> 10005 bytes im-uniapp/unpackage/res/icons/152x152.png | Bin 0 -> 10801 bytes im-uniapp/unpackage/res/icons/167x167.png | Bin 0 -> 12308 bytes im-uniapp/unpackage/res/icons/180x180.png | Bin 0 -> 13839 bytes im-uniapp/unpackage/res/icons/192x192.png | Bin 0 -> 14965 bytes im-uniapp/unpackage/res/icons/20x20.png | Bin 0 -> 839 bytes im-uniapp/unpackage/res/icons/29x29.png | Bin 0 -> 1267 bytes im-uniapp/unpackage/res/icons/40x40.png | Bin 0 -> 1908 bytes im-uniapp/unpackage/res/icons/58x58.png | Bin 0 -> 3017 bytes im-uniapp/unpackage/res/icons/60x60.png | Bin 0 -> 3211 bytes im-uniapp/unpackage/res/icons/72x72.png | Bin 0 -> 4042 bytes im-uniapp/unpackage/res/icons/76x76.png | Bin 0 -> 4355 bytes im-uniapp/unpackage/res/icons/80x80.png | Bin 0 -> 4678 bytes im-uniapp/unpackage/res/icons/87x87.png | Bin 0 -> 5214 bytes im-uniapp/unpackage/res/icons/96x96.png | Bin 0 -> 5809 bytes im-uniapp/vite.config.js | 17 + im-web/.env.development | 8 + im-web/.env.production | 8 + im-web/README.md | 24 + im-web/babel.config.js | 5 + im-web/package.json | 63 + im-web/public/index.html | 13 + im-web/public/logo.png | Bin 0 -> 143941 bytes im-web/src/App.vue | 24 + im-web/src/api/camera.js | 72 + im-web/src/api/date.js | 66 + im-web/src/api/element.js | 30 + im-web/src/api/emotion.js | 40 + im-web/src/api/enums.js | 72 + im-web/src/api/eventBus.js | 3 + im-web/src/api/httpRequest.js | 106 + im-web/src/api/messageType.js | 40 + im-web/src/api/rtcGroupApi.js | 151 + im-web/src/api/rtcPrivateApi.js | 75 + im-web/src/api/str.js | 14 + im-web/src/api/url.js | 15 + im-web/src/api/webrtc.js | 120 + im-web/src/api/wssocket.js | 145 + im-web/src/assets/audio/call.wav | Bin 0 -> 190044 bytes im-web/src/assets/audio/tip.mp3 | Bin 0 -> 9188 bytes im-web/src/assets/emoji/0.gif | Bin 0 -> 25453 bytes im-web/src/assets/emoji/1.gif | Bin 0 -> 21025 bytes im-web/src/assets/emoji/10.gif | Bin 0 -> 26326 bytes im-web/src/assets/emoji/11.gif | Bin 0 -> 31166 bytes im-web/src/assets/emoji/12.gif | Bin 0 -> 20518 bytes im-web/src/assets/emoji/13.gif | Bin 0 -> 26710 bytes im-web/src/assets/emoji/14.gif | Bin 0 -> 18572 bytes im-web/src/assets/emoji/15.gif | Bin 0 -> 6813 bytes im-web/src/assets/emoji/16.gif | Bin 0 -> 25676 bytes im-web/src/assets/emoji/17.gif | Bin 0 -> 3941 bytes im-web/src/assets/emoji/18.gif | Bin 0 -> 28136 bytes im-web/src/assets/emoji/19.gif | Bin 0 -> 19070 bytes im-web/src/assets/emoji/2.gif | Bin 0 -> 27832 bytes im-web/src/assets/emoji/20.gif | Bin 0 -> 28772 bytes im-web/src/assets/emoji/21.gif | Bin 0 -> 28910 bytes im-web/src/assets/emoji/22.gif | Bin 0 -> 30881 bytes im-web/src/assets/emoji/23.gif | Bin 0 -> 14550 bytes im-web/src/assets/emoji/24.gif | Bin 0 -> 18907 bytes im-web/src/assets/emoji/25.gif | Bin 0 -> 27966 bytes im-web/src/assets/emoji/26.gif | Bin 0 -> 23448 bytes im-web/src/assets/emoji/27.gif | Bin 0 -> 18056 bytes im-web/src/assets/emoji/28.gif | Bin 0 -> 24473 bytes im-web/src/assets/emoji/29.gif | Bin 0 -> 15014 bytes im-web/src/assets/emoji/3.gif | Bin 0 -> 30063 bytes im-web/src/assets/emoji/30.gif | Bin 0 -> 29712 bytes im-web/src/assets/emoji/31.gif | Bin 0 -> 11899 bytes im-web/src/assets/emoji/32.gif | Bin 0 -> 15778 bytes im-web/src/assets/emoji/33.gif | Bin 0 -> 12778 bytes im-web/src/assets/emoji/34.gif | Bin 0 -> 12700 bytes im-web/src/assets/emoji/35.gif | Bin 0 -> 11496 bytes im-web/src/assets/emoji/36.gif | Bin 0 -> 5239 bytes im-web/src/assets/emoji/37.gif | Bin 0 -> 25648 bytes im-web/src/assets/emoji/38.gif | Bin 0 -> 16059 bytes im-web/src/assets/emoji/39.gif | Bin 0 -> 14181 bytes im-web/src/assets/emoji/4.gif | Bin 0 -> 8534 bytes im-web/src/assets/emoji/40.gif | Bin 0 -> 26853 bytes im-web/src/assets/emoji/41.gif | Bin 0 -> 7578 bytes im-web/src/assets/emoji/42.gif | Bin 0 -> 26805 bytes im-web/src/assets/emoji/43.gif | Bin 0 -> 13418 bytes im-web/src/assets/emoji/44.gif | Bin 0 -> 10462 bytes im-web/src/assets/emoji/45.gif | Bin 0 -> 20958 bytes im-web/src/assets/emoji/46.gif | Bin 0 -> 19709 bytes im-web/src/assets/emoji/47.gif | Bin 0 -> 19470 bytes im-web/src/assets/emoji/48.gif | Bin 0 -> 20357 bytes im-web/src/assets/emoji/49.gif | Bin 0 -> 15067 bytes im-web/src/assets/emoji/5.gif | Bin 0 -> 30181 bytes im-web/src/assets/emoji/50.gif | Bin 0 -> 18193 bytes im-web/src/assets/emoji/51.gif | Bin 0 -> 18456 bytes im-web/src/assets/emoji/52.gif | Bin 0 -> 24389 bytes im-web/src/assets/emoji/53.gif | Bin 0 -> 20781 bytes im-web/src/assets/emoji/54.gif | Bin 0 -> 15802 bytes im-web/src/assets/emoji/55.gif | Bin 0 -> 10814 bytes im-web/src/assets/emoji/56.gif | Bin 0 -> 12603 bytes im-web/src/assets/emoji/6.gif | Bin 0 -> 24978 bytes im-web/src/assets/emoji/7.gif | Bin 0 -> 30431 bytes im-web/src/assets/emoji/8.gif | Bin 0 -> 12217 bytes im-web/src/assets/emoji/9.gif | Bin 0 -> 31401 bytes im-web/src/assets/iconfont/iconfont.css | 153 + im-web/src/assets/iconfont/iconfont.ttf | Bin 0 -> 10720 bytes im-web/src/assets/image/icp_logo.png | Bin 0 -> 11143 bytes im-web/src/assets/style/element.scss | 130 + im-web/src/assets/style/im.scss | 115 + im-web/src/assets/style/thems.scss | 6 + im-web/src/components/chat/ChatAtBox.vue | 133 + im-web/src/components/chat/ChatBox.vue | 991 + .../src/components/chat/ChatGroupMember.vue | 64 + .../src/components/chat/ChatGroupReaded.vue | 189 + im-web/src/components/chat/ChatGroupSide.vue | 274 + im-web/src/components/chat/ChatHistory.vue | 171 + im-web/src/components/chat/ChatInput.vue | 549 + im-web/src/components/chat/ChatItem.vue | 222 + .../src/components/chat/ChatMessageItem.vue | 474 + im-web/src/components/chat/ChatRecord.vue | 137 + im-web/src/components/common/Emotion.vue | 71 + im-web/src/components/common/FileUpload.vue | 109 + im-web/src/components/common/FullImage.vue | 86 + im-web/src/components/common/HeadImage.vue | 137 + im-web/src/components/common/Icp.vue | 26 + .../src/components/common/ResizableAside.vue | 131 + im-web/src/components/common/RightMenu.vue | 90 + im-web/src/components/common/UserInfo.vue | 173 + .../src/components/common/VirtualScroller.vue | 74 + im-web/src/components/friend/AddFriend.vue | 141 + im-web/src/components/friend/FriendItem.vue | 132 + .../src/components/group/AddGroupMember.vue | 173 + im-web/src/components/group/GroupItem.vue | 62 + im-web/src/components/group/GroupMember.vue | 62 + .../src/components/group/GroupMemberItem.vue | 69 + .../components/group/GroupMemberSelector.vue | 178 + im-web/src/components/rtc/RtcGroupJoin.vue | 118 + im-web/src/components/rtc/RtcGroupVideo.vue | 45 + .../src/components/rtc/RtcPrivateAcceptor.vue | 127 + im-web/src/components/rtc/RtcPrivateVideo.vue | 497 + im-web/src/components/setting/Setting.vue | 140 + im-web/src/main.js | 54 + im-web/src/router/index.js | 49 + im-web/src/store/chatStore.js | 573 + im-web/src/store/configStore.js | 37 + im-web/src/store/friendStore.js | 99 + im-web/src/store/groupStore.js | 67 + im-web/src/store/userStore.js | 48 + im-web/src/utils/directive/dialogDrag.js | 72 + im-web/src/view/Chat.vue | 129 + im-web/src/view/Friend.vue | 290 + im-web/src/view/Group.vue | 496 + im-web/src/view/Home.vue | 626 + im-web/src/view/Login.vue | 235 + im-web/src/view/Register.vue | 234 + im-web/vue.config.js | 18 + pom.xml | 90 + 截图/app/1.png | Bin 0 -> 1281504 bytes 截图/app/2.png | Bin 0 -> 1872579 bytes 截图/h5二维码.png | Bin 0 -> 15355 bytes 截图/web/单人通话.jpg | Bin 0 -> 172593 bytes 截图/web/多人通话.jpg | Bin 0 -> 206414 bytes 截图/web/好友.jpg | Bin 0 -> 144521 bytes 截图/web/私聊.jpg | Bin 0 -> 147180 bytes 截图/web/群列表.jpg | Bin 0 -> 193436 bytes 截图/web/群聊.jpg | Bin 0 -> 191729 bytes 截图/消息推送集群化.jpg | Bin 0 -> 135767 bytes 1273 files changed, 158507 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 db/im-platform.sql create mode 100644 db/敏感词库初始化.sql create mode 100644 im-client/pom.xml create mode 100644 im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java create mode 100644 im-client/src/main/java/com/bx/imclient/IMClient.java create mode 100644 im-client/src/main/java/com/bx/imclient/annotation/IMListener.java create mode 100644 im-client/src/main/java/com/bx/imclient/listener/MessageListener.java create mode 100644 im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java create mode 100644 im-client/src/main/java/com/bx/imclient/sender/IMSender.java create mode 100644 im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java create mode 100644 im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java create mode 100644 im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java create mode 100644 im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java create mode 100644 im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 im-common/pom.xml create mode 100644 im-common/src/main/java/com/bx/imcommon/contant/IMConstant.java create mode 100644 im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java create mode 100644 im-common/src/main/java/com/bx/imcommon/enums/IMCmdType.java create mode 100644 im-common/src/main/java/com/bx/imcommon/enums/IMListenerType.java create mode 100644 im-common/src/main/java/com/bx/imcommon/enums/IMSendCode.java create mode 100644 im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMGroupMessage.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMLoginInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMRecvInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMSendInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMSendResult.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMSessionInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMSystemMessage.java create mode 100644 im-common/src/main/java/com/bx/imcommon/model/IMUserInfo.java create mode 100644 im-common/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java create mode 100644 im-common/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java create mode 100644 im-common/src/main/java/com/bx/imcommon/mq/RedisMQListener.java create mode 100644 im-common/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java create mode 100644 im-common/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java create mode 100644 im-common/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java create mode 100644 im-common/src/main/java/com/bx/imcommon/util/CommaTextUtils.java create mode 100644 im-common/src/main/java/com/bx/imcommon/util/JwtUtil.java create mode 100644 im-common/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java create mode 100644 im-platform/pom.xml create mode 100644 im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java create mode 100644 im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java create mode 100644 im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java create mode 100644 im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java create mode 100644 im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java create mode 100644 im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java create mode 100644 im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/ICEServer.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/TaskSchedulerConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/WebrtcConfig.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/props/JwtProperties.java create mode 100644 im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java create mode 100644 im-platform/src/main/java/com/bx/implatform/contant/Constant.java create mode 100644 im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/FileController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/FriendController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/GroupController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/LoginController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/SystemController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/UserController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/Friend.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/Group.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java create mode 100644 im-platform/src/main/java/com/bx/implatform/entity/User.java create mode 100644 im-platform/src/main/java/com/bx/implatform/enums/FileType.java create mode 100644 im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java create mode 100644 im-platform/src/main/java/com/bx/implatform/enums/MessageType.java create mode 100644 im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java create mode 100644 im-platform/src/main/java/com/bx/implatform/enums/WebrtcMode.java create mode 100644 im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java create mode 100644 im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java create mode 100644 im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java create mode 100644 im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java create mode 100644 im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java create mode 100644 im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java create mode 100644 im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java create mode 100644 im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/FriendMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/GroupMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/GroupMemberMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/GroupMessageMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/PrivateMessageMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/mapper/UserMapper.java create mode 100644 im-platform/src/main/java/com/bx/implatform/result/Result.java create mode 100644 im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/FileService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/FriendService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/GroupService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/UserService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/SensitiveWordServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java create mode 100644 im-platform/src/main/java/com/bx/implatform/session/SessionContext.java create mode 100644 im-platform/src/main/java/com/bx/implatform/session/UserSession.java create mode 100644 im-platform/src/main/java/com/bx/implatform/session/WebrtcGroupSession.java create mode 100644 im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java create mode 100644 im-platform/src/main/java/com/bx/implatform/session/WebrtcUserInfo.java create mode 100644 im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java create mode 100644 im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java create mode 100644 im-platform/src/main/java/com/bx/implatform/task/consumer/UserBannedConsumerTask.java create mode 100644 im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java create mode 100644 im-platform/src/main/java/com/bx/implatform/task/schedule/ReloadSensitiveWordTask.java create mode 100644 im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/FileUtil.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/ImageUtil.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/SensitiveFilterUtil.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java create mode 100644 im-platform/src/main/java/com/bx/implatform/util/XssUtil.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/SystemMessageVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/UploadImageVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/UserVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupFailedVO.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java create mode 100644 im-platform/src/main/resources/application-dev.yml create mode 100644 im-platform/src/main/resources/application-prod.yml create mode 100644 im-platform/src/main/resources/application-test.yml create mode 100644 im-platform/src/main/resources/application.yml create mode 100644 im-platform/src/main/resources/logback.xml create mode 100644 im-platform/src/main/resources/static/favicon.ico create mode 100644 im-server/pom.xml create mode 100644 im-server/src/main/java/com/bx/imserver/IMServerApp.java create mode 100644 im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/IMServer.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/processor/SystemMessageProcessor.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolDecoder.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolEncoder.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolDecoder.java create mode 100644 im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java create mode 100644 im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java create mode 100644 im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java create mode 100644 im-server/src/main/java/com/bx/imserver/task/PullPrivateMessageTask.java create mode 100644 im-server/src/main/java/com/bx/imserver/task/PullSystemMessageTask.java create mode 100644 im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java create mode 100644 im-server/src/main/resources/application-dev.yml create mode 100644 im-server/src/main/resources/application-prod.yml create mode 100644 im-server/src/main/resources/application-test.yml create mode 100644 im-server/src/main/resources/application.yml create mode 100644 im-server/src/main/resources/logback.xml create mode 100644 im-uniapp/.env.js create mode 100644 im-uniapp/App.vue create mode 100644 im-uniapp/common/date.js create mode 100644 im-uniapp/common/emotion.js create mode 100644 im-uniapp/common/enums.js create mode 100644 im-uniapp/common/messageType.js create mode 100644 im-uniapp/common/recorder-app.js create mode 100644 im-uniapp/common/recorder-h5.js create mode 100644 im-uniapp/common/request.js create mode 100644 im-uniapp/common/str.js create mode 100644 im-uniapp/common/url.js create mode 100644 im-uniapp/common/wssocket.js create mode 100644 im-uniapp/components/bar/arrow-bar.vue create mode 100644 im-uniapp/components/bar/bar-group.vue create mode 100644 im-uniapp/components/bar/btn-bar.vue create mode 100644 im-uniapp/components/bar/switch-bar.vue create mode 100644 im-uniapp/components/chat-at-box/chat-at-box.vue create mode 100644 im-uniapp/components/chat-group-readed/chat-group-readed.vue create mode 100644 im-uniapp/components/chat-item/chat-item.vue create mode 100644 im-uniapp/components/chat-message-item/chat-message-item.vue create mode 100644 im-uniapp/components/chat-record/chat-record.vue create mode 100644 im-uniapp/components/file-upload/file-upload.vue create mode 100644 im-uniapp/components/friend-item/friend-item.vue create mode 100644 im-uniapp/components/group-item/group-item.vue create mode 100644 im-uniapp/components/group-member-selector/group-member-selector.vue create mode 100644 im-uniapp/components/group-rtc-join/group-rtc-join.vue create mode 100644 im-uniapp/components/head-image/head-image.vue create mode 100644 im-uniapp/components/image-upload/image-upload.vue create mode 100644 im-uniapp/components/loading/loading.vue create mode 100644 im-uniapp/components/long-press-menu/long-press-menu.vue create mode 100644 im-uniapp/components/nav-bar/nav-bar.vue create mode 100644 im-uniapp/components/virtual-scroller/virtual-scroller.vue create mode 100644 im-uniapp/hybrid/html/rtc-group/index.html create mode 100644 im-uniapp/hybrid/html/rtc-private/index.html create mode 100644 im-uniapp/im-var.scss create mode 100644 im-uniapp/im.scss create mode 100644 im-uniapp/index.html create mode 100644 im-uniapp/main.js create mode 100644 im-uniapp/manifest.json create mode 100644 im-uniapp/package.json create mode 100644 im-uniapp/pages.json create mode 100644 im-uniapp/pages/chat/chat-box.vue create mode 100644 im-uniapp/pages/chat/chat-group-video.vue create mode 100644 im-uniapp/pages/chat/chat-private-video.vue create mode 100644 im-uniapp/pages/chat/chat.vue create mode 100644 im-uniapp/pages/common/external-link.vue create mode 100644 im-uniapp/pages/common/user-info.vue create mode 100644 im-uniapp/pages/friend/friend-add.vue create mode 100644 im-uniapp/pages/friend/friend.vue create mode 100644 im-uniapp/pages/group/group-edit.vue create mode 100644 im-uniapp/pages/group/group-info.vue create mode 100644 im-uniapp/pages/group/group-invite.vue create mode 100644 im-uniapp/pages/group/group-member.vue create mode 100644 im-uniapp/pages/group/group.vue create mode 100644 im-uniapp/pages/login/login.vue create mode 100644 im-uniapp/pages/mine/mine-edit.vue create mode 100644 im-uniapp/pages/mine/mine-password.vue create mode 100644 im-uniapp/pages/mine/mine.vue create mode 100644 im-uniapp/pages/register/register.vue create mode 100644 im-uniapp/pnpm-lock.yaml create mode 100644 im-uniapp/static/emoji/0.gif create mode 100644 im-uniapp/static/emoji/1.gif create mode 100644 im-uniapp/static/emoji/10.gif create mode 100644 im-uniapp/static/emoji/11.gif create mode 100644 im-uniapp/static/emoji/12.gif create mode 100644 im-uniapp/static/emoji/13.gif create mode 100644 im-uniapp/static/emoji/14.gif create mode 100644 im-uniapp/static/emoji/15.gif create mode 100644 im-uniapp/static/emoji/16.gif create mode 100644 im-uniapp/static/emoji/17.gif create mode 100644 im-uniapp/static/emoji/18.gif create mode 100644 im-uniapp/static/emoji/19.gif create mode 100644 im-uniapp/static/emoji/2.gif create mode 100644 im-uniapp/static/emoji/20.gif create mode 100644 im-uniapp/static/emoji/21.gif create mode 100644 im-uniapp/static/emoji/22.gif create mode 100644 im-uniapp/static/emoji/23.gif create mode 100644 im-uniapp/static/emoji/24.gif create mode 100644 im-uniapp/static/emoji/25.gif create mode 100644 im-uniapp/static/emoji/26.gif create mode 100644 im-uniapp/static/emoji/27.gif create mode 100644 im-uniapp/static/emoji/28.gif create mode 100644 im-uniapp/static/emoji/29.gif create mode 100644 im-uniapp/static/emoji/3.gif create mode 100644 im-uniapp/static/emoji/30.gif create mode 100644 im-uniapp/static/emoji/31.gif create mode 100644 im-uniapp/static/emoji/32.gif create mode 100644 im-uniapp/static/emoji/33.gif create mode 100644 im-uniapp/static/emoji/34.gif create mode 100644 im-uniapp/static/emoji/35.gif create mode 100644 im-uniapp/static/emoji/36.gif create mode 100644 im-uniapp/static/emoji/37.gif create mode 100644 im-uniapp/static/emoji/38.gif create mode 100644 im-uniapp/static/emoji/39.gif create mode 100644 im-uniapp/static/emoji/4.gif create mode 100644 im-uniapp/static/emoji/40.gif create mode 100644 im-uniapp/static/emoji/41.gif create mode 100644 im-uniapp/static/emoji/42.gif create mode 100644 im-uniapp/static/emoji/43.gif create mode 100644 im-uniapp/static/emoji/44.gif create mode 100644 im-uniapp/static/emoji/45.gif create mode 100644 im-uniapp/static/emoji/46.gif create mode 100644 im-uniapp/static/emoji/47.gif create mode 100644 im-uniapp/static/emoji/48.gif create mode 100644 im-uniapp/static/emoji/49.gif create mode 100644 im-uniapp/static/emoji/5.gif create mode 100644 im-uniapp/static/emoji/50.gif create mode 100644 im-uniapp/static/emoji/51.gif create mode 100644 im-uniapp/static/emoji/52.gif create mode 100644 im-uniapp/static/emoji/53.gif create mode 100644 im-uniapp/static/emoji/54.gif create mode 100644 im-uniapp/static/emoji/55.gif create mode 100644 im-uniapp/static/emoji/56.gif create mode 100644 im-uniapp/static/emoji/6.gif create mode 100644 im-uniapp/static/emoji/7.gif create mode 100644 im-uniapp/static/emoji/8.gif create mode 100644 im-uniapp/static/emoji/9.gif create mode 100644 im-uniapp/static/icon/iconfont.css create mode 100644 im-uniapp/static/icon/iconfont.ttf create mode 100644 im-uniapp/static/image/online_app.png create mode 100644 im-uniapp/static/image/online_web.png create mode 100644 im-uniapp/static/tab/chat.png create mode 100644 im-uniapp/static/tab/chat_active.png create mode 100644 im-uniapp/static/tab/friend.png create mode 100644 im-uniapp/static/tab/friend_active.png create mode 100644 im-uniapp/static/tab/group.png create mode 100644 im-uniapp/static/tab/group_active.png create mode 100644 im-uniapp/static/tab/mine.png create mode 100644 im-uniapp/static/tab/mine_active.png create mode 100644 im-uniapp/store/chatStore.js create mode 100644 im-uniapp/store/configStore.js create mode 100644 im-uniapp/store/friendStore.js create mode 100644 im-uniapp/store/groupStore.js create mode 100644 im-uniapp/store/userStore.js create mode 100644 im-uniapp/uni.promisify.adaptor.js create mode 100644 im-uniapp/uni.scss create mode 100644 im-uniapp/uni_modules/lsj-upload/changelog.md create mode 100644 im-uniapp/uni_modules/lsj-upload/components/lsj-upload/LsjFile.js create mode 100644 im-uniapp/uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue create mode 100644 im-uniapp/uni_modules/lsj-upload/hybrid/html/js/vue.min.js create mode 100644 im-uniapp/uni_modules/lsj-upload/hybrid/html/uploadFile.html create mode 100644 im-uniapp/uni_modules/lsj-upload/package.json create mode 100644 im-uniapp/uni_modules/uni-badge/changelog.md create mode 100644 im-uniapp/uni_modules/uni-badge/components/uni-badge/uni-badge.vue create mode 100644 im-uniapp/uni_modules/uni-badge/package.json create mode 100644 im-uniapp/uni_modules/uni-badge/readme.md create mode 100644 im-uniapp/uni_modules/uni-calendar/changelog.md create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/calendar.js create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue create mode 100644 im-uniapp/uni_modules/uni-calendar/components/uni-calendar/util.js create mode 100644 im-uniapp/uni_modules/uni-calendar/package.json create mode 100644 im-uniapp/uni_modules/uni-calendar/readme.md create mode 100644 im-uniapp/uni_modules/uni-card/changelog.md create mode 100644 im-uniapp/uni_modules/uni-card/components/uni-card/uni-card.vue create mode 100644 im-uniapp/uni_modules/uni-card/package.json create mode 100644 im-uniapp/uni_modules/uni-card/readme.md create mode 100644 im-uniapp/uni_modules/uni-collapse/changelog.md create mode 100644 im-uniapp/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue create mode 100644 im-uniapp/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue create mode 100644 im-uniapp/uni_modules/uni-collapse/package.json create mode 100644 im-uniapp/uni_modules/uni-collapse/readme.md create mode 100644 im-uniapp/uni_modules/uni-combox/changelog.md create mode 100644 im-uniapp/uni_modules/uni-combox/components/uni-combox/uni-combox.vue create mode 100644 im-uniapp/uni_modules/uni-combox/package.json create mode 100644 im-uniapp/uni_modules/uni-combox/readme.md create mode 100644 im-uniapp/uni_modules/uni-countdown/changelog.md create mode 100644 im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue create mode 100644 im-uniapp/uni_modules/uni-countdown/package.json create mode 100644 im-uniapp/uni_modules/uni-countdown/readme.md create mode 100644 im-uniapp/uni_modules/uni-data-checkbox/changelog.md create mode 100644 im-uniapp/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue create mode 100644 im-uniapp/uni_modules/uni-data-checkbox/package.json create mode 100644 im-uniapp/uni_modules/uni-data-checkbox/readme.md create mode 100644 im-uniapp/uni_modules/uni-data-picker/changelog.md create mode 100644 im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js create mode 100644 im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue create mode 100644 im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js create mode 100644 im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue create mode 100644 im-uniapp/uni_modules/uni-data-picker/package.json create mode 100644 im-uniapp/uni_modules/uni-data-picker/readme.md create mode 100644 im-uniapp/uni_modules/uni-data-select/changelog.md create mode 100644 im-uniapp/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue create mode 100644 im-uniapp/uni_modules/uni-data-select/package.json create mode 100644 im-uniapp/uni_modules/uni-data-select/readme.md create mode 100644 im-uniapp/uni_modules/uni-dateformat/changelog.md create mode 100644 im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js create mode 100644 im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue create mode 100644 im-uniapp/uni_modules/uni-dateformat/package.json create mode 100644 im-uniapp/uni_modules/uni-dateformat/readme.md create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/changelog.md create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/package.json create mode 100644 im-uniapp/uni_modules/uni-datetime-picker/readme.md create mode 100644 im-uniapp/uni_modules/uni-drawer/changelog.md create mode 100644 im-uniapp/uni_modules/uni-drawer/components/uni-drawer/keypress.js create mode 100644 im-uniapp/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue create mode 100644 im-uniapp/uni_modules/uni-drawer/package.json create mode 100644 im-uniapp/uni_modules/uni-drawer/readme.md create mode 100644 im-uniapp/uni_modules/uni-easyinput/changelog.md create mode 100644 im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/common.js create mode 100644 im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue create mode 100644 im-uniapp/uni_modules/uni-easyinput/package.json create mode 100644 im-uniapp/uni_modules/uni-easyinput/readme.md create mode 100644 im-uniapp/uni_modules/uni-fab/changelog.md create mode 100644 im-uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue create mode 100644 im-uniapp/uni_modules/uni-fab/package.json create mode 100644 im-uniapp/uni_modules/uni-fab/readme.md create mode 100644 im-uniapp/uni_modules/uni-fav/changelog.md create mode 100644 im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue create mode 100644 im-uniapp/uni_modules/uni-fav/package.json create mode 100644 im-uniapp/uni_modules/uni-fav/readme.md create mode 100644 im-uniapp/uni_modules/uni-file-picker/changelog.md create mode 100644 im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js create mode 100644 im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue create mode 100644 im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue create mode 100644 im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue create mode 100644 im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js create mode 100644 im-uniapp/uni_modules/uni-file-picker/package.json create mode 100644 im-uniapp/uni_modules/uni-file-picker/readme.md create mode 100644 im-uniapp/uni_modules/uni-forms/changelog.md create mode 100644 im-uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue create mode 100644 im-uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue create mode 100644 im-uniapp/uni_modules/uni-forms/components/uni-forms/utils.js create mode 100644 im-uniapp/uni_modules/uni-forms/components/uni-forms/validate.js create mode 100644 im-uniapp/uni_modules/uni-forms/package.json create mode 100644 im-uniapp/uni_modules/uni-forms/readme.md create mode 100644 im-uniapp/uni_modules/uni-goods-nav/changelog.md create mode 100644 im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue create mode 100644 im-uniapp/uni_modules/uni-goods-nav/package.json create mode 100644 im-uniapp/uni_modules/uni-goods-nav/readme.md create mode 100644 im-uniapp/uni_modules/uni-grid/changelog.md create mode 100644 im-uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue create mode 100644 im-uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue create mode 100644 im-uniapp/uni_modules/uni-grid/package.json create mode 100644 im-uniapp/uni_modules/uni-grid/readme.md create mode 100644 im-uniapp/uni_modules/uni-group/changelog.md create mode 100644 im-uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue create mode 100644 im-uniapp/uni_modules/uni-group/package.json create mode 100644 im-uniapp/uni_modules/uni-group/readme.md create mode 100644 im-uniapp/uni_modules/uni-icons/changelog.md create mode 100644 im-uniapp/uni_modules/uni-icons/components/uni-icons/icons.js create mode 100644 im-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue create mode 100644 im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css create mode 100644 im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf create mode 100644 im-uniapp/uni_modules/uni-icons/package.json create mode 100644 im-uniapp/uni_modules/uni-icons/readme.md create mode 100644 im-uniapp/uni_modules/uni-indexed-list/changelog.md create mode 100644 im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue create mode 100644 im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue create mode 100644 im-uniapp/uni_modules/uni-indexed-list/package.json create mode 100644 im-uniapp/uni_modules/uni-indexed-list/readme.md create mode 100644 im-uniapp/uni_modules/uni-link/changelog.md create mode 100644 im-uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue create mode 100644 im-uniapp/uni_modules/uni-link/package.json create mode 100644 im-uniapp/uni_modules/uni-link/readme.md create mode 100644 im-uniapp/uni_modules/uni-list/changelog.md create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue create mode 100644 im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs create mode 100644 im-uniapp/uni_modules/uni-list/package.json create mode 100644 im-uniapp/uni_modules/uni-list/readme.md create mode 100644 im-uniapp/uni_modules/uni-load-more/changelog.md create mode 100644 im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue create mode 100644 im-uniapp/uni_modules/uni-load-more/package.json create mode 100644 im-uniapp/uni_modules/uni-load-more/readme.md create mode 100644 im-uniapp/uni_modules/uni-nav-bar/changelog.md create mode 100644 im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue create mode 100644 im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue create mode 100644 im-uniapp/uni_modules/uni-nav-bar/package.json create mode 100644 im-uniapp/uni_modules/uni-nav-bar/readme.md create mode 100644 im-uniapp/uni_modules/uni-notice-bar/changelog.md create mode 100644 im-uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue create mode 100644 im-uniapp/uni_modules/uni-notice-bar/package.json create mode 100644 im-uniapp/uni_modules/uni-notice-bar/readme.md create mode 100644 im-uniapp/uni_modules/uni-number-box/changelog.md create mode 100644 im-uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue create mode 100644 im-uniapp/uni_modules/uni-number-box/package.json create mode 100644 im-uniapp/uni_modules/uni-number-box/readme.md create mode 100644 im-uniapp/uni_modules/uni-pagination/changelog.md create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue create mode 100644 im-uniapp/uni_modules/uni-pagination/package.json create mode 100644 im-uniapp/uni_modules/uni-pagination/readme.md create mode 100644 im-uniapp/uni_modules/uni-popup/changelog.md create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/popup.js create mode 100644 im-uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue create mode 100644 im-uniapp/uni_modules/uni-popup/package.json create mode 100644 im-uniapp/uni_modules/uni-popup/readme.md create mode 100644 im-uniapp/uni_modules/uni-rate/changelog.md create mode 100644 im-uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue create mode 100644 im-uniapp/uni_modules/uni-rate/package.json create mode 100644 im-uniapp/uni_modules/uni-rate/readme.md create mode 100644 im-uniapp/uni_modules/uni-row/changelog.md create mode 100644 im-uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue create mode 100644 im-uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue create mode 100644 im-uniapp/uni_modules/uni-row/package.json create mode 100644 im-uniapp/uni_modules/uni-row/readme.md create mode 100644 im-uniapp/uni_modules/uni-scss/changelog.md create mode 100644 im-uniapp/uni_modules/uni-scss/index.scss create mode 100644 im-uniapp/uni_modules/uni-scss/package.json create mode 100644 im-uniapp/uni_modules/uni-scss/readme.md create mode 100644 im-uniapp/uni_modules/uni-scss/styles/index.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_border.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_color.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_space.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_text.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss create mode 100644 im-uniapp/uni_modules/uni-scss/styles/tools/functions.scss create mode 100644 im-uniapp/uni_modules/uni-scss/theme.scss create mode 100644 im-uniapp/uni_modules/uni-scss/variables.scss create mode 100644 im-uniapp/uni_modules/uni-search-bar/changelog.md create mode 100644 im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue create mode 100644 im-uniapp/uni_modules/uni-search-bar/package.json create mode 100644 im-uniapp/uni_modules/uni-search-bar/readme.md create mode 100644 im-uniapp/uni_modules/uni-section/changelog.md create mode 100644 im-uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue create mode 100644 im-uniapp/uni_modules/uni-section/package.json create mode 100644 im-uniapp/uni_modules/uni-section/readme.md create mode 100644 im-uniapp/uni_modules/uni-segmented-control/changelog.md create mode 100644 im-uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue create mode 100644 im-uniapp/uni_modules/uni-segmented-control/package.json create mode 100644 im-uniapp/uni_modules/uni-segmented-control/readme.md create mode 100644 im-uniapp/uni_modules/uni-steps/changelog.md create mode 100644 im-uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue create mode 100644 im-uniapp/uni_modules/uni-steps/package.json create mode 100644 im-uniapp/uni_modules/uni-steps/readme.md create mode 100644 im-uniapp/uni_modules/uni-swipe-action/changelog.md create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs create mode 100644 im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue create mode 100644 im-uniapp/uni_modules/uni-swipe-action/package.json create mode 100644 im-uniapp/uni_modules/uni-swipe-action/readme.md create mode 100644 im-uniapp/uni_modules/uni-swiper-dot/changelog.md create mode 100644 im-uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue create mode 100644 im-uniapp/uni_modules/uni-swiper-dot/package.json create mode 100644 im-uniapp/uni_modules/uni-swiper-dot/readme.md create mode 100644 im-uniapp/uni_modules/uni-table/changelog.md create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue create mode 100644 im-uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue create mode 100644 im-uniapp/uni_modules/uni-table/i18n/en.json create mode 100644 im-uniapp/uni_modules/uni-table/i18n/es.json create mode 100644 im-uniapp/uni_modules/uni-table/i18n/fr.json create mode 100644 im-uniapp/uni_modules/uni-table/i18n/index.js create mode 100644 im-uniapp/uni_modules/uni-table/i18n/zh-Hans.json create mode 100644 im-uniapp/uni_modules/uni-table/i18n/zh-Hant.json create mode 100644 im-uniapp/uni_modules/uni-table/package.json create mode 100644 im-uniapp/uni_modules/uni-table/readme.md create mode 100644 im-uniapp/uni_modules/uni-tag/changelog.md create mode 100644 im-uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue create mode 100644 im-uniapp/uni_modules/uni-tag/package.json create mode 100644 im-uniapp/uni_modules/uni-tag/readme.md create mode 100644 im-uniapp/uni_modules/uni-title/changelog.md create mode 100644 im-uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue create mode 100644 im-uniapp/uni_modules/uni-title/package.json create mode 100644 im-uniapp/uni_modules/uni-title/readme.md create mode 100644 im-uniapp/uni_modules/uni-tooltip/changelog.md create mode 100644 im-uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue create mode 100644 im-uniapp/uni_modules/uni-tooltip/package.json create mode 100644 im-uniapp/uni_modules/uni-tooltip/readme.md create mode 100644 im-uniapp/uni_modules/uni-transition/changelog.md create mode 100644 im-uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js create mode 100644 im-uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue create mode 100644 im-uniapp/uni_modules/uni-transition/package.json create mode 100644 im-uniapp/uni_modules/uni-transition/readme.md create mode 100644 im-uniapp/uni_modules/uni-ui/changelog.md create mode 100644 im-uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue create mode 100644 im-uniapp/uni_modules/uni-ui/package.json create mode 100644 im-uniapp/uni_modules/uni-ui/readme.md create mode 100644 im-uniapp/uni_modules/uview-plus/LICENSE create mode 100644 im-uniapp/uni_modules/uview-plus/README.md create mode 100644 im-uniapp/uni_modules/uview-plus/changelog.md create mode 100644 im-uniapp/uni_modules/uview-plus/components/u--form/u--form.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u--image/u--image.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u--input/u--input.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u--text/u--text.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u--textarea/u--textarea.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-action-sheet/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-album/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-album/u-album.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-alert/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-alert/u-alert.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-avatar-group/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-avatar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-avatar/u-avatar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-back-top/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-back-top/u-back-top.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-badge/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-badge/u-badge.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-button/nvue.scss create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-button/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-button/u-button.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-button/vue.scss create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-calendar/header.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-calendar/month.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-calendar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-calendar/u-calendar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-calendar/util.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-cell-group/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-cell/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-cell/u-cell.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-checkbox/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-circle-progress/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-code-input/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-code-input/u-code-input.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-code/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-code/u-code.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-col/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-col/u-col.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-collapse-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-collapse/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-collapse/u-collapse.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-column-notice/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-copy/u-copy.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-count-down/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-count-down/u-count-down.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-count-down/utils.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-count-to/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-count-to/u-count-to.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-divider/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-divider/u-divider.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/u-dropdown-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-dropdown/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-empty/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-empty/u-empty.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-form-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-form-item/u-form-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-form/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-form/u-form.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-gap/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-gap/u-gap.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-grid-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-grid/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-grid/u-grid.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-icon/icons.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-icon/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-icon/u-icon.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-image/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-image/u-image.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-anchor/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-anchor/u-index-anchor.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-item/u-index-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-list/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-index-list/u-index-list.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-input/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-input/u-input.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-keyboard/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-line-progress/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-line/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-line/u-line.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-link/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-link/u-link.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-list-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-list-item/u-list-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-list/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-list/u-list.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loading-icon/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loading-page/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loadmore/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-message-input/u-message-input.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-modal/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-modal/u-modal.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-navbar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-navbar/u-navbar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-no-network/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-no-network/u-no-network.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-notice-bar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-notify/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-notify/u-notify.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-number-box/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-number-box/u-number-box.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/u-number-keyboard.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-overlay/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-overlay/u-overlay.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-parse/node/node.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-parse/parser.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-parse/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-parse/u-parse.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-picker-column/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-picker-column/u-picker-column.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-picker/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-picker/u-picker.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-popup/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-popup/u-popup.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-qrcode/qrcode.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-qrcode/u-qrcode.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-radio-group/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-radio-group/u-radio-group.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-radio/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-radio/u-radio.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-rate/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-rate/u-rate.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-read-more/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-read-more/u-read-more.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-row-notice/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-row-notice/u-row-notice.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-row/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-row/u-row.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-safe-bottom/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-scroll-list/nvue.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-scroll-list/other.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-scroll-list/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-scroll-list/scrollWxs.wxs create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-scroll-list/u-scroll-list.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-search/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-search/u-search.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-skeleton/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-skeleton/u-skeleton.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/mpother.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/mpwxs.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/mpwxs.wxs create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/nvue - 副本.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/nvue.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-slider/u-slider.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-status-bar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-status-bar/u-status-bar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-steps-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-steps-item/u-steps-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-steps/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-steps/u-steps.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-sticky/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-sticky/u-sticky.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-subsection/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-subsection/u-subsection.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/alipay.sjs create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/index - backup.wxs create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/index.wxs create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/nvue - backup.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/nvue.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/other.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/u-swipe-action-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action-item/wxs.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swipe-action/u-swipe-action.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swiper-indicator/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swiper-indicator/u-swiper-indicator.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swiper/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-swiper/u-swiper.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-switch/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-switch/u-switch.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabbar-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabbar-item/u-tabbar-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabbar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabbar/u-tabbar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-table/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-table/u-table.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabs-item/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabs-item/u-tabs-item.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabs/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tabs/u-tabs.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tag/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tag/u-tag.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-td/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-td/u-td.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-text/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-text/u-text.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-text/value.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-textarea/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-textarea/u-textarea.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-toast/u-toast.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-toolbar/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-toolbar/u-toolbar.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tooltip/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tooltip/u-tooltip.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tr/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-tr/u-tr.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-transition/nvue-ani-map.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-transition/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-transition/transition.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-transition/u-transition.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-transition/vue.ani-style.scss create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-upload/mixin.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-upload/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-upload/u-upload.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-upload/utils.js create mode 100644 im-uniapp/uni_modules/uview-plus/components/u-waterfall/u-waterfall.vue create mode 100644 im-uniapp/uni_modules/uview-plus/components/uview-plus/uview-plus.vue create mode 100644 im-uniapp/uni_modules/uview-plus/index.js create mode 100644 im-uniapp/uni_modules/uview-plus/index.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/color.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/config.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/actionSheet.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/album.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/alert.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/avatar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/avatarGroup.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/backtop.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/badge.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/button.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/calendar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/carKeyboard.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/cell.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/cellGroup.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/checkbox.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/checkboxGroup.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/circleProgress.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/code.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/codeInput.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/col.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/collapse.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/collapseItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/columnNotice.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/countDown.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/countTo.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/datetimePicker.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/divider.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/empty.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/form.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/formItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/gap.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/grid.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/gridItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/icon.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/image.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/indexAnchor.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/indexList.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/input.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/keyboard.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/line.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/lineProgress.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/link.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/list.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/listItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/loadingIcon.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/loadingPage.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/loadmore.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/modal.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/navbar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/noNetwork.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/noticeBar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/notify.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/numberBox.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/numberKeyboard.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/overlay.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/parse.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/picker.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/popup.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/radio.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/radioGroup.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/rate.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/readMore.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/row.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/rowNotice.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/scrollList.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/search.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/section.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/skeleton.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/slider.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/statusBar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/steps.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/stepsItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/sticky.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/subsection.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/swipeAction.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/swipeActionItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/swiper.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/swipterIndicator.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/switch.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/tabbar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/tabbarItem.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/tabs.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/tag.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/text.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/textarea.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/toast.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/toolbar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/tooltip.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/transition.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/props/upload.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/config/zIndex.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/color.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/common.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/components.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/flex.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/h5.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/mixin.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/mp.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/nvue.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/css/vue.scss create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/colorGradient.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/debounce.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/digit.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/index.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/platform.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/test.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/function/throttle.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/adapters/index.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/InterceptorManager.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/Request.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/buildFullPath.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/defaults.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/dispatchRequest.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/mergeConfig.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/core/settle.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/helpers/buildURL.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/helpers/combineURLs.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/helpers/isAbsoluteURL.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/index.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/index.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/utils.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/luch-request/utils/clone.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/button.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/mixin.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/mpMixin.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/mpShare.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/openType.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/style.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/mixin/touch.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/util/async-validator.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/util/calendar.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/util/emitter.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/util/route.js create mode 100644 im-uniapp/uni_modules/uview-plus/libs/vue.js create mode 100644 im-uniapp/uni_modules/uview-plus/package.json create mode 100644 im-uniapp/uni_modules/uview-plus/theme.scss create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/_common.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/actionSheet.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/album.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/alert.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/avatar.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/avatarGroup.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/backTop.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/badge.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/button.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/calendar.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/cell.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/cellGroup.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/checkbox.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/checkboxGroup.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/code.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/codeInput.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/col.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/collapse.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/collapseItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/countDown.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/countTo.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/datetimePicker.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/divider.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/empty.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/form.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/formItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/gap.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/grid.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/gridItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/icon.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/image.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/indexAnchor.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/indexItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/indexList.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/input.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/keyboard.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/line.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/lineProgress.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/link.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/list.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/listItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/loadMore.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/loadingIcon.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/loadingPage.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/modal.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/navbar.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/noNetwork.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/noticeBar.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/notify.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/numberBox.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/overlay.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/parse.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/picker.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/popup.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/radio.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/radioGroup.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/rate.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/readMore.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/row.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/scrollList.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/search.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/skeleton.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/slider.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/steps.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/stepsItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/sticky.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/subsection.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/swipeAction.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/swipeActionItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/swiper.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/swiperIndicator.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/switch.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/tabbar.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/tabbarItem.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/tabs.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/tag.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/text.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/textarea.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/toast.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/tooltip.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/transition.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/comps/upload.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/index.d.ts create mode 100644 im-uniapp/uni_modules/uview-plus/types/package.json create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/_metadata.json create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/chunk-Y2F7D3TJ.js create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/chunk-Y2F7D3TJ.js.map create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/package.json create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/pinyin-pro.js create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/pinyin-pro.js.map create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/quill-image-resize-mp.js create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/quill-image-resize-mp.js.map create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/quill.js create mode 100644 im-uniapp/unpackage/dist/cache/.vite/deps/quill.js.map create mode 100644 im-uniapp/unpackage/res/icons/1024x1024.png create mode 100644 im-uniapp/unpackage/res/icons/120x120.png create mode 100644 im-uniapp/unpackage/res/icons/144x144.png create mode 100644 im-uniapp/unpackage/res/icons/152x152.png create mode 100644 im-uniapp/unpackage/res/icons/167x167.png create mode 100644 im-uniapp/unpackage/res/icons/180x180.png create mode 100644 im-uniapp/unpackage/res/icons/192x192.png create mode 100644 im-uniapp/unpackage/res/icons/20x20.png create mode 100644 im-uniapp/unpackage/res/icons/29x29.png create mode 100644 im-uniapp/unpackage/res/icons/40x40.png create mode 100644 im-uniapp/unpackage/res/icons/58x58.png create mode 100644 im-uniapp/unpackage/res/icons/60x60.png create mode 100644 im-uniapp/unpackage/res/icons/72x72.png create mode 100644 im-uniapp/unpackage/res/icons/76x76.png create mode 100644 im-uniapp/unpackage/res/icons/80x80.png create mode 100644 im-uniapp/unpackage/res/icons/87x87.png create mode 100644 im-uniapp/unpackage/res/icons/96x96.png create mode 100644 im-uniapp/vite.config.js create mode 100644 im-web/.env.development create mode 100644 im-web/.env.production create mode 100644 im-web/README.md create mode 100644 im-web/babel.config.js create mode 100644 im-web/package.json create mode 100644 im-web/public/index.html create mode 100644 im-web/public/logo.png create mode 100644 im-web/src/App.vue create mode 100644 im-web/src/api/camera.js create mode 100644 im-web/src/api/date.js create mode 100644 im-web/src/api/element.js create mode 100644 im-web/src/api/emotion.js create mode 100644 im-web/src/api/enums.js create mode 100644 im-web/src/api/eventBus.js create mode 100644 im-web/src/api/httpRequest.js create mode 100644 im-web/src/api/messageType.js create mode 100644 im-web/src/api/rtcGroupApi.js create mode 100644 im-web/src/api/rtcPrivateApi.js create mode 100644 im-web/src/api/str.js create mode 100644 im-web/src/api/url.js create mode 100644 im-web/src/api/webrtc.js create mode 100644 im-web/src/api/wssocket.js create mode 100644 im-web/src/assets/audio/call.wav create mode 100644 im-web/src/assets/audio/tip.mp3 create mode 100644 im-web/src/assets/emoji/0.gif create mode 100644 im-web/src/assets/emoji/1.gif create mode 100644 im-web/src/assets/emoji/10.gif create mode 100644 im-web/src/assets/emoji/11.gif create mode 100644 im-web/src/assets/emoji/12.gif create mode 100644 im-web/src/assets/emoji/13.gif create mode 100644 im-web/src/assets/emoji/14.gif create mode 100644 im-web/src/assets/emoji/15.gif create mode 100644 im-web/src/assets/emoji/16.gif create mode 100644 im-web/src/assets/emoji/17.gif create mode 100644 im-web/src/assets/emoji/18.gif create mode 100644 im-web/src/assets/emoji/19.gif create mode 100644 im-web/src/assets/emoji/2.gif create mode 100644 im-web/src/assets/emoji/20.gif create mode 100644 im-web/src/assets/emoji/21.gif create mode 100644 im-web/src/assets/emoji/22.gif create mode 100644 im-web/src/assets/emoji/23.gif create mode 100644 im-web/src/assets/emoji/24.gif create mode 100644 im-web/src/assets/emoji/25.gif create mode 100644 im-web/src/assets/emoji/26.gif create mode 100644 im-web/src/assets/emoji/27.gif create mode 100644 im-web/src/assets/emoji/28.gif create mode 100644 im-web/src/assets/emoji/29.gif create mode 100644 im-web/src/assets/emoji/3.gif create mode 100644 im-web/src/assets/emoji/30.gif create mode 100644 im-web/src/assets/emoji/31.gif create mode 100644 im-web/src/assets/emoji/32.gif create mode 100644 im-web/src/assets/emoji/33.gif create mode 100644 im-web/src/assets/emoji/34.gif create mode 100644 im-web/src/assets/emoji/35.gif create mode 100644 im-web/src/assets/emoji/36.gif create mode 100644 im-web/src/assets/emoji/37.gif create mode 100644 im-web/src/assets/emoji/38.gif create mode 100644 im-web/src/assets/emoji/39.gif create mode 100644 im-web/src/assets/emoji/4.gif create mode 100644 im-web/src/assets/emoji/40.gif create mode 100644 im-web/src/assets/emoji/41.gif create mode 100644 im-web/src/assets/emoji/42.gif create mode 100644 im-web/src/assets/emoji/43.gif create mode 100644 im-web/src/assets/emoji/44.gif create mode 100644 im-web/src/assets/emoji/45.gif create mode 100644 im-web/src/assets/emoji/46.gif create mode 100644 im-web/src/assets/emoji/47.gif create mode 100644 im-web/src/assets/emoji/48.gif create mode 100644 im-web/src/assets/emoji/49.gif create mode 100644 im-web/src/assets/emoji/5.gif create mode 100644 im-web/src/assets/emoji/50.gif create mode 100644 im-web/src/assets/emoji/51.gif create mode 100644 im-web/src/assets/emoji/52.gif create mode 100644 im-web/src/assets/emoji/53.gif create mode 100644 im-web/src/assets/emoji/54.gif create mode 100644 im-web/src/assets/emoji/55.gif create mode 100644 im-web/src/assets/emoji/56.gif create mode 100644 im-web/src/assets/emoji/6.gif create mode 100644 im-web/src/assets/emoji/7.gif create mode 100644 im-web/src/assets/emoji/8.gif create mode 100644 im-web/src/assets/emoji/9.gif create mode 100644 im-web/src/assets/iconfont/iconfont.css create mode 100644 im-web/src/assets/iconfont/iconfont.ttf create mode 100644 im-web/src/assets/image/icp_logo.png create mode 100644 im-web/src/assets/style/element.scss create mode 100644 im-web/src/assets/style/im.scss create mode 100644 im-web/src/assets/style/thems.scss create mode 100644 im-web/src/components/chat/ChatAtBox.vue create mode 100644 im-web/src/components/chat/ChatBox.vue create mode 100644 im-web/src/components/chat/ChatGroupMember.vue create mode 100644 im-web/src/components/chat/ChatGroupReaded.vue create mode 100644 im-web/src/components/chat/ChatGroupSide.vue create mode 100644 im-web/src/components/chat/ChatHistory.vue create mode 100644 im-web/src/components/chat/ChatInput.vue create mode 100644 im-web/src/components/chat/ChatItem.vue create mode 100644 im-web/src/components/chat/ChatMessageItem.vue create mode 100644 im-web/src/components/chat/ChatRecord.vue create mode 100644 im-web/src/components/common/Emotion.vue create mode 100644 im-web/src/components/common/FileUpload.vue create mode 100644 im-web/src/components/common/FullImage.vue create mode 100644 im-web/src/components/common/HeadImage.vue create mode 100644 im-web/src/components/common/Icp.vue create mode 100644 im-web/src/components/common/ResizableAside.vue create mode 100644 im-web/src/components/common/RightMenu.vue create mode 100644 im-web/src/components/common/UserInfo.vue create mode 100644 im-web/src/components/common/VirtualScroller.vue create mode 100644 im-web/src/components/friend/AddFriend.vue create mode 100644 im-web/src/components/friend/FriendItem.vue create mode 100644 im-web/src/components/group/AddGroupMember.vue create mode 100644 im-web/src/components/group/GroupItem.vue create mode 100644 im-web/src/components/group/GroupMember.vue create mode 100644 im-web/src/components/group/GroupMemberItem.vue create mode 100644 im-web/src/components/group/GroupMemberSelector.vue create mode 100644 im-web/src/components/rtc/RtcGroupJoin.vue create mode 100644 im-web/src/components/rtc/RtcGroupVideo.vue create mode 100644 im-web/src/components/rtc/RtcPrivateAcceptor.vue create mode 100644 im-web/src/components/rtc/RtcPrivateVideo.vue create mode 100644 im-web/src/components/setting/Setting.vue create mode 100644 im-web/src/main.js create mode 100644 im-web/src/router/index.js create mode 100644 im-web/src/store/chatStore.js create mode 100644 im-web/src/store/configStore.js create mode 100644 im-web/src/store/friendStore.js create mode 100644 im-web/src/store/groupStore.js create mode 100644 im-web/src/store/userStore.js create mode 100644 im-web/src/utils/directive/dialogDrag.js create mode 100644 im-web/src/view/Chat.vue create mode 100644 im-web/src/view/Friend.vue create mode 100644 im-web/src/view/Group.vue create mode 100644 im-web/src/view/Home.vue create mode 100644 im-web/src/view/Login.vue create mode 100644 im-web/src/view/Register.vue create mode 100644 im-web/vue.config.js create mode 100644 pom.xml create mode 100644 截图/app/1.png create mode 100644 截图/app/2.png create mode 100644 截图/h5二维码.png create mode 100644 截图/web/单人通话.jpg create mode 100644 截图/web/多人通话.jpg create mode 100644 截图/web/好友.jpg create mode 100644 截图/web/私聊.jpg create mode 100644 截图/web/群列表.jpg create mode 100644 截图/web/群聊.jpg create mode 100644 截图/消息推送集群化.jpg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09144bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/.idea/ +/box-im.iml +/im-server/im-server.iml +/im-platform/im-platform.iml +/im-client/im-client.iml +/im-common/im-common.iml +/im-server/target/ +/im-platform/target/ +/im-client/target/ +/im-common/target/ +/im-web/node_modules/ +/im-web/dist/ +/im-uniapp/node_modules/ +/im-uniapp/package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edc4cb5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 blue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..96a5f4c --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ + +### **盒子IM** +![MIT协议](https://img.shields.io/badge/license-MIT-red) +[![star](https://gitee.com/bluexsx/box-im/badge/star.svg)](https://gitee.com/bluexsx/box-im) +[![star](https://img.shields.io/github/stars/bluexsx/box-im.svg?style=flat&logo=GitHub)](https://github.com/bluexsx/box-im) + + +1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。 +1. 支持私聊、群聊、离线消息、发送语音、图片、文件、已读未读、群@等功能 +1. 支持音视频通话(基于原生webrtc实现,需要ssl证书) +1. uniapp端兼容app、h5、微信小程序,可与web端同时在线,并保持消息同步 +1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp +1. 服务器支持集群化部署,具有良好的横向扩展能力 + + +详细文档:https://www.yuque.com/u1475064/mufu2a +官方论坛: https://bbs.boxim.online + +#### 近期更新 +发布3.0版本: + +- 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin +- 框架和组件版本全面升级: jdk17、springboot3.3、node18等 +- 部分界面,功能、性能优化 + + +#### 在线体验 + +网页端:https://www.boxim.online + +移动安卓端:已上架至各大主流手机应用市场以及腾讯应用宝,搜索"盒子IM",下载安装即可 + +移动ios端: 已上架至app store,搜索"盒子IM",下载安装即可 + +小程序段: 已上架至微信小程序,搜索“盒子IM”进入即可 + +移动H5端: https://www.boxim.online/h5/ ,或扫码: + +![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png) + +测试账号:张三/Aa123123 李四/Aa123123,也可以自行注册 + +说明: +1.**请勿利用测试账号辱骂他人、发布低俗内容,否则将直接对您的IP进行封禁** +2.由于部分厂商上架审核要求实名制,app端隐藏了"用户名注册"注册通道,可通过长按注册页面蓝色文字标题解除限制 +3.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序 +4.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考: +https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq + + +#### 付费服务 +商业版源码: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29 +远程协助: https://www.yuque.com/u1475064/imk5n2/fettd57rvzc29s5r +环境搭建: https://www.yuque.com/u1475064/imk5n2/qgq5cvgmavallqnl + + +#### 项目结构 +| 模块 | 功能 | +|-------------|----------------------------------| +| im-platform | 业务平台服务,负责处理来自用户的业务请求(http) | +| im-server | 消息推送服务,不依赖业务,负责将消息推送给用户(ws) | +| im-client | 消息推送sdk, 其他服务可集成此sdk与im-server通信 | +| im-common | 公共包,后端服务均依赖此包 | +| im-web | web页面 | +| im-uniapp | uniapp页面,可打包成app、h5、微信小程序 | + +#### 消息推送方案 +当消息的发送者和接收者连的不是同一个server时,消息是无法直接推送的,所以我们设计出了能够支持跨节点推送的方案: +![输入图片说明](%E6%88%AA%E5%9B%BE/%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81%E9%9B%86%E7%BE%A4%E5%8C%96.jpg) + +- 利用了redis的list数据实现消息推送,其中key为im:unread:${serverid},每个key的数据可以看做一个queue,每个im-server根据自身的id只消费属于自己的queue +- redis记录了每个用户的websocket连接的是哪个im-server,当用户发送消息时,im-platform将根据所连接的im-server的id,决定将消息推向哪个queue + + +#### 本地启动 +1.安装运行环境 +- 安装node:v18.19.0 +- 安装jdk:17 +- 安装maven:3.9.6 +- 安装mysql:8.0,账号密码分别为root/root,创建名为im_platform的数据库,运行db/im_platfrom.sql脚本 +- 安装redis:6.2 +- 安装minio:RELEASE.2024-xx,使用默认账号、密码、端口 + +2.启动后端服务 +``` +mvn clean package +java -jar ./im-platform/target/im-platform.jar +java -jar ./im-server/target/im-server.jar +``` + +3.启动前端web +``` +cd im-web +npm install +npm run serve +``` +访问 http://localhost:8080 + +4.启动uniapp-h5 +将im-uniapp目录导入HBuilderX,点击菜单"运行"->"开发环境-h5" +访问 http://localhost:5173 + + +#### 界面截图 +私聊: +![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%A7%81%E8%81%8A.jpg) + +群聊: +![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A.jpg) + +群通话(商业版): +![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A4%9A%E4%BA%BA%E9%80%9A%E8%AF%9D.jpg) + +好友列表: +![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A5%BD%E5%8F%8B.jpg) + +群列表: +![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E5%88%97%E8%A1%A8.jpg) + +移动端APP: +![输入图片说明](%E6%88%AA%E5%9B%BE/app/1.png) + + +![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.png) + +#### 加入交流群 +群1: 741174521(已满) +群2: 937470451(已满) +群3: 1012017031(已满) +群4: 765849133 + +欢迎进群与小伙们一起交流, **申请加群前请务必先star哦** + + +#### 点下star吧 +如果项目对您有帮助,请点亮右上方的star,支持一下作者吧! + + +#### 说明几点 + +1. 本系统允许用于商业用途,且不收费,**但切记不要用于任何非法用途** ,本软件作者不会为此承担任何责任 +1. 基于本系统二次开发后再次开源的项目,请注明引用出处,以避免引发不必要的误会 +1. 为方便管理,要pr的同学请将代码提交到v_3.0.0分支,作者会在功能上线时合并到master分支 +1. 允许商用不代表可以随意倒卖开源代码,尤其是代码、名字、截图一点都不改就拿去卖钱的,我劝你们善良一点! + diff --git a/db/im-platform.sql b/db/im-platform.sql new file mode 100644 index 0000000..20d1c71 --- /dev/null +++ b/db/im-platform.sql @@ -0,0 +1,112 @@ +create table `im_user`( + `id` bigint not null auto_increment primary key comment 'id', + `user_name` varchar(255) not null comment '用户名', + `nick_name` varchar(255) not null comment '用户昵称', + `head_image` varchar(255) default '' comment '用户头像', + `head_image_thumb` varchar(255) default '' comment '用户头像缩略图', + `password` varchar(255) not null comment '密码', + `sex` tinyint(1) default 0 comment '性别 0:男 1:女', + `is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是', + `reason` varchar(255) default '' comment '被封禁原因', + `type` smallint default 1 comment '用户类型 1:普通用户 2:审核账户', + `signature` varchar(1024) default '' comment '个性签名', + `last_login_time` datetime DEFAULT null comment '最后登录时间', + `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', + unique key `idx_user_name`(user_name), + key `idx_nick_name`(nick_name) +) ENGINE=InnoDB CHARSET=utf8mb4 comment '用户'; + +create table `im_friend`( + `id` bigint not null auto_increment primary key comment 'id', + `user_id` bigint not null comment '用户id', + `friend_id` bigint not null comment '好友id', + `friend_nick_name` varchar(255) not null comment '好友昵称', + `friend_head_image` varchar(255) default '' comment '好友头像', + `is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启', + `deleted` tinyint comment '删除标识 0:正常 1:已删除', + `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', + key `idx_user_id` (`user_id`), + key `idx_friend_id` (`friend_id`) +) ENGINE=InnoDB CHARSET=utf8mb4 comment '好友'; + +create table `im_private_message`( + `id` bigint not null auto_increment primary key comment 'id', + `tmp_id` varchar(32) comment '临时id,由前端生成', + `send_id` bigint not null comment '发送用户id', + `recv_id` bigint not null comment '接收用户id', + `content` text comment '发送内容', + `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示', + `status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 2:撤回 3:已读', + `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', + key `idx_send_id` (`send_id`), + key `idx_recv_id` (`recv_id`) +)ENGINE=InnoDB CHARSET=utf8mb4 comment '私聊消息'; + + +create table `im_group`( + `id` bigint not null auto_increment primary key comment 'id', + `name` varchar(255) not null comment '群名字', + `owner_id` bigint not null comment '群主id', + `head_image` varchar(255) default '' comment '群头像', + `head_image_thumb` varchar(255) default '' comment '群头像缩略图', + `notice` varchar(1024) default '' comment '群公告', + `is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是', + `reason` varchar(255) default '' comment '被封禁原因', + `dissolve` tinyint(1) default 0 comment '是否已解散', + `created_time` datetime default CURRENT_TIMESTAMP comment '创建时间' +)ENGINE=InnoDB CHARSET=utf8mb4 comment '群'; + +create table `im_group_member`( + `id` bigint not null auto_increment primary key comment 'id', + `group_id` bigint not null comment '群id', + `user_id` bigint not null comment '用户id', + `user_nick_name` varchar(255) DEFAULT '' comment '用户昵称', + `remark_nick_name` varchar(255) DEFAULT '' comment '显示昵称备注', + `head_image` varchar(255) DEFAULT '' comment '用户头像', + `remark_group_name` varchar(255) DEFAULT '' comment '显示群名备注', + `is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启', + `quit` tinyint(1) DEFAULT 0 comment '是否已退出', + `quit_time` datetime DEFAULT NULL comment '退出时间', + `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', + key `idx_group_id`(`group_id`), + key `idx_user_id`(`user_id`) +)ENGINE=InnoDB CHARSET=utf8mb4 comment '群成员'; + +create table `im_group_message`( + `id` bigint not null auto_increment primary key comment 'id', + `tmp_id` varchar(32) comment '临时id,由前端生成', + `group_id` bigint not null comment '群id', + `send_id` bigint not null comment '发送用户id', + `send_nick_name` varchar(255) DEFAULT '' comment '发送用户昵称', + `recv_ids` varchar(1024) DEFAULT '' comment '接收用户id,逗号分隔,为空表示发给所有成员', + `content` text comment '发送内容', + `at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔', + `receipt` tinyint DEFAULT 0 comment '是否回执消息', + `receipt_ok` tinyint DEFAULT 0 comment '回执消息是否完成', + `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示' , + `status` tinyint(1) DEFAULT 0 comment '状态 0:未发出 2:撤回 ', + `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', + key `idx_group_id` (group_id) +)ENGINE=InnoDB CHARSET=utf8mb4 comment '群消息'; + +create table `im_sensitive_word`( + `id` bigint not null auto_increment primary key comment 'id', + `content` varchar(64) not null comment '敏感词内容', + `enabled` tinyint DEFAULT 0 COMMENT '是否启用 0:未启用 1:启用', + `creator` bigint DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' +)ENGINE=InnoDB CHARSET=utf8mb4 comment '敏感词'; + +CREATE TABLE `im_file_info` ( + `id` BIGINT NOT NULL auto_increment PRIMARY key comment 'id', + `file_name` VARCHAR(255) NOT NULL comment '文件名', + `file_path` VARCHAR(255) NOT NULL comment '文件地址', + `file_size` INTEGER NOT NULL comment '文件大小', + `file_type` tinyint NOT NULL comment '0:普通文件 1:图片 2:视频', + `compressed_path` VARCHAR(255) DEFAULT NULL comment '压缩文件路径', + `cover_path` VARCHAR(255) DEFAULT NULL comment '封面文件路径,仅视频文件有效', + `upload_time` datetime DEFAULT CURRENT_TIMESTAMP comment '上传时间', + `is_permanent` tinyint DEFAULT 0 comment '是否永久文件', + `md5` VARCHAR(64) NOT NULL comment '文件md5', + KEY `idx_md5` (md5) +) ENGINE = InnoDB CHARSET = utf8mb4 comment '文件'; \ No newline at end of file diff --git a/db/敏感词库初始化.sql b/db/敏感词库初始化.sql new file mode 100644 index 0000000..f6e46aa --- /dev/null +++ b/db/敏感词库初始化.sql @@ -0,0 +1,470 @@ +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爱液', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('按摩棒', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('拔出来', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆草', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('包二奶', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴干', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('暴淫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屄', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被操', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被插', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('被干', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('逼奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('仓井空', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插暴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操黑', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操烂', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肏你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肏死', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操死', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操我', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('厕奴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插比', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插进', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插我', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('插阴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('潮吹', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('潮喷', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人论坛', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小说', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人卡通', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人聊', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人视', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人图', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人电影', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人论坛', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人色情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人网站', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人小说', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('艳情小说', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人游戏', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('吃精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('赤裸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抽插', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('扌由插', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抽一插', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('春药', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大波', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大力抽送', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('大乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('荡妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('荡女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('盗撮', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('多人轮', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发浪', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('放尿', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肥逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('粉穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('封面女郎', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('风月大陆', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干死你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肛交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肛门', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('龟头', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裹本', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('好嫩', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('豪乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('黑逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('后庭', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('后穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('虎骑', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('花花公子', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('换妻俱乐部', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('黄片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('几吧', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡吧', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡巴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('鸡奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('寂寞男', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('寂寞女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妓女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('激情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('集体淫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奸情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('叫床', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脚交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('精液', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('就去日', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨屌', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊花洞', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊门', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨奶', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('巨乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('菊穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('开苞', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口爆', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口活', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口射', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('口淫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裤袜', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狂操', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狂插', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪叫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浪女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狼友', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('聊性', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('流淫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('铃木麻', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('凌辱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('漏乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮暴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮操', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('轮奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸陪', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('买春', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美少妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美腿', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美幼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('秘唇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('迷奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('密穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('蜜穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('蜜液', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('摸奶', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('摸胸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('母奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奈美', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('奶子', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('男奴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('内射', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('嫩穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('捏弄', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('女优', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('炮友', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('砲友', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('喷精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屁眼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('品香堂', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('前凸后翘', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强暴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强奸处女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('情趣用品', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('情色', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('拳交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全裸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('群交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('惹火身材', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人妻', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人兽', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日烂', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉棒', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉唇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉洞', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉缝', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉棍', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉茎', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉具', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('揉乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('肉欲', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳爆', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳房', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳沟', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乳头', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('三级片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚比', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚水', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('骚穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色界', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色猫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色盟', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色情网站', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色区', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色诱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('色欲', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射爽', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射颜', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('食精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('释欲', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('手淫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽欲', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟母', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('熟女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爽片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爽死我了', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('双臀', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('死逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('丝袜', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('丝诱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('松岛枫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('酥痒', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('汤加丽', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('套弄', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('体奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('体位', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舔脚', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舔阴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('调教', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('偷欢', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('偷拍', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('推油', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脱内裤', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('文做', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我就色', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无码', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('舞女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无修正', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('吸精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('夏川纯', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('相奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('小逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('校鸡', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('小穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('写真', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性感妖娆', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性感诱惑', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虎', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性饥渴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性技巧', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虐', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性息', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性欲', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('胸推', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('穴口', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('学生妹', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('穴图', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('亚情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('颜射', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阳具', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('杨思敏', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('要射了', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('夜勤病栋', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一本道', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一夜欢', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一夜情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴部', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫虫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴唇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴道', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫电影', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴阜', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫河', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴核', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴户', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫贱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫叫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫教师', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫浪', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫媚', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫糜', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫母', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫虐', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫妻', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫色', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫声浪语', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫兽学园', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫书', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫术炼金士', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫水', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫娃', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫威', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫亵', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫样', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫液', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫照', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('应召', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼男', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('幼女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲火', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉女心经', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉蒲团', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉乳', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欲仙欲死', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('玉穴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('援交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('原味内衣', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('援助交际', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('张筱雨', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('招鸡', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('招妓', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('中年美妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('抓胸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('自拍', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('自慰', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('作爱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱人', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('傻逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('煞逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('煞笔', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('刹笔', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('傻比', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('沙比', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('欠干', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('婊子养的', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我日你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我操', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我草', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('卧艹', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('卧槽', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('爆你菊', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('艹你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你他妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('真他妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('别他吗', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('草你吗', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('草你丫', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('擦你妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你娘', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操他妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('日你妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你妈', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你娘', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('娘西皮', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗操', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗草', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗杂种', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗日的', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你祖宗', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你全家', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('操你大爷', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妈逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你麻痹', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('麻痹的', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('妈了个逼', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('马勒', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('狗娘养', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱比', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('下贱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('死全家', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家死光', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家不得好死', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('全家死绝', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('白痴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('无耻', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('你妈的', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('婊子', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('贱货', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('人渣', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('混蛋', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('媚外', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('和弦', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兼职', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('限量', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('铃声', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性伴侣', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('男公关', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('火辣', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('精子', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('射精', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('诱奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('强奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('做爱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性爱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发生关系', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('按摩', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('快感', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('处男', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('猛男', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('少妇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屌', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('屁股', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('下体', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('内裤', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('浑圆', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('咪咪', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('发情', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('刺激', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('白嫩', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('粉嫩', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('兽性', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('风骚', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('呻吟', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阉割', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('高潮', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸露', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('不穿', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('一丝不挂', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('脱光', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干你', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('干死', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('我干', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裙中性运动', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱奸', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦类', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('乱伦小', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理大', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理电影', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理毛', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('伦理片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸聊', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸聊网', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸体写真', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸舞视', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('裸照', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('美女裸体', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交视频', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性交图片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性奴集中营', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('性虐', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴唇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴道', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴蒂', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴户', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴间来电', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎增大', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴茎助勃', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('阴毛', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰唇', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰道', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('陰戶', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡美女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡视频', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫荡照片', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫乱', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫靡', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫魔舞', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫情女', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫肉', 1, 10000); +INSERT INTO `im_sensitive_word` (`content`, `enabled`, `creator`) VALUES ('淫騷妹', 1, 10000); + + diff --git a/im-client/pom.xml b/im-client/pom.xml new file mode 100644 index 0000000..dbe3d30 --- /dev/null +++ b/im-client/pom.xml @@ -0,0 +1,21 @@ + + + + box-im + com.bx + 3.0.0 + + 4.0.0 + + im-client + + + + com.bx + im-common + 3.0.0 + + + \ No newline at end of file diff --git a/im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java b/im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java new file mode 100644 index 0000000..c172099 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java @@ -0,0 +1,13 @@ +package com.bx.imclient; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + + +@Slf4j +@Configuration +@ComponentScan(basePackages = {"com.bx.imclient", "com.bx.imcommon"}) +public class IMAutoConfiguration { + +} diff --git a/im-client/src/main/java/com/bx/imclient/IMClient.java b/im-client/src/main/java/com/bx/imclient/IMClient.java new file mode 100644 index 0000000..1630d64 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/IMClient.java @@ -0,0 +1,92 @@ +package com.bx.imclient; + +import com.bx.imclient.sender.IMSender; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMGroupMessage; +import com.bx.imcommon.model.IMPrivateMessage; +import com.bx.imcommon.model.IMSystemMessage; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.Map; + +@Configuration +@AllArgsConstructor +public class IMClient { + + private final IMSender imSender; + + /** + * 判断用户是否在线 + * + * @param userId 用户id + */ + public Boolean isOnline(Long userId){ + return imSender.isOnline(userId); + } + + /** + * 判断用户是否在线 + * + * @param userId 用户id + * @param terminal 终端可惜 + */ + public Boolean isOnline(Long userId,IMTerminalType terminal){ + return imSender.isOnline(userId,terminal); + } + + + /** + * 判断多个用户是否在线 + * + * @param userIds 用户id列表 + * @return 在线的用户列表 + */ + public List getOnlineUser(List userIds){ + return imSender.getOnlineUser(userIds); + } + + + /** + * 判断多个用户是否在线 + * + * @param userIds 用户id列表 + * @return 在线的用户终端 + */ + public Map> getOnlineTerminal(List userIds){ + return imSender.getOnlineTerminal(userIds); + } + + /** + * 发送系统消息(发送结果通过MessageListener接收) + * + * @param message 私有消息 + */ + public void sendSystemMessage(IMSystemMessage message){ + imSender.sendSystemMessage(message); + } + + + /** + * 发送私聊消息(发送结果通过MessageListener接收) + * + * @param message 私有消息 + */ + public void sendPrivateMessage(IMPrivateMessage message){ + imSender.sendPrivateMessage(message); + } + + /** + * 发送群聊消息(发送结果通过MessageListener接收) + * + * @param message 群聊消息 + */ + public void sendGroupMessage(IMGroupMessage message){ + imSender.sendGroupMessage(message); + } + + + + +} diff --git a/im-client/src/main/java/com/bx/imclient/annotation/IMListener.java b/im-client/src/main/java/com/bx/imclient/annotation/IMListener.java new file mode 100644 index 0000000..4f54bf9 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/annotation/IMListener.java @@ -0,0 +1,18 @@ +package com.bx.imclient.annotation; + +import com.bx.imcommon.enums.IMListenerType; +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE,ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface IMListener { + + IMListenerType type(); + +} diff --git a/im-client/src/main/java/com/bx/imclient/listener/MessageListener.java b/im-client/src/main/java/com/bx/imclient/listener/MessageListener.java new file mode 100644 index 0000000..0ee2af3 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/listener/MessageListener.java @@ -0,0 +1,12 @@ +package com.bx.imclient.listener; + + +import com.bx.imcommon.model.IMSendResult; + +import java.util.List; + +public interface MessageListener { + + void process(List> result); + +} diff --git a/im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java b/im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java new file mode 100644 index 0000000..bd95284 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java @@ -0,0 +1,46 @@ +package com.bx.imclient.listener; + + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSONObject; +import com.bx.imclient.annotation.IMListener; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.model.IMSendResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +@Component +public class MessageListenerMulticaster { + + @Autowired(required = false) + private List messageListeners = Collections.emptyList(); + + public void multicast(IMListenerType listenerType, List results){ + if(CollUtil.isEmpty(results)){ + return; + } + for(MessageListener listener:messageListeners){ + IMListener annotation = listener.getClass().getAnnotation(IMListener.class); + if(annotation!=null && (annotation.type().equals(IMListenerType.ALL) || annotation.type().equals(listenerType))){ + results.forEach(result->{ + // 将data转回对象类型 + if(result.getData() instanceof JSONObject){ + Type superClass = listener.getClass().getGenericInterfaces()[0]; + Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; + JSONObject data = (JSONObject)result.getData(); + result.setData(data.toJavaObject(type)); + } + }); + // 回调到调用方处理 + listener.process(results); + } + } + } + + +} diff --git a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java new file mode 100644 index 0000000..1ced811 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java @@ -0,0 +1,262 @@ +package com.bx.imclient.sender; + +import cn.hutool.core.collection.CollUtil; +import com.bx.imclient.listener.MessageListenerMulticaster; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.*; +import com.bx.imcommon.mq.RedisMQTemplate; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +public class IMSender { + + @Autowired + private RedisMQTemplate redisMQTemplate; + + @Value("${spring.application.name}") + private String appName; + + private final MessageListenerMulticaster listenerMulticaster; + + + public void sendSystemMessage(IMSystemMessage message){ + // 根据群聊每个成员所连的IM-server,进行分组 + Map sendMap = new HashMap<>(); + for (Integer terminal : message.getRecvTerminals()) { + message.getRecvIds().forEach(id -> { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString()); + sendMap.put(key,new IMUserInfo(id, terminal)); + }); + } + // 批量拉取 + List serverIds = redisMQTemplate.opsForValue().multiGet(sendMap.keySet()); + // 格式:map<服务器id,list<接收方>> + Map> serverMap = new HashMap<>(); + List offLineUsers = new LinkedList<>(); + int idx = 0; + for (Map.Entry entry : sendMap.entrySet()) { + Integer serverId = (Integer)serverIds.get(idx++); + if (!Objects.isNull(serverId)) { + List list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>()); + list.add(entry.getValue()); + } else { + // 加入离线列表 + offLineUsers.add(entry.getValue()); + } + } + // 逐个server发送 + for (Map.Entry> entry : serverMap.entrySet()) { + IMRecvInfo recvInfo = new IMRecvInfo(); + recvInfo.setCmd(IMCmdType.SYSTEM_MESSAGE.code()); + recvInfo.setReceivers(new LinkedList<>(entry.getValue())); + recvInfo.setServiceName(appName); + recvInfo.setSendResult(message.getSendResult()); + recvInfo.setData(message.getData()); + // 推送至队列 + String key = String.join(":", IMRedisKey.IM_MESSAGE_SYSTEM_QUEUE, entry.getKey().toString()); + redisMQTemplate.opsForList().rightPush(key, recvInfo); + } + // 对离线用户回复消息状态 + if(message.getSendResult() && !offLineUsers.isEmpty()){ + List results = new LinkedList<>(); + for (IMUserInfo offLineUser : offLineUsers) { + IMSendResult result = new IMSendResult(); + result.setReceiver(offLineUser); + result.setCode(IMSendCode.NOT_ONLINE.code()); + result.setData(message.getData()); + results.add(result); + } + listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results); + } + } + + + public void sendPrivateMessage(IMPrivateMessage message) { + List results = new LinkedList<>(); + if(!Objects.isNull(message.getRecvId())){ + for (Integer terminal : message.getRecvTerminals()) { + // 获取对方连接的channelId + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString()); + Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key); + // 如果对方在线,将数据存储至redis,等待拉取推送 + if (serverId != null) { + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); + IMRecvInfo recvInfo = new IMRecvInfo(); + recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code()); + recvInfo.setSendResult(message.getSendResult()); + recvInfo.setServiceName(appName); + recvInfo.setSender(message.getSender()); + recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal))); + recvInfo.setData(message.getData()); + redisMQTemplate.opsForList().rightPush(sendKey, recvInfo); + } else { + IMSendResult result = new IMSendResult(); + result.setSender(message.getSender()); + result.setReceiver(new IMUserInfo(message.getRecvId(), terminal)); + result.setCode(IMSendCode.NOT_ONLINE.code()); + result.setData(message.getData()); + results.add(result); + } + } + } + + // 推送给自己的其他终端 + if(message.getSendToSelf()){ + for (Integer terminal : IMTerminalType.codes()) { + if (message.getSender().getTerminal().equals(terminal)) { + continue; + } + // 获取终端连接的channelId + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString()); + Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key); + // 如果终端在线,将数据存储至redis,等待拉取推送 + if (serverId != null) { + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); + IMRecvInfo recvInfo = new IMRecvInfo(); + // 自己的消息不需要回推消息结果 + recvInfo.setSendResult(false); + recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code()); + recvInfo.setSender(message.getSender()); + recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal))); + recvInfo.setData(message.getData()); + redisMQTemplate.opsForList().rightPush(sendKey, recvInfo); + } + } + } + // 对离线用户回复消息状态 + if(message.getSendResult() && !results.isEmpty()){ + listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results); + } + + } + + public void sendGroupMessage(IMGroupMessage message) { + // 根据群聊每个成员所连的IM-server,进行分组 + Map sendMap = new HashMap<>(); + for (Integer terminal : message.getRecvTerminals()) { + message.getRecvIds().forEach(id -> { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString()); + sendMap.put(key,new IMUserInfo(id, terminal)); + }); + } + // 批量拉取 + List serverIds = redisMQTemplate.opsForValue().multiGet(sendMap.keySet()); + // 格式:map<服务器id,list<接收方>> + Map> serverMap = new HashMap<>(); + List offLineUsers = new LinkedList<>(); + int idx = 0; + for (Map.Entry entry : sendMap.entrySet()) { + Integer serverId = (Integer)serverIds.get(idx++); + if (!Objects.isNull(serverId)) { + List list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>()); + list.add(entry.getValue()); + } else { + // 加入离线列表 + offLineUsers.add(entry.getValue()); + } + } + // 逐个server发送 + for (Map.Entry> entry : serverMap.entrySet()) { + IMRecvInfo recvInfo = new IMRecvInfo(); + recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code()); + recvInfo.setReceivers(new LinkedList<>(entry.getValue())); + recvInfo.setSender(message.getSender()); + recvInfo.setServiceName(appName); + recvInfo.setSendResult(message.getSendResult()); + recvInfo.setData(message.getData()); + // 推送至队列 + String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString()); + redisMQTemplate.opsForList().rightPush(key, recvInfo); + } + + // 推送给自己的其他终端 + if (message.getSendToSelf()) { + for (Integer terminal : IMTerminalType.codes()) { + if (terminal.equals(message.getSender().getTerminal())) { + continue; + } + // 获取终端连接的channelId + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString()); + Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key); + // 如果终端在线,将数据存储至redis,等待拉取推送 + if (!Objects.isNull(serverId)) { + IMRecvInfo recvInfo = new IMRecvInfo(); + recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code()); + recvInfo.setSender(message.getSender()); + recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal))); + // 自己的消息不需要回推消息结果 + recvInfo.setSendResult(false); + recvInfo.setData(message.getData()); + String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString()); + redisMQTemplate.opsForList().rightPush(sendKey, recvInfo); + } + } + } + // 对离线用户回复消息状态 + if(message.getSendResult() && !offLineUsers.isEmpty()){ + List results = new LinkedList<>(); + for (IMUserInfo offLineUser : offLineUsers) { + IMSendResult result = new IMSendResult(); + result.setSender(message.getSender()); + result.setReceiver(offLineUser); + result.setCode(IMSendCode.NOT_ONLINE.code()); + result.setData(message.getData()); + results.add(result); + } + listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results); + } + + } + + public Map> getOnlineTerminal(List userIds){ + if(CollUtil.isEmpty(userIds)){ + return Collections.emptyMap(); + } + // 把所有用户的key都存起来 + Map userMap = new HashMap<>(); + for(Long id:userIds){ + for (Integer terminal : IMTerminalType.codes()) { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString()); + userMap.put(key,new IMUserInfo(id,terminal)); + } + } + // 批量拉取 + List serverIds = redisMQTemplate.opsForValue().multiGet(userMap.keySet()); + int idx = 0; + Map> onlineMap = new HashMap<>(); + for (Map.Entry entry : userMap.entrySet()) { + // serverid有值表示用户在线 + if(serverIds.get(idx++) != null){ + IMUserInfo userInfo = entry.getValue(); + List terminals = onlineMap.computeIfAbsent(userInfo.getId(), o -> new LinkedList<>()); + terminals.add(IMTerminalType.fromCode(userInfo.getTerminal())); + } + } + // 去重并返回 + return onlineMap; + } + + public Boolean isOnline(Long userId, IMTerminalType terminal) { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.code().toString()); + return redisMQTemplate.hasKey(key); + } + + public Boolean isOnline(Long userId) { + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*"); + return !Objects.requireNonNull(redisMQTemplate.keys(key)).isEmpty(); + } + + public List getOnlineUser(List userIds){ + return new LinkedList<>(getOnlineTerminal(userIds).keySet()); + } +} diff --git a/im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java b/im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java new file mode 100644 index 0000000..346cfab --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java @@ -0,0 +1,21 @@ +package com.bx.imclient.task; + +import cn.hutool.core.util.StrUtil; +import com.bx.imcommon.mq.RedisMQConsumer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; + +@Slf4j +public abstract class AbstractMessageResultTask extends RedisMQConsumer { + + @Value("${spring.application.name}") + private String appName; + + @Override + public String generateKey() { + return StrUtil.join(":", super.generateKey(), appName); + } + + + +} diff --git a/im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java b/im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java new file mode 100644 index 0000000..c8be7e2 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java @@ -0,0 +1,27 @@ +package com.bx.imclient.task; + +import com.bx.imclient.listener.MessageListenerMulticaster; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.mq.RedisMQListener; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + + +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = IMRedisKey.IM_RESULT_GROUP_QUEUE, batchSize = 100) +public class GroupMessageResultResultTask extends AbstractMessageResultTask { + + private final MessageListenerMulticaster listenerMulticaster; + + @Override + public void onMessage(List results) { + listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results); + } + + +} diff --git a/im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java b/im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java new file mode 100644 index 0000000..38e5ac1 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java @@ -0,0 +1,26 @@ +package com.bx.imclient.task; + +import com.bx.imclient.listener.MessageListenerMulticaster; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.mq.RedisMQListener; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + + +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = IMRedisKey.IM_RESULT_PRIVATE_QUEUE, batchSize = 100) +public class PrivateMessageResultResultTask extends AbstractMessageResultTask { + + private final MessageListenerMulticaster listenerMulticaster; + + @Override + public void onMessage(List results) { + listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results); + } + +} diff --git a/im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java b/im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java new file mode 100644 index 0000000..a42ca94 --- /dev/null +++ b/im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java @@ -0,0 +1,25 @@ +package com.bx.imclient.task; + +import com.bx.imclient.listener.MessageListenerMulticaster; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.mq.RedisMQListener; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = IMRedisKey.IM_RESULT_SYSTEM_QUEUE, batchSize = 100) +public class SystemMessageResultResultTask extends AbstractMessageResultTask { + + private final MessageListenerMulticaster listenerMulticaster; + + @Override + public void onMessage(List results) { + listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results); + } + +} diff --git a/im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..fe31de9 --- /dev/null +++ b/im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.bx.imclient.IMAutoConfiguration \ No newline at end of file diff --git a/im-common/pom.xml b/im-common/pom.xml new file mode 100644 index 0000000..3e87e6f --- /dev/null +++ b/im-common/pom.xml @@ -0,0 +1,62 @@ + + + + box-im + com.bx + 3.0.0 + + 4.0.0 + im-common + jar + + + org.projectlombok + lombok + ${lombok.version} + + + cn.hutool + hutool-all + + + + com.alibaba + fastjson + + + org.apache.commons + commons-lang3 + + + + org.springframework + spring-beans + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + + + org.springframework + spring-context + + + + com.auth0 + java-jwt + 3.11.0 + + + + org.slf4j + slf4j-api + + + + org.springframework.boot + spring-boot-starter-data-redis + + + \ No newline at end of file diff --git a/im-common/src/main/java/com/bx/imcommon/contant/IMConstant.java b/im-common/src/main/java/com/bx/imcommon/contant/IMConstant.java new file mode 100644 index 0000000..40f3128 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/contant/IMConstant.java @@ -0,0 +1,18 @@ +package com.bx.imcommon.contant; + +public final class IMConstant { + + private IMConstant() { + } + + /** + * 在线状态过期时间 600s + */ + public static final long ONLINE_TIMEOUT_SECOND = 600; + /** + * 消息允许撤回时间 300s + */ + public static final long ALLOW_RECALL_SECOND = 300; + + +} diff --git a/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java b/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java new file mode 100644 index 0000000..a7a8bf3 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/contant/IMRedisKey.java @@ -0,0 +1,40 @@ +package com.bx.imcommon.contant; + +public final class IMRedisKey { + + + /** + * im-server最大id,从0开始递增 + */ + public static final String IM_MAX_SERVER_ID = "im:max_server_id"; + /** + * 用户ID所连接的IM-server的ID + */ + public static final String IM_USER_SERVER_ID = "im:user:server_id"; + /** + * 系统消息队列 + */ + public static final String IM_MESSAGE_SYSTEM_QUEUE = "im:message:system"; + /** + * 私聊消息队列 + */ + public static final String IM_MESSAGE_PRIVATE_QUEUE = "im:message:private"; + /** + * 群聊消息队列 + */ + public static final String IM_MESSAGE_GROUP_QUEUE = "im:message:group"; + + /** + * 系统消息发送结果队列 + */ + public static final String IM_RESULT_SYSTEM_QUEUE = "im:result:system"; + /** + * 私聊消息发送结果队列 + */ + public static final String IM_RESULT_PRIVATE_QUEUE = "im:result:private"; + /** + * 群聊消息发送结果队列 + */ + public static final String IM_RESULT_GROUP_QUEUE = "im:result:group"; + +} diff --git a/im-common/src/main/java/com/bx/imcommon/enums/IMCmdType.java b/im-common/src/main/java/com/bx/imcommon/enums/IMCmdType.java new file mode 100644 index 0000000..74793ec --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/enums/IMCmdType.java @@ -0,0 +1,55 @@ +package com.bx.imcommon.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum IMCmdType { + + /** + * 登录 + */ + LOGIN(0, "登录"), + /** + * 心跳 + */ + HEART_BEAT(1, "心跳"), + /** + * 强制下线 + */ + FORCE_LOGUT(2, "强制下线"), + /** + * 私聊消息 + */ + PRIVATE_MESSAGE(3, "私聊消息"), + /** + * 群发消息 + */ + GROUP_MESSAGE(4, "群发消息"), + /** + * 系统消息 + */ + SYSTEM_MESSAGE(5,"系统消息"); + + + private final Integer code; + + private final String desc; + + + public static IMCmdType fromCode(Integer code) { + for (IMCmdType typeEnum : values()) { + if (typeEnum.code.equals(code)) { + return typeEnum; + } + } + return null; + } + + + public Integer code() { + return this.code; + } + + +} + diff --git a/im-common/src/main/java/com/bx/imcommon/enums/IMListenerType.java b/im-common/src/main/java/com/bx/imcommon/enums/IMListenerType.java new file mode 100644 index 0000000..5dbb1b2 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/enums/IMListenerType.java @@ -0,0 +1,34 @@ +package com.bx.imcommon.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum IMListenerType { + /** + * 全部消息 + */ + ALL(0, "全部消息"), + /** + * 私聊消息 + */ + PRIVATE_MESSAGE(1, "私聊消息"), + /** + * 群聊消息 + */ + GROUP_MESSAGE(2, "群聊消息"), + /** + * 系统消息 + */ + SYSTEM_MESSAGE(3, "群聊消息"); + + + + private final Integer code; + + private final String desc; + + public Integer code() { + return this.code; + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/enums/IMSendCode.java b/im-common/src/main/java/com/bx/imcommon/enums/IMSendCode.java new file mode 100644 index 0000000..1babdc9 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/enums/IMSendCode.java @@ -0,0 +1,32 @@ +package com.bx.imcommon.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum IMSendCode { + + /** + * 发送成功 + */ + SUCCESS(0, "发送成功"), + /** + * 对方当前不在线 + */ + NOT_ONLINE(1, "对方当前不在线"), + /** + * 未找到对方的channel + */ + NOT_FIND_CHANNEL(2, "未找到对方的channel"), + /** + * 未知异常 + */ + UNKONW_ERROR(9999, "未知异常"); + + private final Integer code; + private final String desc; + + public Integer code() { + return this.code; + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java b/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java new file mode 100644 index 0000000..d5f5fac --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java @@ -0,0 +1,51 @@ +package com.bx.imcommon.enums; + +import lombok.AllArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +public enum IMTerminalType { + + /** + * web + */ + WEB(0, "web"), + /** + * app + */ + APP(1, "app"), + /** + * pc + */ + PC(2, "pc"), + /** + * 未知 + */ + UNKNOW(-1, "未知"); + + private final Integer code; + + private final String desc; + + + public static IMTerminalType fromCode(Integer code) { + for (IMTerminalType typeEnum : values()) { + if (typeEnum.code.equals(code)) { + return typeEnum; + } + } + return null; + } + + public static List codes() { + return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList()); + } + + public Integer code() { + return this.code; + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMGroupMessage.java b/im-common/src/main/java/com/bx/imcommon/model/IMGroupMessage.java new file mode 100644 index 0000000..05b3333 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMGroupMessage.java @@ -0,0 +1,44 @@ +package com.bx.imcommon.model; + +import com.bx.imcommon.enums.IMTerminalType; +import lombok.Data; + +import java.util.LinkedList; +import java.util.List; + +@Data +public class IMGroupMessage { + + /** + * 发送方 + */ + private IMUserInfo sender; + + /** + * 接收者id列表(群成员列表,为空则不会推送) + */ + private List recvIds = new LinkedList<>(); + + + /** + * 接收者终端类型,默认全部 + */ + private List recvTerminals = IMTerminalType.codes(); + + /** + * 是否发送给自己的其他终端,默认true + */ + private Boolean sendToSelf = true; + + /** + * 是否需要回推发送结果,默认true + */ + private Boolean sendResult = true; + + /** + * 消息内容 + */ + private T data; + + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java new file mode 100644 index 0000000..4b50271 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java @@ -0,0 +1,7 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +@Data +public class IMHeartbeatInfo { +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMLoginInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMLoginInfo.java new file mode 100644 index 0000000..afccfca --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMLoginInfo.java @@ -0,0 +1,9 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +@Data +public class IMLoginInfo { + + private String accessToken; +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java b/im-common/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java new file mode 100644 index 0000000..e33b540 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java @@ -0,0 +1,44 @@ +package com.bx.imcommon.model; + +import com.bx.imcommon.enums.IMTerminalType; +import lombok.Data; + +import java.util.List; + + +@Data +public class IMPrivateMessage { + + /** + * 发送方 + */ + private IMUserInfo sender; + + /** + * 接收者id + */ + private Long recvId; + + + /** + * 接收者终端类型,默认全部 + */ + private List recvTerminals = IMTerminalType.codes(); + + /** + * 是否同步消息给自己的其他终端,默认true + */ + private Boolean sendToSelf = true; + + /** + * 是否需要回推发送结果,默认true + */ + private Boolean sendResult = true; + + /** + * 消息内容 + */ + private T data; + + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMRecvInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMRecvInfo.java new file mode 100644 index 0000000..52c74e6 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMRecvInfo.java @@ -0,0 +1,40 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +import java.util.List; + +@Data +public class IMRecvInfo { + + /** + * 命令类型 IMCmdType + */ + private Integer cmd; + + /** + * 发送方 + */ + private IMUserInfo sender; + + /** + * 接收方用户列表 + */ + List receivers; + + /** + * 是否需要回调发送结果 + */ + private Boolean sendResult; + + /** + * 当前服务名(回调发送结果使用) + */ + private String serviceName; + /** + * 推送消息体 + */ + private Object data; +} + + diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMSendInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMSendInfo.java new file mode 100644 index 0000000..90d8522 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMSendInfo.java @@ -0,0 +1,18 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +@Data +public class IMSendInfo { + + /** + * 命令 + */ + private Integer cmd; + + /** + * 推送消息体 + */ + private T data; + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMSendResult.java b/im-common/src/main/java/com/bx/imcommon/model/IMSendResult.java new file mode 100644 index 0000000..9f4688c --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMSendResult.java @@ -0,0 +1,28 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +@Data +public class IMSendResult { + + /** + * 发送方 + */ + private IMUserInfo sender; + + /** + * 接收方 + */ + private IMUserInfo receiver; + + /** + * 发送状态编码 IMSendCode + */ + private Integer code; + + /** + * 消息内容 + */ + private T data; + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMSessionInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMSessionInfo.java new file mode 100644 index 0000000..ee7122e --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMSessionInfo.java @@ -0,0 +1,17 @@ +package com.bx.imcommon.model; + +import lombok.Data; + +@Data +public class IMSessionInfo { + /** + * 用户id + */ + private Long userId; + + /** + * 终端类型 + */ + private Integer terminal; + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMSystemMessage.java b/im-common/src/main/java/com/bx/imcommon/model/IMSystemMessage.java new file mode 100644 index 0000000..2241cb5 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMSystemMessage.java @@ -0,0 +1,34 @@ +package com.bx.imcommon.model; + +import com.bx.imcommon.enums.IMTerminalType; +import lombok.Data; + +import java.util.LinkedList; +import java.util.List; + +@Data +public class IMSystemMessage { + + + /** + * 接收者id列表,为空表示向所有在线用户广播 + */ + private List recvIds = new LinkedList<>(); + + /** + * 接收者终端类型,默认全部 + */ + private List recvTerminals = IMTerminalType.codes(); + + /** + * 是否需要回推发送结果,默认true + */ + private Boolean sendResult = true; + + /** + * 消息内容 + */ + private T data; + + +} diff --git a/im-common/src/main/java/com/bx/imcommon/model/IMUserInfo.java b/im-common/src/main/java/com/bx/imcommon/model/IMUserInfo.java new file mode 100644 index 0000000..680cda6 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/model/IMUserInfo.java @@ -0,0 +1,28 @@ +package com.bx.imcommon.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author: Blue + * @date: 2023-09-24 09:23:11 + * @version: 1.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IMUserInfo { + + /** + * 用户id + */ + private Long id; + + /** + * 用户终端类型 IMTerminalType + */ + private Integer terminal; + + +} diff --git a/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java new file mode 100644 index 0000000..99c5074 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java @@ -0,0 +1,32 @@ +package com.bx.imcommon.mq; + +import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.StringRedisSerializer; + + +@Configuration +public class RedisMQConfig { + + @Bean + public RedisMQTemplate redisMQTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisMQTemplate redisMQTemplate = new RedisMQTemplate(); + redisMQTemplate.setConnectionFactory(redisConnectionFactory); + // 设置值(value)的序列化采用FastJsonRedisSerializer + redisMQTemplate.setValueSerializer(fastJsonRedisSerializer()); + redisMQTemplate.setHashValueSerializer(fastJsonRedisSerializer()); + // 设置键(key)的序列化采用StringRedisSerializer。 + redisMQTemplate.setKeySerializer(new StringRedisSerializer()); + redisMQTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisMQTemplate.afterPropertiesSet(); + return redisMQTemplate; + } + + @Bean + public FastJsonRedisSerializer fastJsonRedisSerializer(){ + return new FastJsonRedisSerializer<>(Object.class); + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java new file mode 100644 index 0000000..cd997fd --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java @@ -0,0 +1,35 @@ +package com.bx.imcommon.mq; + +import java.util.List; + +/** + * redis 队列消费者抽象类 + */ +public abstract class RedisMQConsumer { + + /** + * 消费消息回调(单条) + */ + public void onMessage(T data){} + + /** + * 消费消息回调(批量) + */ + public void onMessage(List datas){} + + /** + * 生成redis队列完整key + */ + public String generateKey(){ + // 默认队列名就是redis的key + RedisMQListener annotation = this.getClass().getAnnotation(RedisMQListener.class); + return annotation.queue(); + } + + /** + * 队列是否就绪,返回true才会开始消费 + */ + public Boolean isReady(){ + return true; + } +} diff --git a/im-common/src/main/java/com/bx/imcommon/mq/RedisMQListener.java b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQListener.java new file mode 100644 index 0000000..eb5fd1e --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQListener.java @@ -0,0 +1,29 @@ +package com.bx.imcommon.mq; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * redis 队列消费监听注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RedisMQListener { + + /** + * 队列,也是redis的key + */ + String queue(); + + /** + * 一次性拉取的数据数量 + */ + int batchSize() default 1; + + /** + * 拉取间隔周期,单位:ms + */ + int period() default 100; +} diff --git a/im-common/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java new file mode 100644 index 0000000..a2ecded --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java @@ -0,0 +1,116 @@ +package com.bx.imcommon.mq; + +import com.alibaba.fastjson.JSONObject; +import com.bx.imcommon.util.ThreadPoolExecutorFactory; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * reids 队列拉取定时任务 + * + * @author: Blue + * @date: 2024-07-15 + * @version: 1.0 + */ +@Slf4j +@Component +public class RedisMQPullTask implements CommandLineRunner { + + private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor(); + + @Autowired(required = false) + private List consumers = Collections.emptyList(); + + @Autowired + private RedisMQTemplate redisMQTemplate; + + @Override + public void run(String... args) { + consumers.forEach((consumer -> { + // 注解参数 + RedisMQListener annotation = consumer.getClass().getAnnotation(RedisMQListener.class); + String queue = annotation.queue(); + int batchSize = annotation.batchSize(); + int period = annotation.period(); + // 获取泛型类型 + Type superClass = consumer.getClass().getGenericSuperclass(); + Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0]; + EXECUTOR.execute(new Runnable() { + @Override + public void run() { + List datas = new LinkedList<>(); + try { + if (consumer.isReady()) { + String key = consumer.generateKey(); + // 拉取一个批次的数据 + List objects = pullBatch(key, batchSize); + for (Object obj : objects) { + if (obj instanceof JSONObject) { + JSONObject jsonObject = (JSONObject)obj; + Object data = jsonObject.toJavaObject(type); + consumer.onMessage(data); + datas.add(data); + } + } + if (!datas.isEmpty()) { + consumer.onMessage(datas); + } + } + } catch (Exception e) { + log.error("数据消费异常,队列:{}", queue, e); + // 出现异常,10s后再重新尝试消费 + EXECUTOR.schedule(this, 10, TimeUnit.SECONDS); + return; + } + // 继续消费数据 + if (!EXECUTOR.isShutdown()) { + if (datas.size() < batchSize) { + // 数据已经消费完,等待下一个周期继续拉取 + EXECUTOR.schedule(this, period, TimeUnit.MILLISECONDS); + } else { + // 数据没有消费完,直接开启下一个消费周期 + EXECUTOR.execute(this); + } + } + } + }); + })); + } + + private List pullBatch(String key, Integer batchSize) { + List objects = new LinkedList<>(); + if (redisMQTemplate.isSupportBatchPull()) { + // 版本大于6.2,支持批量拉取 + objects = redisMQTemplate.opsForList().leftPop(key, batchSize); + } else { + // 版本小于6.2,只能逐条拉取 + Object obj = redisMQTemplate.opsForList().leftPop(key); + while (!Objects.isNull(obj) && objects.size() < batchSize) { + objects.add(obj); + obj = redisMQTemplate.opsForList().leftPop(key); + } + if (!Objects.isNull(obj)){ + objects.add(obj); + } + } + return objects; + } + + @PreDestroy + public void destory() { + log.info("消费线程停止..."); + ThreadPoolExecutorFactory.shutDown(); + } +} diff --git a/im-common/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java new file mode 100644 index 0000000..dbb927b --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java @@ -0,0 +1,44 @@ +package com.bx.imcommon.mq; + +import org.apache.logging.log4j.util.Strings; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisConnectionUtils; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.Properties; + +/** + * @author: Blue + * @date: 2024-07-16 + * @version: 1.0 + */ +public class RedisMQTemplate extends RedisTemplate { + + private String version = Strings.EMPTY; + + public String getVersion() { + if (version.isEmpty()) { + RedisConnection connection = RedisConnectionUtils.getConnection(getConnectionFactory()); + Properties properties = connection.info(); + version = properties.getProperty("redis_version"); + RedisConnectionUtils.releaseConnection(connection,getConnectionFactory()); + } + return version; + } + + /** + * 是否支持批量拉取,redis版本大于6.2支持批量拉取 + * @return + */ + Boolean isSupportBatchPull() { + String version = getVersion(); + String[] arr = version.split("\\."); + if (arr.length < 2) { + return false; + } + Integer firVersion = Integer.valueOf(arr[0]); + Integer secVersion = Integer.valueOf(arr[1]); + return firVersion > 6 || (firVersion == 6 && secVersion >= 2); + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java b/im-common/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java new file mode 100644 index 0000000..6aa3421 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java @@ -0,0 +1,28 @@ +package com.bx.imcommon.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.WritableTypeId; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; + +import java.io.IOException; +import java.util.Date; + +public class DateToLongSerializer extends JsonSerializer { + + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(date.getTime()); + } + + @Override + public void serializeWithType(Date value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { + WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, + typeSer.typeId(value, JsonToken.VALUE_STRING)); + serialize(value, gen, serializers); + typeSer.writeTypeSuffix(gen, typeIdDef); + } +} diff --git a/im-common/src/main/java/com/bx/imcommon/util/CommaTextUtils.java b/im-common/src/main/java/com/bx/imcommon/util/CommaTextUtils.java new file mode 100644 index 0000000..f1c3354 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/util/CommaTextUtils.java @@ -0,0 +1,89 @@ +package com.bx.imcommon.util; + + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 逗号分格文本处理工具类 + * + * @author: blue + * @date: 2023-11-09 09:52:49 + * @version: 1.0 + */ +public class CommaTextUtils { + + /** + * 文本转列表 + * + * @param strText 文件 + * @return 列表 + */ + public static List asList(String strText) { + if (StrUtil.isEmpty(strText)) { + return new LinkedList<>(); + } + return new LinkedList<>(Arrays.asList(strText.split(","))); + + } + + /** + * 列表转字符串,并且自动清空、去重、排序 + * + * @param texts 列表 + * @return 文本 + */ + public static String asText(Collection texts) { + if (CollUtil.isEmpty(texts)) { + return StrUtil.EMPTY; + } + return texts.stream().map(text -> StrUtil.toString(text)).filter(StrUtil::isNotEmpty).distinct().sorted().collect(Collectors.joining(",")); + } + + /** + * 追加一个单词 + * + * @param strText 文本 + * @param word 单词 + * @return 文本 + */ + public static String appendWord(String strText, T word) { + List texts = asList(strText); + texts.add(StrUtil.toString(word)); + return asText(texts); + } + + /** + * 删除一个单词 + * + * @param strText 文本 + * @param word 单词 + * @return 文本 + */ + public static String removeWord(String strText, T word) { + List texts = asList(strText); + texts.remove(StrUtil.toString(word)); + return asText(texts); + } + + /** + * 合并 + * + * @param strText1 文本1 + * @param strText2 文本2 + * @return 文本 + */ + public static String merge(String strText1, String strText2) { + List texts = asList(strText1); + texts.addAll(asList(strText2)); + return asText(texts); + } + +} diff --git a/im-common/src/main/java/com/bx/imcommon/util/JwtUtil.java b/im-common/src/main/java/com/bx/imcommon/util/JwtUtil.java new file mode 100644 index 0000000..6645f28 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/util/JwtUtil.java @@ -0,0 +1,89 @@ +package com.bx.imcommon.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; + +import java.util.Date; + +public final class JwtUtil { + + private JwtUtil() { + } + + /** + * 生成jwt字符串 JWT(json web token) + * + * @param userId 用户id + * @param info 用户细腻系 + * @param expireIn 过期时间 + * @param secret 秘钥 + * @return token + */ + public static String sign(Long userId, String info, long expireIn, String secret) { + try { + Date date = new Date(System.currentTimeMillis() + expireIn * 1000); + Algorithm algorithm = Algorithm.HMAC256(secret); + return JWT.create() + //将userId保存到token里面 + .withAudience(userId.toString()) + //存放自定义数据 + .withClaim("info", info) + //过期时间 + .withExpiresAt(date) + //token的密钥 + .sign(algorithm); + } catch (Exception e) { + return null; + } + } + + /** + * 根据token获取userId + * + * @param token 登录token + * @return 用户id + */ + public static Long getUserId(String token) { + try { + String userId = JWT.decode(token).getAudience().get(0); + return Long.parseLong(userId); + } catch (JWTDecodeException e) { + return null; + } + } + + /** + * 根据token获取用户数据 + * + * @param token 用户登录token + * @return 用户数据 + */ + public static String getInfo(String token) { + try { + return JWT.decode(token).getClaim("info").asString(); + } catch (JWTDecodeException e) { + return null; + } + } + + /** + * 校验token + * + * @param token 用户登录token + * @param secret 秘钥 + * @return true/false + */ + public static Boolean checkSign(String token, String secret) { + try { + Algorithm algorithm = Algorithm.HMAC256(secret); + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(token); + return true; + } catch (JWTVerificationException e) { + return false; + } + } +} diff --git a/im-common/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java b/im-common/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java new file mode 100644 index 0000000..46d77b3 --- /dev/null +++ b/im-common/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java @@ -0,0 +1,87 @@ +package com.bx.imcommon.util; + +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 创建单例线程池 + * + * @author Andrews + * @date 2023/11/30 11:12 + */ +@Slf4j +public final class ThreadPoolExecutorFactory { + /** + * 机器的CPU核数:Runtime.getRuntime().availableProcessors() + * corePoolSize 池中所保存的线程数,包括空闲线程。 + * CPU 密集型:核心线程数 = CPU核数 + 1 + * IO 密集型:核心线程数 = CPU核数 * 2 + */ + private static final int CORE_POOL_SIZE = + Math.min(ThreadPoolExecutorFactory.MAX_IMUM_POOL_SIZE, Runtime.getRuntime().availableProcessors() * 2); + /** + * maximumPoolSize - 池中允许的最大线程数(采用LinkedBlockingQueue时没有作用)。 + */ + private static final int MAX_IMUM_POOL_SIZE = 100; + /** + * keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,线程池维护线程所允许的空闲时间 + */ + private static final int KEEP_ALIVE_TIME = 1000; + /** + * 等待队列的大小。默认是无界的,性能损耗的关键 + */ + private static final int QUEUE_SIZE = 200; + + /** + * 线程池对象 + */ + private static volatile ScheduledThreadPoolExecutor threadPoolExecutor = null; + + /** + * 构造方法私有化 + */ + private ThreadPoolExecutorFactory() { + if (null == threadPoolExecutor) { + threadPoolExecutor = ThreadPoolExecutorFactory.getThreadPoolExecutor(); + } + } + + + /** + * 双检锁创建线程安全的单例 + */ + public static ScheduledThreadPoolExecutor getThreadPoolExecutor() { + if (null == threadPoolExecutor) { + synchronized (ThreadPoolExecutorFactory.class) { + if (null == threadPoolExecutor) { + threadPoolExecutor = new ScheduledThreadPoolExecutor( + //核心线程数 + CORE_POOL_SIZE, + //拒绝策略 + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + } + } + return threadPoolExecutor; + } + + /** + * 关闭线程池 + */ + public static void shutDown() { + if (threadPoolExecutor != null) { + threadPoolExecutor.shutdown(); + } + } + + public static void execute(Runnable runnable) { + if (runnable == null) { + return; + } + threadPoolExecutor.execute(runnable); + } + +} diff --git a/im-platform/pom.xml b/im-platform/pom.xml new file mode 100644 index 0000000..65ef8ab --- /dev/null +++ b/im-platform/pom.xml @@ -0,0 +1,111 @@ + + + + box-im + com.bx + 3.0.0 + + 4.0.0 + im-platform + + + + com.bx + im-client + 3.0.0 + + + org.springframework.boot + spring-boot-starter-web + + + com.baomidou + mybatis-plus-boot-starter + + + org.mybatis + mybatis-spring + + + + + org.mybatis + mybatis-spring + ${mybatis.spring.version} + + + com.alibaba + druid + + + com.mysql + mysql-connector-j + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.aspectj + aspectjweaver + + + org.springframework.security + spring-security-crypto + 6.3.1 + + + org.springframework.session + spring-session-data-redis + + + org.springframework.boot + spring-boot-starter-validation + + + + io.minio + minio + ${minio.version} + + + + net.coobird + thumbnailator + 0.4.8 + + + com.baomidou + mybatis-plus-generator + ${mybatisplus.version} + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java b/im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java new file mode 100644 index 0000000..73dab5d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java @@ -0,0 +1,23 @@ +package com.bx.implatform; + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + + + +@Slf4j +@EnableAspectJAutoProxy(exposeProxy = true) +@ComponentScan(basePackages = {"com.bx"}) +@MapperScan(basePackages = {"com.bx.implatform.mapper"}) +@SpringBootApplication +public class IMPlatformApp { + + public static void main(String[] args) { + SpringApplication.run(IMPlatformApp.class, args); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java b/im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java new file mode 100644 index 0000000..4baec54 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java @@ -0,0 +1,15 @@ +package com.bx.implatform.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 在线校验,标注此注解的接口用户必须保持长连接,否则将抛异常 + */ +@Retention(RetentionPolicy.RUNTIME)//运行时生效 +@Target(ElementType.METHOD)//作用在方法上 +public @interface OnlineCheck { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java b/im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java new file mode 100644 index 0000000..fca9a98 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/annotation/RedisLock.java @@ -0,0 +1,37 @@ +package com.bx.implatform.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * 分布式锁注解 + */ +@Retention(RetentionPolicy.RUNTIME)//运行时生效 +@Target(ElementType.METHOD)//作用在方法上 +public @interface RedisLock { + + /** + * key的前缀,prefixKey+key就是redis的key + */ + String prefixKey() ; + + /** + * spel 表达式 + */ + String key() default ""; + + /** + * 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1 + */ + int waitTime() default -1; + + /** + * 等待锁的时间单位,默认毫秒 + * + */ + TimeUnit unit() default TimeUnit.MILLISECONDS; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java b/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java new file mode 100644 index 0000000..7bd6704 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.bx.implatform.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * 防止表单重复提交注解 + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit { + + + /** + * 间隔时间,小于此时间视为重复提交 + */ + int interval() default 5000; + + /** + * 间隔时间单位 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; + + /** + * 提示消息 + */ + String message() default "请勿提交重复请求"; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java b/im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java new file mode 100644 index 0000000..a9be97e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java @@ -0,0 +1,36 @@ +package com.bx.implatform.aspect; + +import com.bx.imclient.IMClient; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +/** + * @author: blue + * @date: 2024-06-16 + * @version: 1.0 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class OnlineCheckAspect { + + private final IMClient imClient; + + @Around("@annotation(com.bx.implatform.annotation.OnlineCheck)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + UserSession session = SessionContext.getSession(); + if(!imClient.isOnline(session.getUserId())){ + throw new GlobalException("您当前的网络连接已断开,请稍后重试"); + } + return joinPoint.proceed(); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java b/im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java new file mode 100644 index 0000000..2c0d26a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java @@ -0,0 +1,85 @@ +package com.bx.implatform.aspect; + +import cn.hutool.core.util.StrUtil; +import com.bx.implatform.annotation.RedisLock; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.annotation.Order; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * @author: blue + * @date: 2024-06-09 + * @version: 1.0 + */ + +@Slf4j +@Aspect +@Order(0) +@Component +@RequiredArgsConstructor +public class RedisLockAspect { + + private ExpressionParser parser = new SpelExpressionParser(); + private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + private final RedissonClient redissonClient; + + @Around("@annotation(com.bx.implatform.annotation.RedisLock)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + RedisLock annotation = method.getAnnotation(RedisLock.class); + // 解析表达式中的key + String key = parseKey(joinPoint); + String lockKey = StrUtil.join(":",annotation.prefixKey(),key); + // 上锁 + RLock lock = redissonClient.getLock(lockKey); + lock.lock(annotation.waitTime(),annotation.unit()); + try { + // 执行方法 + return joinPoint.proceed(); + }finally { + lock.unlock(); + } + } + + private String parseKey(ProceedingJoinPoint joinPoint){ + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + RedisLock annotation = method.getAnnotation(RedisLock.class); + String key = annotation.key(); + if(StrUtil.isEmpty(key)){ + return Strings.EMPTY; + } + // el解析需要的上下文对象 + EvaluationContext context = new StandardEvaluationContext(); + // 参数名 + String[] params = parameterNameDiscoverer.getParameterNames(method); + if(Objects.isNull(params)){ + return key; + } + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < params.length; i++) { + context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去 + } + Expression expression = parser.parseExpression(key); + return expression.getValue(context, String.class); + } + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java b/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java new file mode 100644 index 0000000..cdd566f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/aspect/RepeatSubmitAspect.java @@ -0,0 +1,100 @@ +package com.bx.implatform.aspect; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import com.alibaba.fastjson.JSON; +import com.bx.implatform.annotation.RepeatSubmit; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.session.SessionContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collection; +import java.util.Map; +import java.util.StringJoiner; + +/** + * @author: blue + * @date: 2024-12-08 + * @version: 1.0 + */ + +@Aspect +@Component +@AllArgsConstructor +public class RepeatSubmitAspect { + + private final RedisTemplate redisTemplate; + + @Before("@annotation(repeatSubmit)") + public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { + // 如果注解不为0 则使用注解数值 + long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); + HttpServletRequest request = + ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); + String url = request.getRequestURL().toString(); + Long userId = SessionContext.getSession().getUserId(); + String reqParams = argsArrayToString(point.getArgs()); + String md5 = SecureUtil.md5(StrUtil.join(":", userId, url, reqParams)); + // 唯一标识 + String key = String.join(":",RedisKey.IM_REPEAT_SUBMIT,md5) ; + if(redisTemplate.hasKey(key)){ + throw new GlobalException(repeatSubmit.message()); + } + redisTemplate.opsForValue().set(key,1,repeatSubmit.interval(),repeatSubmit.timeUnit()); + } + + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) { + StringJoiner params = new StringJoiner(" "); + if (ArrayUtil.isEmpty(paramsArray)) { + return params.toString(); + } + for (Object o : paramsArray) { + if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { + params.add(JSON.toJSONString(o)); + } + } + return params.toString(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection)o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map)o; + for (Object value : map.values()) { + return value instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java b/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java new file mode 100644 index 0000000..4aa6113 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/ICEServer.java @@ -0,0 +1,11 @@ +package com.bx.implatform.config; + +import lombok.Data; + +@Data +public class ICEServer { + private String urls; + private String username; + private String credential; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java b/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java new file mode 100644 index 0000000..ca661b8 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java @@ -0,0 +1,19 @@ +package com.bx.implatform.config; + +import com.bx.implatform.config.props.MinioProperties; +import io.minio.MinioClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MinIoClientConfig { + + @Bean + public MinioClient minioClient(MinioProperties minioProps) { + // 注入minio 客户端 + return MinioClient.builder() + .endpoint(minioProps.getEndpoint()) + .credentials(minioProps.getAccessKey(), minioProps.getSecretKey()) + .build(); + } +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java b/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java new file mode 100644 index 0000000..2dee3c9 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java @@ -0,0 +1,34 @@ +package com.bx.implatform.config; + +import com.bx.implatform.interceptor.AuthInterceptor; +import com.bx.implatform.interceptor.XssInterceptor; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@AllArgsConstructor +public class MvcConfig implements WebMvcConfigurer { + + private final AuthInterceptor authInterceptor; + private final XssInterceptor xssInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(xssInterceptor).addPathPatterns("/**").excludePathPatterns("/error"); + registry.addInterceptor(authInterceptor).addPathPatterns("/**") + .excludePathPatterns("/login", "/logout", "/register", "/refreshToken", "/swagger/**", "/v3/api-docs/**", + "/swagger-resources/**", "/swagger-ui.html", "/swagger-ui/**", "/doc.html"); + } + + @Bean + public PasswordEncoder passwordEncoder() { + // 使用BCrypt加密密码 + return new BCryptPasswordEncoder(); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java b/im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java new file mode 100644 index 0000000..d74e656 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java @@ -0,0 +1,74 @@ +package com.bx.implatform.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.annotation.Resource; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@EnableCaching +@Configuration +public class RedisConfig extends CachingConfigurerSupport { + + @Resource + private RedisConnectionFactory factory; + + @Primary + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + // 设置值(value)的序列化采用jackson2JsonRedisSerializer + redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); + redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); + // 设置键(key)的序列化采用StringRedisSerializer。 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + + + + @Bean + public CacheManager cacheManager() { + // 设置redis缓存管理器 + RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())); + return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build(); + } + + + @Bean + public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() { + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // 解决jackson2无法反序列化LocalDateTime的问题 + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + om.registerModule(new JavaTimeModule()); + om.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略空值 + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + //忽略无效字段 + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return new Jackson2JsonRedisSerializer<>(om, Object.class); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java b/im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java new file mode 100644 index 0000000..3b2ae2a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java @@ -0,0 +1,38 @@ +package com.bx.implatform.config; + + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public GroupedOpenApi userApi() { + String[] paths = {"/**"}; + String[] packagedToMatch = {"com.bx"}; + return GroupedOpenApi.builder().group("IM-Platform") + .pathsToMatch(paths) + .packagesToScan(packagedToMatch).build(); + } + + @Bean + public OpenAPI customOpenAPI() { + Contact contact = new Contact(); + contact.setName("Blue"); + return new OpenAPI().info(new Info() + .title("盒子IM接口文档") + .description("盒子IM业务平台服务") + .contact(contact) + .version("3.0") + .termsOfService("https://www.boxim.online") + .license(new License().name("MIT") + .url("https://www.boxim.online"))); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/TaskSchedulerConfig.java b/im-platform/src/main/java/com/bx/implatform/config/TaskSchedulerConfig.java new file mode 100644 index 0000000..ed14e09 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/TaskSchedulerConfig.java @@ -0,0 +1,27 @@ +package com.bx.implatform.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * @author: Blue + * @date: 2024-09-01 + * @version: 1.0 + */ + +@EnableScheduling +@Configuration +public class TaskSchedulerConfig { + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setPoolSize(10); // 设置线程池大小 + taskScheduler.setThreadNamePrefix("scheduled-task-"); + taskScheduler.initialize(); + return taskScheduler; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/WebrtcConfig.java b/im-platform/src/main/java/com/bx/implatform/config/WebrtcConfig.java new file mode 100644 index 0000000..633d640 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/WebrtcConfig.java @@ -0,0 +1,19 @@ +package com.bx.implatform.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Component +@ConfigurationProperties(prefix = "webrtc") +public class WebrtcConfig { + + private Integer maxChannel = 9; + + private List iceServers = new ArrayList<>(); + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/props/JwtProperties.java b/im-platform/src/main/java/com/bx/implatform/config/props/JwtProperties.java new file mode 100644 index 0000000..4642b8d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/props/JwtProperties.java @@ -0,0 +1,22 @@ +package com.bx.implatform.config.props; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Data +@Component +public class JwtProperties { + + @Value("${jwt.accessToken.expireIn}") + private Integer accessTokenExpireIn; + + @Value("${jwt.accessToken.secret}") + private String accessTokenSecret; + + @Value("${jwt.refreshToken.expireIn}") + private Integer refreshTokenExpireIn; + + @Value("${jwt.refreshToken.secret}") + private String refreshTokenSecret; +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java b/im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java new file mode 100644 index 0000000..5a64db3 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/props/MinioProperties.java @@ -0,0 +1,34 @@ +package com.bx.implatform.config.props; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author: Blue + * @date: 2024-09-28 + * @version: 1.0 + */ +@Data +@Component +@ConfigurationProperties(prefix = "minio") +public class MinioProperties { + + private String endpoint; + + private String accessKey; + + private String secretKey; + + private String domain; + + private String bucketName; + + private String imagePath; + + private String filePath; + + private String videoPath; + + private Integer expireIn; +} diff --git a/im-platform/src/main/java/com/bx/implatform/contant/Constant.java b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java new file mode 100644 index 0000000..d255ba3 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java @@ -0,0 +1,33 @@ +package com.bx.implatform.contant; + +public final class Constant { + + /** + * 系统用户id + */ + public static final Long SYS_USER_ID = 0L; + /** + * 最大图片上传大小 + */ + public static final Long MAX_IMAGE_SIZE = 20 * 1024 * 1024L; + /** + * 最大上传文件大小 + */ + public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L; + + /** + * 最大文件名长度 + */ + public static final Long MAX_FILE_NAME_LENGTH = 128L; + + /** + * 大群人数上限 + */ + public static final Long MAX_LARGE_GROUP_MEMBER = 3000L; + + /** + * 普通群人数上限 + */ + public static final Long MAX_NORMAL_GROUP_MEMBER = 500L; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java b/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java new file mode 100644 index 0000000..e19c8aa --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java @@ -0,0 +1,72 @@ +package com.bx.implatform.contant; + +public final class RedisKey { + + /** + * 用户状态 无值:空闲 1:正在忙 + */ + public static final String IM_USER_STATE = "im:user:state"; + /** + * 已读群聊消息位置(已读最大id) + */ + public static final String IM_GROUP_READED_POSITION = "im:readed:group:position"; + /** + * webrtc 单人通话 + */ + public static final String IM_WEBRTC_PRIVATE_SESSION = "im:webrtc:private:session"; + /** + * webrtc 群通话 + */ + public static final String IM_WEBRTC_GROUP_SESSION = "im:webrtc:group:session"; + + /** + * 用户被封禁消息队列 + */ + public static final String IM_QUEUE_USER_BANNED = "im:queue:user:banned"; + + /** + * 群聊被封禁消息队列 + */ + public static final String IM_QUEUE_GROUP_BANNED = "im:queue:group:banned"; + + /** + * 群聊解封消息队列 + */ + public static final String IM_QUEUE_GROUP_UNBAN = "im:queue:group:unban"; + + + /** + * 缓存是否好友:bool + */ + public static final String IM_CACHE_FRIEND = "im:cache:friend"; + /** + * 缓存群聊信息 + */ + public static final String IM_CACHE_GROUP = "im:cache:group"; + /** + * 缓存群聊成员id + */ + public static final String IM_CACHE_GROUP_MEMBER_ID = "im:cache:group_member_ids"; + + /** + * 重复提交 + */ + public static final String IM_REPEAT_SUBMIT = "im:repeat:submit"; + + /** + * 分布式锁-添加好友 + */ + public static final String IM_LOCK_FRIEND_ADD = "im:lock:friend:add"; + + /** + * 分布式锁-进入群聊 + */ + public static final String IM_LOCK_GROUP_ENTER = "im:lock:group:enter"; + + + /** + * 分布式锁-清理过期文件 + */ + public static final String IM_LOCK_FILE_TASK = "im:lock:task:file"; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/FileController.java b/im-platform/src/main/java/com/bx/implatform/controller/FileController.java new file mode 100644 index 0000000..e9c0856 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/FileController.java @@ -0,0 +1,38 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.FileService; +import com.bx.implatform.vo.UploadImageVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@RestController +@Tag(name = "文件上传") +@RequiredArgsConstructor +public class FileController { + + private final FileService fileService; + + @Operation(summary = "上传图片", description = "上传图片,上传后返回原图和缩略图的url") + @PostMapping("/image/upload") + public Result uploadImage(@RequestParam("file") MultipartFile file, + @RequestParam(defaultValue = "true") Boolean isPermanent, @RequestParam(defaultValue = "50") Long thumbSize) { + return ResultUtils.success(fileService.uploadImage(file, isPermanent,thumbSize)); + } + + @Operation(summary = "上传文件", description = "上传文件,上传后返回文件url") + @PostMapping("/file/upload") + public Result uploadFile(@RequestParam("file") MultipartFile file) { + return ResultUtils.success(fileService.uploadFile(file), Strings.EMPTY); + } + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java b/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java new file mode 100644 index 0000000..76e6e5f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java @@ -0,0 +1,64 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.annotation.RepeatSubmit; +import com.bx.implatform.dto.FriendDndDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.FriendService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.vo.FriendVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "好友") +@RestController +@RequestMapping("/friend") +@RequiredArgsConstructor +public class FriendController { + + private final FriendService friendService; + + @GetMapping("/list") + @Operation(summary = "好友列表", description = "获取好友列表") + public Result> findFriends() { + return ResultUtils.success(friendService.findFriends()); + } + + + @RepeatSubmit + @PostMapping("/add") + @Operation(summary = "添加好友", description = "双方建立好友关系") + public Result addFriend(@NotNull(message = "好友id不可为空") @RequestParam Long friendId) { + friendService.addFriend(SessionContext.getSession().getUserId(),friendId); + return ResultUtils.success(); + } + + @GetMapping("/find/{friendId}") + @Operation(summary = "查找好友信息", description = "查找好友信息") + public Result findFriend(@NotNull(message = "好友id不可为空") @PathVariable Long friendId) { + return ResultUtils.success(friendService.findFriend(friendId)); + } + + + @DeleteMapping("/delete/{friendId}") + @Operation(summary = "删除好友", description = "解除好友关系") + public Result delFriend(@NotNull(message = "好友id不可为空") @PathVariable Long friendId) { + friendService.delFriend(friendId); + return ResultUtils.success(); + } + + @PutMapping("/dnd") + @Operation(summary = "开启/关闭免打扰状态", description = "开启/关闭免打扰状态") + public Result setFriendDnd(@Valid @RequestBody FriendDndDTO dto) { + friendService.setDnd(dto); + return ResultUtils.success(); + } + +} + diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java new file mode 100644 index 0000000..af25a5a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java @@ -0,0 +1,113 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.annotation.RepeatSubmit; +import com.bx.implatform.dto.GroupDndDTO; +import com.bx.implatform.dto.GroupInviteDTO; +import com.bx.implatform.dto.GroupMemberRemoveDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.GroupService; +import com.bx.implatform.vo.GroupMemberVO; +import com.bx.implatform.vo.GroupVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "群聊") +@RestController +@RequestMapping("/group") +@RequiredArgsConstructor +public class GroupController { + + private final GroupService groupService; + + @RepeatSubmit + @Operation(summary = "创建群聊", description = "创建群聊") + @PostMapping("/create") + public Result createGroup(@Valid @RequestBody GroupVO vo) { + return ResultUtils.success(groupService.createGroup(vo)); + } + + @RepeatSubmit + @Operation(summary = "修改群聊信息", description = "修改群聊信息") + @PutMapping("/modify") + public Result modifyGroup(@Valid @RequestBody GroupVO vo) { + return ResultUtils.success(groupService.modifyGroup(vo)); + } + + @RepeatSubmit + @Operation(summary = "解散群聊", description = "解散群聊") + @DeleteMapping("/delete/{groupId}") + public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { + groupService.deleteGroup(groupId); + return ResultUtils.success(); + } + + + @Operation(summary = "查询群聊", description = "查询单个群聊信息") + @GetMapping("/find/{groupId}") + public Result findGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { + return ResultUtils.success(groupService.findById(groupId)); + } + + @Operation(summary = "查询群聊列表", description = "查询群聊列表") + @GetMapping("/list") + public Result> findGroups() { + return ResultUtils.success(groupService.findGroups()); + } + + @RepeatSubmit + @Operation(summary = "邀请进群", description = "邀请好友进群") + @PostMapping("/invite") + public Result invite(@Valid @RequestBody GroupInviteDTO dto) { + groupService.invite(dto); + return ResultUtils.success(); + } + + @Operation(summary = "查询群聊成员", description = "查询群聊成员") + @GetMapping("/members/{groupId}") + public Result> findGroupMembers( + @NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { + return ResultUtils.success(groupService.findGroupMembers(groupId)); + } + + @RepeatSubmit + @Operation(summary = "将成员移出群聊", description = "将成员移出群聊") + @DeleteMapping("/members/remove") + public Result removeMembers(@Valid @RequestBody GroupMemberRemoveDTO dto) { + groupService.removeGroupMembers(dto); + return ResultUtils.success(); + } + + + @RepeatSubmit + @Operation(summary = "退出群聊", description = "退出群聊") + @DeleteMapping("/quit/{groupId}") + public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { + groupService.quitGroup(groupId); + return ResultUtils.success(); + } + + @RepeatSubmit + @Operation(summary = "踢出群聊(已废弃)", description = "将用户踢出群聊") + @DeleteMapping("/kick/{groupId}") + public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId, + @NotNull(message = "用户id不能为空") @RequestParam Long userId) { + groupService.kickGroup(groupId, userId); + return ResultUtils.success(); + } + + @Operation(summary = "开启/关闭免打扰", description = "开启/关闭免打扰") + @PutMapping("/dnd") + public Result setGroupDnd(@Valid @RequestBody GroupDndDTO dto) { + groupService.setDnd(dto); + return ResultUtils.success(); + } + +} + diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java new file mode 100644 index 0000000..108bb68 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -0,0 +1,73 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.dto.GroupMessageDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.GroupMessageService; +import com.bx.implatform.vo.GroupMessageVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "群聊消息") +@RestController +@RequestMapping("/message/group") +@RequiredArgsConstructor +public class GroupMessageController { + + private final GroupMessageService groupMessageService; + + @PostMapping("/send") + @Operation(summary = "发送群聊消息", description = "发送群聊消息") + public Result sendMessage(@Valid @RequestBody GroupMessageDTO vo) { + return ResultUtils.success(groupMessageService.sendMessage(vo)); + } + + @DeleteMapping("/recall/{id}") + @Operation(summary = "撤回消息", description = "撤回群聊消息") + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success(groupMessageService.recallMessage(id)); + } + + @GetMapping("/pullOfflineMessage") + @Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送") + public Result pullOfflineMessage(@RequestParam Long minId) { + groupMessageService.pullOfflineMessage(minId); + return ResultUtils.success(); + } + + @GetMapping(value = "/loadOfflineMessage") + @Operation(summary = "拉取离线消息", description = "拉取离线消息") + public Result> loadOfflineMessage(@RequestParam Long minId) { + return ResultUtils.success(groupMessageService.loadOffineMessage(minId)); + } + + + @PutMapping("/readed") + @Operation(summary = "消息已读", description = "将群聊中的消息状态置为已读") + public Result readedMessage(@RequestParam Long groupId) { + groupMessageService.readedMessage(groupId); + return ResultUtils.success(); + } + + @GetMapping("/findReadedUsers") + @Operation(summary = "获取已读用户id", description = "获取消息已读用户列表") + public Result> findReadedUsers(@RequestParam Long groupId, + @RequestParam Long messageId) { + return ResultUtils.success(groupMessageService.findReadedUsers(groupId, messageId)); + } + + @GetMapping("/history") + @Operation(summary = "查询聊天记录", description = "查询聊天记录") + public Result> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId, + @NotNull(message = "页码不能为空") @RequestParam Long page, + @NotNull(message = "size不能为空") @RequestParam Long size) { + return ResultUtils.success(groupMessageService.findHistoryMessage(groupId, page, size)); + } +} + diff --git a/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java new file mode 100644 index 0000000..36a7a43 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java @@ -0,0 +1,51 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.dto.LoginDTO; +import com.bx.implatform.dto.ModifyPwdDTO; +import com.bx.implatform.dto.RegisterDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.UserService; +import com.bx.implatform.vo.LoginVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "注册登录") +@RestController +@RequiredArgsConstructor +public class LoginController { + + private final UserService userService; + + @PostMapping("/login") + @Operation(summary = "用户登录", description = "用户登录") + public Result login(@Valid @RequestBody LoginDTO dto) { + LoginVO vo = userService.login(dto); + return ResultUtils.success(vo); + } + + @PutMapping("/refreshToken") + @Operation(summary = "刷新token", description = "用refreshtoken换取新的token") + public Result refreshToken(@RequestHeader("refreshToken") String refreshToken) { + LoginVO vo = userService.refreshToken(refreshToken); + return ResultUtils.success(vo); + } + + @PostMapping("/register") + @Operation(summary = "用户注册", description = "用户注册") + public Result register(@Valid @RequestBody RegisterDTO dto) { + userService.register(dto); + return ResultUtils.success(); + } + + @PutMapping("/modifyPwd") + @Operation(summary = "修改密码", description = "修改用户密码") + public Result modifyPassword(@Valid @RequestBody ModifyPwdDTO dto) { + userService.modifyPassword(dto); + return ResultUtils.success(); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java new file mode 100644 index 0000000..4fea71d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java @@ -0,0 +1,74 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.dto.PrivateMessageDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.PrivateMessageService; +import com.bx.implatform.vo.PrivateMessageVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "私聊消息") +@RestController +@RequestMapping("/message/private") +@RequiredArgsConstructor +public class PrivateMessageController { + + private final PrivateMessageService privateMessageService; + + @PostMapping("/send") + @Operation(summary = "发送消息", description = "发送私聊消息") + public Result sendMessage(@Valid @RequestBody PrivateMessageDTO vo) { + return ResultUtils.success(privateMessageService.sendMessage(vo)); + } + + @DeleteMapping("/recall/{id}") + @Operation(summary = "撤回消息", description = "撤回私聊消息") + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success( privateMessageService.recallMessage(id)); + } + + @GetMapping("/pullOfflineMessage") + @Operation(summary = "拉取离线消息(已废弃)", description = "拉取离线消息,消息将通过webscoket异步推送") + public Result pullOfflineMessage(@RequestParam Long minId) { + privateMessageService.pullOfflineMessage(minId); + return ResultUtils.success(); + } + + @GetMapping(value = "/loadOfflineMessage") + @Operation(summary = "拉取离线消息", description = "拉取离线消息") + public Result> loadOfflineMessage(@RequestParam Long minId) { + return ResultUtils.success(privateMessageService.loadOfflineMessage(minId)); + } + + + @PutMapping("/readed") + @Operation(summary = "消息已读", description = "将会话中接收的消息状态置为已读") + public Result readedMessage(@RequestParam Long friendId) { + privateMessageService.readedMessage(friendId); + return ResultUtils.success(); + } + + @GetMapping("/maxReadedId") + @Operation(summary = "获取最大已读消息的id", description = "获取某个会话中已读消息的最大id") + public Result getMaxReadedId(@RequestParam Long friendId) { + return ResultUtils.success(privateMessageService.getMaxReadedId(friendId)); + } + + @GetMapping("/history") + @Operation(summary = "查询聊天记录", description = "查询聊天记录") + public Result> recallMessage( + @NotNull(message = "好友id不能为空") @RequestParam Long friendId, + @NotNull(message = "页码不能为空") @RequestParam Long page, + @NotNull(message = "size不能为空") @RequestParam Long size) { + return ResultUtils.success(privateMessageService.findHistoryMessage(friendId, page, size)); + } + +} + diff --git a/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java b/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java new file mode 100644 index 0000000..fd6de4d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java @@ -0,0 +1,36 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.config.WebrtcConfig; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.vo.SystemConfigVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + + +/** + * @author: blue + * @date: 2024-06-10 + * @version: 1.0 + */ +@Tag(name = "系统相关") +@RestController +@RequestMapping("/system") +@RequiredArgsConstructor +public class SystemController { + + private final WebrtcConfig webrtcConfig; + + @GetMapping("/config") + @Operation(summary = "加载系统配置", description = "加载系统配置") + public Result loadConfig() { + return ResultUtils.success(new SystemConfigVO(webrtcConfig)); + } + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java new file mode 100644 index 0000000..9c5af89 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java @@ -0,0 +1,65 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.entity.User; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.UserService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.OnlineTerminalVO; +import com.bx.implatform.vo.UserVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "用户相关") +@RestController +@RequestMapping("/user") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @GetMapping("/terminal/online") + @Operation(summary = "判断用户哪个终端在线", description = "返回在线的用户id的终端集合") + public Result> getOnlineTerminal(@NotNull @RequestParam("userIds") String userIds) { + return ResultUtils.success(userService.getOnlineTerminals(userIds)); + } + + + @GetMapping("/self") + @Operation(summary = "获取当前用户信息", description = "获取当前用户信息") + public Result findSelfInfo() { + UserSession session = SessionContext.getSession(); + User user = userService.getById(session.getUserId()); + UserVO userVO = BeanUtils.copyProperties(user, UserVO.class); + return ResultUtils.success(userVO); + } + + + @GetMapping("/find/{id}") + @Operation(summary = "查找用户", description = "根据id查找用户") + public Result findById(@NotNull @PathVariable("id") Long id) { + return ResultUtils.success(userService.findUserById(id)); + } + + @PutMapping("/update") + @Operation(summary = "修改用户信息", description = "修改用户信息,仅允许修改登录用户信息") + public Result update(@Valid @RequestBody UserVO vo) { + userService.update(vo); + return ResultUtils.success(); + } + + @GetMapping("/findByName") + @Operation(summary = "查找用户", description = "根据用户名或昵称查找用户") + public Result> findByName(@RequestParam String name) { + return ResultUtils.success(userService.findUserByName(name)); + } +} + diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java new file mode 100644 index 0000000..2ce13b4 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java @@ -0,0 +1,76 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.service.WebrtcPrivateService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "单人通话") +@RestController +@RequestMapping("/webrtc/private") +@RequiredArgsConstructor +public class WebrtcPrivateController { + + private final WebrtcPrivateService webrtcPrivateService; + + + @Operation(summary = "呼叫视频通话") + @PostMapping("/call") + public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode, + @RequestBody String offer) { + webrtcPrivateService.call(uid, mode, offer); + return ResultUtils.success(); + } + + @Operation(summary = "接受视频通话") + @PostMapping("/accept") + public Result accept(@RequestParam Long uid, @RequestBody String answer) { + webrtcPrivateService.accept(uid, answer); + return ResultUtils.success(); + } + + @Operation(summary = "拒绝视频通话") + @PostMapping("/reject") + public Result reject(@RequestParam Long uid) { + webrtcPrivateService.reject(uid); + return ResultUtils.success(); + } + + @Operation(summary = "取消呼叫") + @PostMapping("/cancel") + public Result cancel(@RequestParam Long uid) { + webrtcPrivateService.cancel(uid); + return ResultUtils.success(); + } + + @Operation(summary = "呼叫失败") + @PostMapping("/failed") + public Result failed(@RequestParam Long uid, @RequestParam String reason) { + webrtcPrivateService.failed(uid, reason); + return ResultUtils.success(); + } + + @Operation(summary = "挂断") + @PostMapping("/handup") + public Result handup(@RequestParam Long uid) { + webrtcPrivateService.handup(uid); + return ResultUtils.success(); + } + + @PostMapping("/candidate") + @Operation(summary = "同步candidate") + public Result candidate(@RequestParam Long uid, @RequestBody String candidate) { + webrtcPrivateService.candidate(uid, candidate); + return ResultUtils.success(); + } + + @Operation(summary = "心跳") + @PostMapping("/heartbeat") + public Result heartbeat(@RequestParam Long uid) { + webrtcPrivateService.heartbeat(uid); + return ResultUtils.success(); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java new file mode 100644 index 0000000..12d1721 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java @@ -0,0 +1,23 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author Blue + * @version 1.0 + */ +@Data +@Schema(description = "好友免打扰") +public class FriendDndDTO { + + @NotNull(message = "好友id不可为空") + @Schema(description = "好友用户id") + private Long friendId; + + @NotNull(message = "消息免打扰状态不可为空") + @Schema(description = "消息免打扰状态") + private Boolean isDnd; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java new file mode 100644 index 0000000..cbe673d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java @@ -0,0 +1,21 @@ +package com.bx.implatform.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-07-14 + * @version: 1.0 + */ +@Data +@Schema(description = "群组封禁") +public class GroupBanDTO { + + @Schema(description = "群组id") + private Long id; + + @Schema(description = "封禁原因") + private String reason; +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java new file mode 100644 index 0000000..1358b82 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java @@ -0,0 +1,23 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author Blue + * @version 1.0 + * @date 2025-02-23 + */ +@Data +@Schema(description = "群聊免打扰") +public class GroupDndDTO { + + @NotNull(message = "群id不可为空") + @Schema(description = "群组id") + private Long groupId; + + @NotNull(message = "免打扰状态不可为空") + @Schema(description = "免打扰状态") + private Boolean isDnd; +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java new file mode 100644 index 0000000..efeb5fb --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java @@ -0,0 +1,23 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "邀请好友进群请求DTO") +public class GroupInviteDTO { + + @NotNull(message = "群id不可为空") + @Schema(description = "群id") + private Long groupId; + + @Size(max = 50, message = "一次最多只能邀请50位用户") + @NotEmpty(message = "群id不可为空") + @Schema(description = "好友id列表不可为空") + private List friendIds; +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java new file mode 100644 index 0000000..5d98e71 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java @@ -0,0 +1,28 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +/** + * @author Blue + * @version 1.0 + * @date 2025-02-23 + */ +@Data +@Schema(description = "移除群聊成员") +public class GroupMemberRemoveDTO { + + @NotNull(message = "群id不可为空") + @Schema(description = "群组id") + private Long groupId; + + @Size(max = 50, message = "一次最多只能选择50位用户") + @NotEmpty(message = "成员用户id不可为空") + @Schema(description = "成员用户id") + private List userIds; +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java new file mode 100644 index 0000000..8b4f257 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java @@ -0,0 +1,38 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +@Data +@Schema(description = "群聊消息DTO") +public class GroupMessageDTO { + + @Schema(description = "临时id") + private String tmpId; + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @Length(max = 1024, message = "发送内容长度不得大于1024") + @NotEmpty(message = "发送内容不可为空") + @Schema(description = "发送内容") + private String content; + + @NotNull(message = "消息类型不可为空") + @Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频") + private Integer type; + + @Schema(description = "是否回执消息") + private Boolean receipt = false; + + @Size(max = 20, message = "一次最多只能@20个小伙伴哦") + @Schema(description = "被@用户列表") + private List atUserIds; +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java new file mode 100644 index 0000000..2ccffa6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java @@ -0,0 +1,18 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-07-14 + * @version: 1.0 + */ +@Data +@Schema(description = "群组解锁") +public class GroupUnbanDTO { + + @Schema(description = "群组id") + private Long id; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java new file mode 100644 index 0000000..30b240e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java @@ -0,0 +1,28 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +@Schema(description = "用户登录DTO") +public class LoginDTO { + + @Max(value = 2, message = "登录终端类型取值范围:0,2") + @Min(value = 0, message = "登录终端类型取值范围:0,2") + @NotNull(message = "登录终端类型不可为空") + @Schema(description = "登录终端 0:web 1:app 2:pc") + private Integer terminal; + + @NotEmpty(message = "用户名不可为空") + @Schema(description = "用户名") + private String userName; + + @NotEmpty(message = "用户密码不可为空") + @Schema(description = "用户密码") + private String password; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java new file mode 100644 index 0000000..c733493 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java @@ -0,0 +1,19 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Data +@Schema(description = "修改密码DTO") +public class ModifyPwdDTO { + + @NotEmpty(message = "旧用户密码不可为空") + @Schema(description = "旧用户密码") + private String oldPassword; + + @NotEmpty(message = "新用户密码不可为空") + @Schema(description = "新用户密码") + private String newPassword; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java new file mode 100644 index 0000000..639325c --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java @@ -0,0 +1,29 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +@Data +@Schema(description = "私聊消息DTO") +public class PrivateMessageDTO { + + @Schema(description = "临时id") + private String tmpId; + + @NotNull(message = "接收用户id不可为空") + @Schema(description = "接收用户id") + private Long recvId; + + @Length(max = 1024, message = "内容长度不得大于1024") + @NotEmpty(message = "发送内容不可为空") + @Schema(description = "发送内容") + private String content; + + @NotNull(message = "消息类型不可为空") + @Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频") + private Integer type; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java new file mode 100644 index 0000000..37a0c51 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java @@ -0,0 +1,27 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +@Data +@Schema(description = "用户注册DTO") +public class RegisterDTO { + + @Length(max = 20, message = "用户名不能大于20字符") + @NotEmpty(message = "用户名不可为空") + @Schema(description = "用户名") + private String userName; + + @Length(min = 5, max = 20, message = "密码长度必须在5-20个字符之间") + @NotEmpty(message = "用户密码不可为空") + @Schema(description = "用户密码") + private String password; + + @Length(max = 20, message = "昵称不能大于20字符") + @Schema(description = "用户昵称") + private String nickName; + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java new file mode 100644 index 0000000..f8953c6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java @@ -0,0 +1,21 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-07-14 + * @version: 1.0 + */ +@Data +@Schema(description = "用户锁定DTO") +public class UserBanDTO { + + @Schema(description = "用户id") + private Long id; + + @Schema(description = "锁定原因") + private String reason; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java new file mode 100644 index 0000000..6e96bc0 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java @@ -0,0 +1,29 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "回复用户连接请求DTO") +public class WebrtcGroupAnswerDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @NotNull(message = "用户id不可为空") + @Schema(description = "用户id,代表回复谁的连接请求") + private Long userId; + + @NotEmpty(message = "anwer不可为空") + @Schema(description = "用户本地anwer信息") + private String answer; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java new file mode 100644 index 0000000..69d054e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java @@ -0,0 +1,29 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "发起群视频通话DTO") +public class WebrtcGroupCandidateDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @NotNull(message = "用户id不可为空") + @Schema(description = "用户id") + private Long userId; + + @NotEmpty(message = "candidate信息不可为空") + @Schema(description = "candidate信息") + private String candidate; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java new file mode 100644 index 0000000..b7e2073 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java @@ -0,0 +1,26 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "用户设备操作DTO") +public class WebrtcGroupDeviceDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @Schema(description = "是否开启摄像头") + private Boolean isCamera; + + @Schema(description = "是否开启麦克风") + private Boolean isMicroPhone; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java new file mode 100644 index 0000000..fa2d5ba --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java @@ -0,0 +1,23 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "用户通话失败DTO") +public class WebrtcGroupFailedDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @Schema(description = "失败原因") + private String reason; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java new file mode 100644 index 0000000..1c0f894 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java @@ -0,0 +1,28 @@ +package com.bx.implatform.dto; + +import com.bx.implatform.session.WebrtcUserInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "邀请用户进入群视频通话DTO") +public class WebrtcGroupInviteDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @NotEmpty(message = "参与用户信息不可为空") + @Schema(description = "参与用户信息") + private List userInfos; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java new file mode 100644 index 0000000..b59adb0 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java @@ -0,0 +1,20 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "进入群视频通话DTO") +public class WebrtcGroupJoinDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java new file mode 100644 index 0000000..dbe0048 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java @@ -0,0 +1,29 @@ +package com.bx.implatform.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "回复用户连接请求DTO") +public class WebrtcGroupOfferDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @NotNull(message = "用户id不可为空") + @Schema(description = "用户id,代表回复谁的连接请求") + private Long userId; + + @NotEmpty(message = "offer不可为空") + @Schema(description = "用户offer信息") + private String offer; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java new file mode 100644 index 0000000..724ceec --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java @@ -0,0 +1,28 @@ +package com.bx.implatform.dto; + +import com.bx.implatform.session.WebrtcUserInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +@Schema(description = "发起群视频通话DTO") +public class WebrtcGroupSetupDTO { + + @NotNull(message = "群聊id不可为空") + @Schema(description = "群聊id") + private Long groupId; + + @NotEmpty(message = "参与用户信息不可为空") + @Schema(description = "参与用户信息") + private List userInfos; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java b/im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java new file mode 100644 index 0000000..9a847f5 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/FileInfo.java @@ -0,0 +1,67 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * @author Blue + * @version 1.0 + */ +@Data +@TableName("im_file_info") +public class FileInfo { + + /** + * 文件ID + */ + @TableId + private Long id; + + /** + * 文件名 + */ + private String fileName; + + /** + * 原始文件存储路径 + */ + private String filePath; + + /** + * 压缩文件存储路径 + */ + private String compressedPath; + + /** + * 封面文件路径 + */ + private String coverPath; + + /** + * 原始文件大小(字节) + */ + private Long fileSize; + + /** + * 上传时间 + */ + private Date uploadTime; + + /** + * 文件类型,枚举: FileType + */ + private Integer fileType; + + /** + * 是否永久存储 + */ + private Boolean isPermanent; + + /** + * 文件MD5哈希值 + */ + private String md5; +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/Friend.java b/im-platform/src/main/java/com/bx/implatform/entity/Friend.java new file mode 100644 index 0000000..e3f96f7 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/Friend.java @@ -0,0 +1,63 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * 好友 + *

+ * + * @author blue + * @since 2022-10-22 + */ +@Data +@TableName("im_friend") +public class Friend{ + + + /** + * id + */ + @TableId + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 好友id + */ + private Long friendId; + + /** + * 用户昵称 + */ + private String friendNickName; + + /** + * 用户头像 + */ + private String friendHeadImage; + + /** + * 是否开启免打扰 + */ + private Boolean isDnd; + + /** + * 是否已删除 + */ + private Boolean deleted; + + /** + * 创建时间 + */ + private Date createdTime; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/Group.java b/im-platform/src/main/java/com/bx/implatform/entity/Group.java new file mode 100644 index 0000000..06ba349 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/Group.java @@ -0,0 +1,72 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 群 + * + * @author blue + * @since 2022-10-31 + */ +@Data +@TableName("im_group") +public class Group { + + /** + * id + */ + @TableId + private Long id; + + /** + * 群名字 + */ + private String name; + + /** + * 群主id + */ + private Long ownerId; + + /** + * 群头像 + */ + private String headImage; + + /** + * 群头像缩略图 + */ + private String headImageThumb; + + /** + * 群公告 + */ + private String notice; + + /** + * 是否被封禁 + */ + private Boolean isBanned; + + /** + * 被封禁原因 + */ + private String reason; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 是否已删除 + */ + private Boolean dissolve; + + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java b/im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java new file mode 100644 index 0000000..8aec5cc --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java @@ -0,0 +1,86 @@ +package com.bx.implatform.entity; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + *

+ * 群成员 + *

+ * + * @author blue + * @since 2022-10-31 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("im_group_member") +public class GroupMember extends Model { + + /** + * id + */ + @TableId + private Long id; + + /** + * 群id + */ + private Long groupId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户昵称 + */ + private String userNickName; + + /** + * 显示昵称备注 + */ + private String remarkNickName; + + /** + * 用户头像 + */ + private String headImage; + + /** + * 显示群名备注 + */ + private String remarkGroupName; + + /** + * 是否免打扰 + */ + private Boolean isDnd; + + + /** + * 是否已退出 + */ + private Boolean quit; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 退出时间 + */ + private Date quitTime; + + public String getShowNickName() { + return StrUtil.blankToDefault(remarkNickName, userNickName); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java b/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java new file mode 100644 index 0000000..77a038a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java @@ -0,0 +1,88 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * 群消息 + *

+ * + * @author blue + * @since 2022-10-31 + */ +@Data + +@TableName("im_group_message") +public class GroupMessage { + + /** + * id + */ + @TableId + private Long id; + + /** + * 临时id,由前端生成 + * 作用:如果用户正在发送消息时掉线了,可以通过此字段获取该消息的实际发送状态 + */ + private String tmpId; + + /** + * 群id + */ + private Long groupId; + + /** + * 发送用户id + */ + private Long sendId; + + /** + * 发送用户昵称 + */ + private String sendNickName; + + /** + * 接受用户id,为空表示全体发送 + */ + private String recvIds; + + /** + * @用户列表 + */ + private String atUserIds; + /** + * 发送内容 + */ + private String content; + + /** + * 消息类型 MessageType + */ + private Integer type; + + /** + * 是否回执消息 + */ + private Boolean receipt; + + /** + * 回执消息是否完成 + */ + private Boolean receiptOk; + + /** + * 状态 MessageStatus + */ + private Integer status; + + /** + * 发送时间 + */ + private Date sendTime; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java b/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java new file mode 100644 index 0000000..8c1dd52 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java @@ -0,0 +1,63 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * + *

+ * + * @author blue + * @since 2022-10-01 + */ +@Data +@TableName("im_private_message") +public class PrivateMessage { + + /** + * id + */ + private Long id; + + /** + * 临时id,由前端生成 + * 作用:如果用户正在发送消息时掉线了,可以通过此字段获取该消息的实际发送状态 + */ + private String tmpId; + + /** + * 发送用户id + */ + private Long sendId; + + /** + * 接收用户id + */ + private Long recvId; + + /** + * 发送内容 + */ + private String content; + + /** + * 消息类型 MessageType + */ + private Integer type; + + /** + * 状态 + */ + private Integer status; + + + /** + * 发送时间 + */ + private Date sendTime; + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java b/im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java new file mode 100644 index 0000000..8ced7c7 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java @@ -0,0 +1,50 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 敏感词 + * + * @author Blue + * @since 1.0.0 2024-07-20 + */ + +@Data +@TableName("im_sensitive_word") +public class SensitiveWord { + /** + * id + */ + @TableId + private Long id; + + /** + * 敏感词内容 + */ + private String content; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private Long creator; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/entity/User.java b/im-platform/src/main/java/com/bx/implatform/entity/User.java new file mode 100644 index 0000000..ca292e6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/entity/User.java @@ -0,0 +1,87 @@ +package com.bx.implatform.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + *

+ * 用户 + *

+ * + * @author blue + * @since 2022-10-01 + */ +@Data +@TableName("im_user") +public class User { + + /** + * id + */ + @TableId + private Long id; + + /** + * 用户名 + */ + private String userName; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 用户头像 + */ + private String headImage; + + /** + * 用户头像缩略图 + */ + private String headImageThumb; + + /** + * 密码(明文) + */ + private String password; + + /** + * 性别 0:男 1::女 + */ + private Integer sex; + + /** + * 个性签名 + */ + private String signature; + + /** + * 账号是否被封禁 + */ + private Boolean isBanned; + + /** + * 账号被封禁原因 + */ + private String reason; + + /** + * 最后登录时间 + */ + private Date lastLoginTime; + + /** + * 创建时间(注册时间) + */ + private Date createdTime; + + /** + * 账号类型 1:普通用户 2:wx小程序审核账户 + */ + private Integer type; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/enums/FileType.java b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java new file mode 100644 index 0000000..2237f48 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java @@ -0,0 +1,32 @@ +package com.bx.implatform.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum FileType { + + /** + * 文件 + */ + FILE(0, "文件"), + /** + * 图片 + */ + IMAGE(1, "图片"), + /** + * 视频 + */ + VIDEO(2, "视频"); + + private final Integer code; + + private final String desc; + + + public Integer code() { + return this.code; + } + + +} + diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java new file mode 100644 index 0000000..73197c1 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java @@ -0,0 +1,33 @@ +package com.bx.implatform.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum MessageStatus { + + /** + * 等待推送(未送达) + */ + PENDING(0, "等待推送"), + /** + * 已送达(未读) + */ + DELIVERED(1, "已送达"), + /** + * 撤回 + */ + RECALL(2, "撤回"), + /** + * 已读 + */ + READED(3, "已读"); + + private final Integer code; + + private final String desc; + + + public Integer code() { + return this.code; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java new file mode 100644 index 0000000..b044a44 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -0,0 +1,74 @@ +package com.bx.implatform.enums; + +import lombok.AllArgsConstructor; + + +/** + * 0-9: 真正的消息,需要存储到数据库 + * 10-19: 状态类消息: 撤回、已读、回执 + * 20-29: 提示类消息: 在会话中间显示的提示 + * 30-39: UI交互类消息: 显示加载状态等 + * 40-49: 操作交互类消息: 语音通话、视频通话消息等 + * 50-60: 后台操作类消息: 用户封禁、群组封禁等 + * 80-89: 好友变化消息 + * 90-99: 群聊变化消息 + * 100-199: 单人语音通话rtc信令 + * 200-299: 多人语音通话rtc信令 + * + */ +@AllArgsConstructor +public enum MessageType { + + TEXT(0, "文字消息"), + IMAGE(1, "图片消息"), + FILE(2, "文件消息"), + AUDIO(3, "语音消息"), + VIDEO(4, "视频消息"), + RECALL(10, "撤回"), + READED(11, "已读"), + RECEIPT(12, "消息已读回执"), + TIP_TIME(20,"时间提示"), + TIP_TEXT(21,"文字提示"), + LOADING(30,"加载中标记"), + ACT_RT_VOICE(40,"语音通话"), + ACT_RT_VIDEO(41,"视频通话"), + USER_BANNED(50,"用户封禁"), + GROUP_BANNED(51,"群聊封禁"), + GROUP_UNBAN(52,"群聊解封"), + FRIEND_NEW(80, "新增好友"), + FRIEND_DEL(81, "删除好友"), + FRIEND_DND(82, "好友免打扰"), + GROUP_NEW(90, "新增群聊"), + GROUP_DEL(91, "删除群聊"), + GROUP_DND(92, "群聊免打扰"), + RTC_CALL_VOICE(100, "语音呼叫"), + RTC_CALL_VIDEO(101, "视频呼叫"), + RTC_ACCEPT(102, "接受"), + RTC_REJECT(103, "拒绝"), + RTC_CANCEL(104, "取消呼叫"), + RTC_FAILED(105, "呼叫失败"), + RTC_HANDUP(106, "挂断"), + RTC_CANDIDATE(107, "同步candidate"), + RTC_GROUP_SETUP(200,"发起群视频通话"), + RTC_GROUP_ACCEPT(201,"接受通话呼叫"), + RTC_GROUP_REJECT(202,"拒绝通话呼叫"), + RTC_GROUP_FAILED(203,"拒绝通话呼叫"), + RTC_GROUP_CANCEL(204,"取消通话呼叫"), + RTC_GROUP_QUIT(205,"退出通话"), + RTC_GROUP_INVITE(206,"邀请进入通话"), + RTC_GROUP_JOIN(207,"主动进入通话"), + RTC_GROUP_OFFER(208,"推送offer信息"), + RTC_GROUP_ANSWER(209,"推送answer信息"), + RTC_GROUP_CANDIDATE(210,"同步candidate"), + RTC_GROUP_DEVICE(211,"设备操作"), + ; + + private final Integer code; + + private final String desc; + + + public Integer code() { + return this.code; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java new file mode 100644 index 0000000..fd1a871 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java @@ -0,0 +1,50 @@ +package com.bx.implatform.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 响应码枚举 + * + * @author Blue + * @date 2020/10/19 + **/ +@Getter +@AllArgsConstructor +public enum ResultCode { + /** + * 成功 + */ + SUCCESS(200, "成功"), + /** + * 未登录 + */ + NO_LOGIN(400, "未登录"), + /** + * token无效或已过期 + */ + INVALID_TOKEN(401, "token无效或已过期"), + /** + * 系统繁忙,请稍后再试 + */ + PROGRAM_ERROR(500, "系统繁忙,请稍后再试"), + /** + * 密码不正确 + */ + PASSWOR_ERROR(10001, "密码不正确"), + /** + * 该用户名已注册 + */ + USERNAME_ALREADY_REGISTER(10003, "该用户名已注册"), + /** + * 请不要输入非法内容 + */ + XSS_PARAM_ERROR(10004, "请不要输入非法内容"); + + + private final int code; + private final String msg; + + +} + diff --git a/im-platform/src/main/java/com/bx/implatform/enums/WebrtcMode.java b/im-platform/src/main/java/com/bx/implatform/enums/WebrtcMode.java new file mode 100644 index 0000000..ebaf099 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/enums/WebrtcMode.java @@ -0,0 +1,27 @@ +package com.bx.implatform.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Getter +@AllArgsConstructor +public enum WebrtcMode { + + /** + * 视频通话 + */ + VIDEO( "video"), + + /** + * 语音通话 + */ + VOICE( "voice"); + + private final String value; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java b/im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java new file mode 100644 index 0000000..049498f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java @@ -0,0 +1,34 @@ +package com.bx.implatform.exception; + +import com.bx.implatform.enums.ResultCode; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class GlobalException extends RuntimeException implements Serializable { + private static final long serialVersionUID = 8134030011662574394L; + private Integer code; + private String message; + + public GlobalException(Integer code, String message) { + this.code = code; + this.message = message; + } + + public GlobalException(ResultCode resultCode, String message) { + this.code = resultCode.getCode(); + this.message = message; + } + + public GlobalException(ResultCode resultCode) { + this.code = resultCode.getCode(); + this.message = resultCode.getMsg(); + } + + public GlobalException(String message) { + this.code = ResultCode.PROGRAM_ERROR.getCode(); + this.message = message; + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java b/im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..f703960 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java @@ -0,0 +1,100 @@ +package com.bx.implatform.exception; + +import cn.hutool.json.JSONException; +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.lang.reflect.UndeclaredThrowableException; +import java.time.format.DateTimeParseException; +import java.util.List; + +@ControllerAdvice +@ResponseBody +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(value = Exception.class) + public Result handleException(Exception e) { + if (e instanceof GlobalException) { + GlobalException ex = (GlobalException) e; + // token过期是正常情况,不打印 + if(!ex.getCode().equals(ResultCode.INVALID_TOKEN.getCode())){ + log.error("全局异常捕获:msg:{},log:{},{}", ex.getMessage(), e); + } + return ResultUtils.error(ex.getCode(), ex.getMessage()); + } else if (e instanceof UndeclaredThrowableException) { + GlobalException ex = (GlobalException) e.getCause(); + log.error("全局异常捕获:msg:{},log:{},{}", ex.getMessage(), e); + return ResultUtils.error(ex.getCode(), ex.getMessage()); + } else { + log.error("全局异常捕获:msg:{},{}", e.getMessage(), e); + return ResultUtils.error(ResultCode.PROGRAM_ERROR); + } + } + + + /** + * 数据解析错误 + **/ + @ExceptionHandler(value = HttpMessageNotReadableException.class) + public Result handleMessageNotReadableException(HttpMessageNotReadableException e) { + log.error("全局异常捕获:msg:{}", e.getMessage()); + Throwable t = e.getCause(); + if (t instanceof JSONException) { + t = t.getCause(); + if (t instanceof DateTimeParseException) { + return ResultUtils.error(ResultCode.PROGRAM_ERROR, "日期格式不正确"); + } + return ResultUtils.error(ResultCode.PROGRAM_ERROR, "数据格式不正确"); + } + return ResultUtils.error(ResultCode.PROGRAM_ERROR); + } + + /** + * 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常 + * + * @param exception + * @return + */ + @ExceptionHandler(value = {MethodArgumentNotValidException.class}) + @ResponseStatus(HttpStatus.OK) + public Result handleValidationExceptionHandler(MethodArgumentNotValidException exception) { + BindingResult bindResult = exception.getBindingResult(); + String msg; + if (bindResult.hasErrors()) { + msg = bindResult.getAllErrors().get(0).getDefaultMessage(); + if (msg.contains("NumberFormatException")) { + msg = "参数类型错误!"; + } + } else { + msg = "系统繁忙,请稍后重试..."; + } + return ResultUtils.error(ResultCode.PROGRAM_ERROR, msg); + } + + + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.OK) + public Result handleBindException(BindException e) { + //抛出异常可能不止一个 输出为一个List集合 + List errors = e.getAllErrors(); + //取第一个异常 + ObjectError error = errors.get(0); + //获取异常信息 + String errorMsg = error.getDefaultMessage(); + return ResultUtils.error(ResultCode.PROGRAM_ERROR, errorMsg); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java b/im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java new file mode 100644 index 0000000..6872796 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java @@ -0,0 +1,29 @@ +package com.bx.implatform.filter; + +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +@ServletComponentScan +@WebFilter(urlPatterns = "/*", filterName = "xssFilter") +public class CacheFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) { + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + chain.doFilter(new CacheHttpServletRequestWrapper((HttpServletRequest) request), response); + } + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java b/im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java new file mode 100644 index 0000000..1e301a6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java @@ -0,0 +1,57 @@ +package com.bx.implatform.filter; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.compress.utils.IOUtils; + +import java.io.*; + +public class CacheHttpServletRequestWrapper extends HttpServletRequestWrapper { + private byte[] requestBody; + private final HttpServletRequest request; + + public CacheHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + this.request = request; + } + + + @Override + public ServletInputStream getInputStream() throws IOException { + if (null == this.requestBody) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IOUtils.copy(request.getInputStream(), baos); + this.requestBody = baos.toByteArray(); + } + ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener listener) { + } + + @Override + public int read() { + return bais.read(); + } + }; + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java b/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java new file mode 100644 index 0000000..8cc0bf1 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java @@ -0,0 +1,50 @@ +package com.bx.implatform.interceptor; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.bx.imcommon.util.JwtUtil; +import com.bx.implatform.config.props.JwtProperties; +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.session.UserSession; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@Component +@AllArgsConstructor +public class AuthInterceptor implements HandlerInterceptor { + + private final JwtProperties jwtProperties; + + @Override + public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception { + //如果不是映射到方法直接通过 + if (!(handler instanceof HandlerMethod)) { + return true; + } + //从 http 请求头中取出 token + String token = request.getHeader("accessToken"); + if (StrUtil.isEmpty(token)) { + log.error("未登录,url:{}", request.getRequestURI()); + throw new GlobalException(ResultCode.NO_LOGIN); + } + String strJson = JwtUtil.getInfo(token); + UserSession userSession = JSON.parseObject(strJson, UserSession.class); + // 验证 token + if (!JwtUtil.checkSign(token, jwtProperties.getAccessTokenSecret())) { + log.error("token已失效,用户:{}", userSession.getUserName()); + log.error("token:{}", token); + throw new GlobalException(ResultCode.INVALID_TOKEN); + } + // 存放session + request.setAttribute("session", userSession); + return true; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java b/im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java new file mode 100644 index 0000000..073b2c4 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java @@ -0,0 +1,50 @@ +package com.bx.implatform.interceptor; + +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.util.XssUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.io.BufferedReader; +import java.util.Map; + +@Slf4j +@Component +public class XssInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { + // 检查参数 + Map paramMap = request.getParameterMap(); + for (String[] values : paramMap.values()) { + for (String value : values) { + if (XssUtil.checkXss(value)) { + throw new GlobalException(ResultCode.XSS_PARAM_ERROR); + } + } + } + // 检查body + String body = getBody(request); + if (XssUtil.checkXss(body)) { + throw new GlobalException(ResultCode.XSS_PARAM_ERROR); + } + return true; + } + + @SneakyThrows + private String getBody(HttpServletRequest request) { + BufferedReader reader = request.getReader(); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + return stringBuilder.toString(); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java new file mode 100644 index 0000000..25a31f6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java @@ -0,0 +1,32 @@ +package com.bx.implatform.listener; + +import com.bx.imclient.annotation.IMListener; +import com.bx.imclient.listener.MessageListener; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMSendResult; +import com.bx.implatform.vo.GroupMessageVO; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.List; + +@Slf4j +@IMListener(type = IMListenerType.GROUP_MESSAGE) +@AllArgsConstructor +public class GroupMessageListener implements MessageListener { + + private final RedisTemplate redisTemplate; + + @Override + public void process(List> results) { + for(IMSendResult result:results){ + GroupMessageVO messageInfo = result.getData(); + if (result.getCode().equals(IMSendCode.SUCCESS.code())) { + log.info("消息送达,消息id:{},发送者:{},接收者:{},终端:{}", messageInfo.getId(), result.getSender().getId(), result.getReceiver().getId(), result.getReceiver().getTerminal()); + } + } + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java new file mode 100644 index 0000000..ba61cd2 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java @@ -0,0 +1,53 @@ +package com.bx.implatform.listener; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.bx.imclient.annotation.IMListener; +import com.bx.imclient.listener.MessageListener; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMSendResult; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.service.PrivateMessageService; +import com.bx.implatform.vo.PrivateMessageVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Slf4j +@IMListener(type = IMListenerType.PRIVATE_MESSAGE) +public class PrivateMessageListener implements MessageListener { + @Lazy + @Autowired + private PrivateMessageService privateMessageService; + + @Override + public void process(List> results) { + Set messageIds = new HashSet<>(); + for (IMSendResult result : results) { + PrivateMessageVO messageInfo = result.getData(); + // 更新消息状态,这里只处理成功消息,失败的消息继续保持未读状态 + if (result.getCode().equals(IMSendCode.SUCCESS.code()) && !Objects.isNull(messageInfo.getId())) { + if (result.getReceiver().getId().equals(messageInfo.getRecvId())) { + messageIds.add(messageInfo.getId()); + log.info("消息送达,消息id:{},发送者:{},接收者:{},终端:{}", messageInfo.getId(), + result.getSender().getId(), result.getReceiver().getId(), result.getReceiver().getTerminal()); + } + } + } + // 批量修改状态 + if (CollUtil.isNotEmpty(messageIds)) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.lambda().in(PrivateMessage::getId, messageIds) + .eq(PrivateMessage::getStatus, MessageStatus.PENDING.code()) + .set(PrivateMessage::getStatus, MessageStatus.DELIVERED.code()); + privateMessageService.update(updateWrapper); + } + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java new file mode 100644 index 0000000..e97474d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java @@ -0,0 +1,26 @@ +package com.bx.implatform.listener; + +import com.bx.imclient.annotation.IMListener; +import com.bx.imclient.listener.MessageListener; +import com.bx.imcommon.enums.IMListenerType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMSendResult; +import com.bx.implatform.vo.SystemMessageVO; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +@IMListener(type = IMListenerType.SYSTEM_MESSAGE) +public class SystemMessageListener implements MessageListener { + + @Override + public void process(List> results) { + for(IMSendResult result : results){ + SystemMessageVO messageInfo = result.getData(); + if (result.getCode().equals(IMSendCode.SUCCESS.code())) { + log.info("消息送达,消息id:{},接收者:{},终端:{}", messageInfo.getId(), result.getReceiver().getId(), result.getReceiver().getTerminal()); + } + } + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java new file mode 100644 index 0000000..37ebf8c --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/FileInfoMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.FileInfo; + +public interface FileInfoMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/FriendMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/FriendMapper.java new file mode 100644 index 0000000..afde0ac --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/FriendMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.Friend; + +public interface FriendMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/GroupMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMapper.java new file mode 100644 index 0000000..a1cbfee --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.Group; + +public interface GroupMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/GroupMemberMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMemberMapper.java new file mode 100644 index 0000000..84afbfb --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMemberMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.GroupMember; + +public interface GroupMemberMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/GroupMessageMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMessageMapper.java new file mode 100644 index 0000000..518092f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/GroupMessageMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.GroupMessage; + +public interface GroupMessageMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/PrivateMessageMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/PrivateMessageMapper.java new file mode 100644 index 0000000..665e331 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/PrivateMessageMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.PrivateMessage; + +public interface PrivateMessageMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java new file mode 100644 index 0000000..d989801 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java @@ -0,0 +1,16 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.SensitiveWord; +import org.apache.ibatis.annotations.Mapper; + +/** +* 敏感词 +* +* @author Blue +* @since 1.0.0 2024-07-20 +*/ +@Mapper +public interface SensitiveWordMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/mapper/UserMapper.java b/im-platform/src/main/java/com/bx/implatform/mapper/UserMapper.java new file mode 100644 index 0000000..b512f4f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/mapper/UserMapper.java @@ -0,0 +1,8 @@ +package com.bx.implatform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bx.implatform.entity.User; + +public interface UserMapper extends BaseMapper { + +} diff --git a/im-platform/src/main/java/com/bx/implatform/result/Result.java b/im-platform/src/main/java/com/bx/implatform/result/Result.java new file mode 100644 index 0000000..3ad52cc --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/result/Result.java @@ -0,0 +1,14 @@ +package com.bx.implatform.result; + +import lombok.Data; + +@Data +public class Result { + + private int code; + + private String message; + + private T data; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java b/im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java new file mode 100644 index 0000000..447fcd0 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java @@ -0,0 +1,63 @@ +package com.bx.implatform.result; + +import com.bx.implatform.enums.ResultCode; + +public final class ResultUtils { + + private ResultUtils() { + } + + public static Result success() { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(ResultCode.SUCCESS.getMsg()); + return result; + } + + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(ResultCode.SUCCESS.getMsg()); + result.setData(data); + return result; + } + + public static Result success(T data, String messsage) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(messsage); + result.setData(data); + return result; + } + + public static Result success(String messsage) { + Result result = new Result<>(); + result.setCode(ResultCode.SUCCESS.getCode()); + result.setMessage(messsage); + return result; + } + + public static Result error(Integer code, String messsage) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(messsage); + return result; + } + + + public static Result error(ResultCode resultCode, String messsage) { + Result result = new Result<>(); + result.setCode(resultCode.getCode()); + result.setMessage(messsage); + return result; + } + + public static Result error(ResultCode resultCode) { + Result result = new Result<>(); + result.setCode(resultCode.getCode()); + result.setMessage(resultCode.getMsg()); + return result; + } + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/FileService.java b/im-platform/src/main/java/com/bx/implatform/service/FileService.java new file mode 100644 index 0000000..2540b9e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/FileService.java @@ -0,0 +1,15 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.entity.FileInfo; +import com.bx.implatform.vo.UploadImageVO; +import org.springframework.web.multipart.MultipartFile; + +public interface FileService extends IService { + + String uploadFile(MultipartFile file); + + UploadImageVO uploadImage(MultipartFile file,Boolean isPermanent,Long thumbSize); + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/FriendService.java b/im-platform/src/main/java/com/bx/implatform/service/FriendService.java new file mode 100644 index 0000000..d25a850 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/FriendService.java @@ -0,0 +1,81 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.FriendDndDTO; +import com.bx.implatform.entity.Friend; +import com.bx.implatform.vo.FriendVO; + +import java.util.List; + +public interface FriendService extends IService { + + /** + * 判断用户2是否用户1的好友 + * + * @param userId1 用户1的id + * @param userId2 用户2的id + * @return true/false + */ + Boolean isFriend(Long userId1, Long userId2); + + /** + * 查询用户的所有好友,包括已删除的 + * + * @return 好友列表 + */ + List findAllFriends(); + + /** + * 查询用户的所有好友 + * + * @param friendIds 好友id + * @return 好友列表 + */ + List findByFriendIds(List friendIds); + + /** + * 查询当前用户的所有好友 + * + * @return 好友列表 + */ + List findFriends(); + + /** + * 添加好友,互相建立好友关系 + * + * @param userId 用户id + * @param friendId 好友的用户id + */ + void addFriend(Long userId,Long friendId); + + /** + * 删除好友,双方都会解除好友关系 + * + * @param friendId 好友的用户id + */ + void delFriend(Long friendId); + + /** + * 查询指定的某个好友信息 + * + * @param friendId 好友的用户id + * @return 好友信息 + */ + FriendVO findFriend(Long friendId); + + /** + * 绑定好友关系 + * + * @param userId 好友的id + * @param friendId 好友的用户id + * @return 好友信息 + */ + void bindFriend(Long userId, Long friendId); + + /** + * 设置好友免打扰状态 + * @param dto + */ + void setDnd(FriendDndDTO dto); + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java b/im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java new file mode 100644 index 0000000..770943b --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java @@ -0,0 +1,102 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.GroupDndDTO; +import com.bx.implatform.entity.GroupMember; + +import java.util.List; + +public interface GroupMemberService extends IService { + + /** + * 根据群聊id和用户id查询群聊成员 + * + * @param groupId 群聊id + * @param userId 用户id + * @return 群聊成员信息 + */ + GroupMember findByGroupAndUserId(Long groupId, Long userId); + + + /** + * 根据用户id查询群聊成员 + * + * @param userId 用户id + * @return 成员列表 + */ + List findByUserId(Long userId); + + /** + * 根据用户id查询一个月内退的群 + * + * @param userId 用户id + * @return 成员列表 + */ + List findQuitInMonth(Long userId); + + /** + * 根据群聊id查询群聊成员(包括已退出) + * + * @param groupId 群聊id + * @return 群聊成员列表 + */ + List findByGroupId(Long groupId); + + + + /** + * 根据群聊id查询没有退出的群聊成员id + * + * @param groupId 群聊id + * @return 群聊成员id列表 + */ + List findUserIdsByGroupId(Long groupId); + + /** + * 批量添加成员 + * + * @param groupId 群聊id + * @param members 成员列表 + * @return 成功或失败 + */ + boolean saveOrUpdateBatch(Long groupId, List members); + + /** + * 根据群聊id删除移除成员 + * + * @param groupId 群聊id + */ + void removeByGroupId(Long groupId); + + /** + * 根据群聊id和用户id移除成员 + * + * @param groupId 群聊id + * @param userId 用户id + */ + void removeByGroupAndUserId(Long groupId, Long userId); + + /** + * 根据群聊id和用户id移除成员 + * + * @param groupId 群聊id + * @param userIds 用户id + */ + void removeByGroupAndUserIds(Long groupId, List userIds); + + /** + * 用户用户是否在群中 + * + * @param groupId 群聊id + * @param userIds 用户id + */ + Boolean isInGroup(Long groupId,List userIds); + + /** + * 设置免打扰状态 + * @param groupId 群id + * @param userId 用户id + * @param isDnd 是否开启免打扰 + */ + void setDnd(Long groupId, Long userId, Boolean isDnd); +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java new file mode 100644 index 0000000..5b526b6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java @@ -0,0 +1,65 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.GroupMessageDTO; +import com.bx.implatform.entity.GroupMessage; +import com.bx.implatform.vo.GroupMessageVO; + +import java.util.List; + +public interface GroupMessageService extends IService { + + /** + * 发送群聊消息(高并发接口,查询mysql接口都要进行缓存) + * + * @param dto 群聊消息 + * @return 群聊id + */ + GroupMessageVO sendMessage(GroupMessageDTO dto); + + /** + * 撤回消息 + * + * @param id 消息id + */ + GroupMessageVO recallMessage(Long id); + + /** + * 拉取离线消息,只能拉取最近1个月的消息,最多拉取1000条 + * + * @param minId 消息起始id + */ + void pullOfflineMessage(Long minId); + + /** + * 拉取离线消息,只能拉取最近1个月的消息 + * + * @param minId 消息起始id + */ + List loadOffineMessage(Long minId); + + + /** + * 消息已读,同步其他终端,清空未读数量 + * + * @param groupId 群聊 + */ + void readedMessage(Long groupId); + + /** + * 查询群里消息已读用户id列表 + * @param groupId 群里id + * @param messageId 消息id + * @return 已读用户id集合 + */ + List findReadedUsers(Long groupId,Long messageId); + /** + * 拉取历史聊天记录 + * + * @param groupId 群聊id + * @param page 页码 + * @param size 页码大小 + * @return 聊天记录列表 + */ + List findHistoryMessage(Long groupId, Long page, Long size); +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/GroupService.java b/im-platform/src/main/java/com/bx/implatform/service/GroupService.java new file mode 100644 index 0000000..a43320a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/GroupService.java @@ -0,0 +1,102 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.GroupDndDTO; +import com.bx.implatform.dto.GroupInviteDTO; +import com.bx.implatform.dto.GroupMemberRemoveDTO; +import com.bx.implatform.entity.Group; +import com.bx.implatform.vo.GroupMemberVO; +import com.bx.implatform.vo.GroupVO; + +import java.util.List; + +public interface GroupService extends IService { + + /** + * 创建新群聊 + * + * @param vo 群聊信息 + * @return 群聊信息 + **/ + GroupVO createGroup(GroupVO vo); + + /** + * 修改群聊信息 + * + * @param vo 群聊信息 + * @return 群聊信息 + **/ + GroupVO modifyGroup(GroupVO vo); + + /** + * 删除群聊 + * + * @param groupId 群聊id + **/ + void deleteGroup(Long groupId); + + /** + * 退出群聊 + * + * @param groupId 群聊id + */ + void quitGroup(Long groupId); + + /** + * 将用户踢出群聊 + * + * @param groupId 群聊id + * @param userId 用户id + */ + void kickGroup(Long groupId, Long userId); + + /** + * 将用户移出群聊 + * @param dto dto + */ + void removeGroupMembers(GroupMemberRemoveDTO dto); + + /** + * 查询当前用户的所有群聊 + * + * @return 群聊信息列表 + **/ + List findGroups(); + + /** + * 邀请好友进群 + * + * @param dto 群id、好友id列表 + **/ + void invite(GroupInviteDTO dto); + + /** + * 根据id查找群聊,并进行缓存 + * + * @param groupId 群聊id + * @return 群聊实体 + */ + Group getAndCheckById(Long groupId); + + /** + * 根据id查找群聊 + * + * @param groupId 群聊id + * @return 群聊vo + */ + GroupVO findById(Long groupId); + + /** + * 查询群成员 + * + * @param groupId 群聊id + * @return List + **/ + List findGroupMembers(Long groupId); + + /** + * 开启/关闭免打扰 + * @param dto + */ + void setDnd(GroupDndDTO dto); +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java new file mode 100644 index 0000000..ed2a269 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java @@ -0,0 +1,67 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.PrivateMessageDTO; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.vo.PrivateMessageVO; + +import java.util.List; + +public interface PrivateMessageService extends IService { + + /** + * 发送私聊消息(高并发接口,查询mysql接口都要进行缓存) + * + * @param dto 私聊消息 + * @return 消息id + */ + PrivateMessageVO sendMessage(PrivateMessageDTO dto); + + + /** + * 撤回消息 + * + * @param id 消息id + */ + PrivateMessageVO recallMessage(Long id); + + /** + * 拉取历史聊天记录 + * + * @param friendId 好友id + * @param page 页码 + * @param size 页码大小 + * @return 聊天记录列表 + */ + List findHistoryMessage(Long friendId, Long page, Long size); + + + /** + * 拉取离线消息,只能拉取最近1个月的消息,最多拉取1000条 + * + * @param minId 消息起始id + */ + void pullOfflineMessage(Long minId); + + /** + * 拉取离线消息,只能拉取最近1个月的消息 + * + * @param minId 消息起始id + */ + List loadOfflineMessage(Long minId); + + + /** + * 消息已读,将整个会话的消息都置为已读状态 + * + * @param friendId 好友id + */ + void readedMessage(Long friendId); + + /** + * 获取某个会话中已读消息的最大id + * + * @param friendId 好友id + */ + Long getMaxReadedId(Long friendId); +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java b/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java new file mode 100644 index 0000000..2a4f099 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java @@ -0,0 +1,15 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.entity.SensitiveWord; + +import java.util.List; + +public interface SensitiveWordService extends IService { + + /** + * 查询所有开启的敏感词 + * @return + */ + List findAllEnabledWords(); +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/UserService.java b/im-platform/src/main/java/com/bx/implatform/service/UserService.java new file mode 100644 index 0000000..37bcc8b --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/UserService.java @@ -0,0 +1,86 @@ +package com.bx.implatform.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bx.implatform.dto.LoginDTO; +import com.bx.implatform.dto.ModifyPwdDTO; +import com.bx.implatform.dto.RegisterDTO; +import com.bx.implatform.entity.User; +import com.bx.implatform.vo.LoginVO; +import com.bx.implatform.vo.OnlineTerminalVO; +import com.bx.implatform.vo.UserVO; + +import java.util.List; + +public interface UserService extends IService { + + /** + * 用户登录 + * + * @param dto 登录dto + * @return 登录token + */ + LoginVO login(LoginDTO dto); + + /** + * 修改用户密码 + * + * @param dto 修改密码dto + */ + void modifyPassword(ModifyPwdDTO dto); + + /** + * 用refreshToken换取新 token + * + * @param refreshToken 刷新token + * @return 登录token + */ + LoginVO refreshToken(String refreshToken); + + /** + * 用户注册 + * + * @param dto 注册dto + */ + void register(RegisterDTO dto); + + /** + * 根据用户名查询用户 + * + * @param username 用户名 + * @return 用户信息 + */ + User findUserByUserName(String username); + + /** + * 更新用户信息,好友昵称和群聊昵称等冗余信息也会更新 + * + * @param vo 用户信息vo + */ + void update(UserVO vo); + + /** + * 根据用户昵id查询用户以及在线状态 + * + * @param id 用户id + * @return 用户信息 + */ + UserVO findUserById(Long id); + + /** + * 根据用户昵称查询用户,最多返回20条数据 + * + * @param name 用户名或昵称 + * @return 用户列表 + */ + List findUserByName(String name); + + /** + * 获取用户在线的终端类型 + * + * @param userIds 用户id,多个用‘,’分割 + * @return 在线用户终端 + */ + List getOnlineTerminals(String userIds); + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java b/im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java new file mode 100644 index 0000000..c2f9c3f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java @@ -0,0 +1,26 @@ +package com.bx.implatform.service; + +/** + * webrtc 通信服务 + * + * @author + */ +public interface WebrtcPrivateService { + + void call(Long uid, String mode,String offer); + + void accept(Long uid, String answer); + + void reject(Long uid); + + void cancel(Long uid); + + void failed(Long uid, String reason); + + void handup(Long uid); + + void candidate(Long uid, String candidate); + + void heartbeat(Long uid); + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..9eb4ce5 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java @@ -0,0 +1,212 @@ +package com.bx.implatform.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.implatform.config.props.MinioProperties; +import com.bx.implatform.contant.Constant; +import com.bx.implatform.entity.FileInfo; +import com.bx.implatform.enums.FileType; +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.FileInfoMapper; +import com.bx.implatform.service.FileService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.thirdparty.MinioService; +import com.bx.implatform.util.FileUtil; +import com.bx.implatform.util.ImageUtil; +import com.bx.implatform.vo.UploadImageVO; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Date; +import java.util.Objects; + +/** + * 文件上传服务 + * + * author: Blue date: 2024-09-28 version: 1.0 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FileServiceImpl extends ServiceImpl implements FileService { + + private final MinioService minioSerivce; + + private final MinioProperties minioProps; + + @PostConstruct + public void init() { + if (!minioSerivce.bucketExists(minioProps.getBucketName())) { + // 创建bucket + minioSerivce.makeBucket(minioProps.getBucketName()); + // 公开bucket + minioSerivce.setBucketPublic(minioProps.getBucketName()); + } + } + + @Override + public String uploadFile(MultipartFile file) { + try { + Long userId = SessionContext.getSession().getUserId(); + // 文件名长度校验 + checkFileNameLength(file); + // 大小校验 + if (file.getSize() > Constant.MAX_FILE_SIZE) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件大小不能超过20M"); + } + // 如果文件已存在,直接复用 + String md5 = DigestUtils.md5DigestAsHex(file.getInputStream()); + FileInfo fileInfo = findByMd5(md5, FileType.FILE.code()); + if (!Objects.isNull(fileInfo)) { + // 更新上传时间 + fileInfo.setUploadTime(new Date()); + this.updateById(fileInfo); + // 返回 + return fileInfo.getFilePath(); + } + // 上传 + String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getFilePath(), file); + if (StringUtils.isEmpty(fileName)) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "文件上传失败"); + } + String url = generUrl(FileType.FILE, fileName); + // 保存文件 + saveFileInfo(file, md5, url); + log.info("文件文件成功,用户id:{},url:{}", userId, url); + return url; + } catch (IOException e) { + log.error("上传图片失败,{}", e.getMessage(), e); + throw new GlobalException(ResultCode.PROGRAM_ERROR, "上传图片失败"); + } + } + + @Transactional + @Override + public UploadImageVO uploadImage(MultipartFile file, Boolean isPermanent,Long thumbSize) { + try { + Long userId = SessionContext.getSession().getUserId(); + // 文件名长度校验 + checkFileNameLength(file); + // 大小校验 + if (file.getSize() > Constant.MAX_IMAGE_SIZE) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片大小不能超过20M"); + } + // 图片格式校验 + if (!FileUtil.isImage(file.getOriginalFilename())) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片格式不合法"); + } + UploadImageVO vo = new UploadImageVO(); + // 获取图片长度和宽度 + BufferedImage bufferedImage = ImageIO.read(file.getInputStream()); + if(!Objects.isNull(bufferedImage)){ + vo.setWidth(bufferedImage.getWidth()); + vo.setHeight(bufferedImage.getHeight()); + } + // 如果文件已存在,直接复用 + String md5 = DigestUtils.md5DigestAsHex(file.getInputStream()); + FileInfo fileInfo = findByMd5(md5, FileType.IMAGE.code()); + if (!Objects.isNull(fileInfo)) { + // 更新上传时间和持久化标记 + fileInfo.setIsPermanent(isPermanent || fileInfo.getIsPermanent()); + fileInfo.setUploadTime(new Date()); + this.updateById(fileInfo); + // 返回 + vo.setOriginUrl(fileInfo.getFilePath()); + vo.setThumbUrl(fileInfo.getCompressedPath()); + return vo; + } + // 上传原图 + String fileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(), file); + if (StringUtils.isEmpty(fileName)) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败"); + } + vo.setOriginUrl(generUrl(FileType.IMAGE, fileName)); + if (file.getSize() > thumbSize * 1024) { + // 大于50K的文件需上传缩略图 + byte[] imageByte = ImageUtil.compressForScale(file.getBytes(), thumbSize); + String thumbFileName = minioSerivce.upload(minioProps.getBucketName(), minioProps.getImagePath(), + file.getOriginalFilename(), imageByte, file.getContentType()); + if (StringUtils.isEmpty(thumbFileName)) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败"); + } + vo.setThumbUrl(generUrl(FileType.IMAGE, thumbFileName)); + // 保存文件信息 + saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), isPermanent); + } else { + // 小于50k,用原图充当缩略图 + vo.setThumbUrl(generUrl(FileType.IMAGE, fileName)); + // 保存文件信息,由于缩略图不允许删除,此时原图也不允许删除 + saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), true); + } + log.info("文件图片成功,用户id:{},url:{}", userId, vo.getOriginUrl()); + return vo; + } catch (IOException e) { + log.error("上传图片失败,{}", e.getMessage(), e); + throw new GlobalException(ResultCode.PROGRAM_ERROR, "图片上传失败"); + } + } + + private String generUrl(FileType fileType, String fileName) { + return StrUtil.join("/", minioProps.getDomain(), minioProps.getBucketName(), getBucketPath(fileType), fileName); + } + + private String getBucketPath(FileType fileType) { + return switch (fileType) { + case FILE -> minioProps.getFilePath(); + case IMAGE -> minioProps.getImagePath(); + case VIDEO -> minioProps.getVideoPath(); + }; + } + + private FileInfo findByMd5(String md5, Integer fileType) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(FileInfo::getMd5, md5); + wrapper.eq(FileInfo::getFileType, fileType); + wrapper.last("limit 1"); + return getOne(wrapper); + } + + private void saveImageFileInfo(MultipartFile file, String md5, String filePath, String compressedPath, + Boolean isPermanent) throws IOException { + FileInfo fileInfo = new FileInfo(); + fileInfo.setFileName(file.getOriginalFilename()); + fileInfo.setFileSize(file.getSize()); + fileInfo.setFileType(FileType.IMAGE.code()); + fileInfo.setFilePath(filePath); + fileInfo.setCompressedPath(compressedPath); + fileInfo.setMd5(md5); + fileInfo.setIsPermanent(isPermanent); + fileInfo.setUploadTime(new Date()); + this.save(fileInfo); + } + + private void saveFileInfo(MultipartFile file, String md5, String filePath) throws IOException { + FileInfo fileInfo = new FileInfo(); + fileInfo.setFileName(file.getOriginalFilename()); + fileInfo.setFileSize(file.getSize()); + fileInfo.setFileType(FileType.FILE.code()); + fileInfo.setFilePath(filePath); + fileInfo.setMd5(md5); + fileInfo.setIsPermanent(false); + fileInfo.setUploadTime(new Date()); + this.save(fileInfo); + } + + private void checkFileNameLength(MultipartFile file){ + if(file.getOriginalFilename().length() > Constant.MAX_FILE_NAME_LENGTH){ + throw new GlobalException("文件名长度不能超过" + Constant.MAX_FILE_NAME_LENGTH); + } + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java new file mode 100644 index 0000000..82fc1df --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java @@ -0,0 +1,289 @@ +package com.bx.implatform.service.impl; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMPrivateMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.implatform.annotation.RedisLock; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.FriendDndDTO; +import com.bx.implatform.entity.Friend; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.entity.User; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.FriendMapper; +import com.bx.implatform.mapper.PrivateMessageMapper; +import com.bx.implatform.mapper.UserMapper; +import com.bx.implatform.service.FriendService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.FriendVO; +import com.bx.implatform.vo.PrivateMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.framework.AopContext; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = RedisKey.IM_CACHE_FRIEND) +public class FriendServiceImpl extends ServiceImpl implements FriendService { + + private final PrivateMessageMapper privateMessageMapper; + private final UserMapper userMapper; + private final IMClient imClient; + + @Override + public List findAllFriends() { + Long userId = SessionContext.getSession().getUserId(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(Friend::getUserId, userId); + return this.list(wrapper); + } + + @Override + public List findByFriendIds(List friendIds) { + Long userId = SessionContext.getSession().getUserId(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(Friend::getUserId, userId); + wrapper.in(Friend::getFriendId, friendIds); + wrapper.eq(Friend::getDeleted,false); + return this.list(wrapper); + } + + @Override + public List findFriends() { + List friends = this.findAllFriends(); + return friends.stream().map(this::conver).collect(Collectors.toList()); + } + + @RedisLock(prefixKey = RedisKey.IM_LOCK_FRIEND_ADD, key = "#userId+':'+#friendId") + @Transactional(rollbackFor = Exception.class) + @Override + public void addFriend(Long userId, Long friendId) { + if (friendId.equals(userId)) { + throw new GlobalException("不允许添加自己为好友"); + } + // 互相绑定好友关系 + FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy(); + proxy.bindFriend(userId, friendId); + proxy.bindFriend(friendId, userId); + // 推送添加好友提示 + sendAddTipMessage(friendId); + log.info("添加好友,用户id:{},好友id:{}", userId, friendId); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delFriend(Long friendId) { + Long userId = SessionContext.getSession().getUserId(); + // 互相解除好友关系,走代理清理缓存 + FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy(); + proxy.unbindFriend(userId, friendId); + proxy.unbindFriend(friendId, userId); + // 推送解除好友提示 + sendDelTipMessage(friendId); + log.info("删除好友,用户id:{},好友id:{}", userId, friendId); + } + + @Cacheable(key = "#userId1+':'+#userId2") + @Override + public Boolean isFriend(Long userId1, Long userId2) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(Friend::getUserId, userId1); + wrapper.eq(Friend::getFriendId, userId2); + wrapper.eq(Friend::getDeleted,false); + return this.exists(wrapper); + } + + /** + * 单向绑定好友关系 + * + * @param userId 用户id + * @param friendId 好友的用户id + */ + @CacheEvict(key = "#userId+':'+#friendId") + public void bindFriend(Long userId, Long friendId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(Friend::getUserId, userId).eq(Friend::getFriendId, friendId); + Friend friend = this.getOne(wrapper); + if (Objects.isNull(friend)) { + friend = new Friend(); + } + friend.setUserId(userId); + friend.setFriendId(friendId); + User friendInfo = userMapper.selectById(friendId); + friend.setFriendHeadImage(friendInfo.getHeadImageThumb()); + friend.setFriendNickName(friendInfo.getNickName()); + friend.setDeleted(false); + this.saveOrUpdate(friend); + // 推送好友变化信息s + sendAddFriendMessage(userId, friendId, friend); + } + + @Override + public void setDnd(FriendDndDTO dto) { + UserSession session = SessionContext.getSession(); + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(Friend::getUserId, session.getUserId()); + wrapper.eq(Friend::getFriendId, dto.getFriendId()); + wrapper.set(Friend::getIsDnd, dto.getIsDnd()); + this.update(wrapper); + // 推送同步消息 + sendSyncDndMessage(dto.getFriendId(), dto.getIsDnd()); + } + + /** + * 单向解除好友关系 + * + * @param userId 用户id + * @param friendId 好友的用户id + */ + @CacheEvict(key = "#userId+':'+#friendId") + public void unbindFriend(Long userId, Long friendId) { + // 逻辑删除 + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(Friend::getUserId, userId); + wrapper.eq(Friend::getFriendId, friendId); + wrapper.set(Friend::getDeleted,true); + this.update(wrapper); + // 推送好友变化信息 + sendDelFriendMessage(userId, friendId); + } + + @Override + public FriendVO findFriend(Long friendId) { + UserSession session = SessionContext.getSession(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(Friend::getUserId, session.getUserId()); + wrapper.eq(Friend::getFriendId, friendId); + Friend friend = this.getOne(wrapper); + if (Objects.isNull(friend)) { + throw new GlobalException("对方不是您的好友"); + } + return conver(friend); + } + + private FriendVO conver(Friend f) { + FriendVO vo = new FriendVO(); + vo.setId(f.getFriendId()); + vo.setHeadImage(f.getFriendHeadImage()); + vo.setNickName(f.getFriendNickName()); + vo.setDeleted(f.getDeleted()); + vo.setIsDnd(f.getIsDnd()); + return vo; + } + + void sendAddFriendMessage(Long userId, Long friendId, Friend friend) { + // 推送好友状态信息 + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setSendId(friendId); + msgInfo.setRecvId(userId); + msgInfo.setSendTime(new Date()); + msgInfo.setType(MessageType.FRIEND_NEW.code()); + FriendVO vo = conver(friend); + msgInfo.setContent(JSON.toJSONString(vo)); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code())); + sendMessage.setRecvId(userId); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendPrivateMessage(sendMessage); + } + + void sendDelFriendMessage(Long userId, Long friendId) { + // 推送好友状态信息 + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setSendId(friendId); + msgInfo.setRecvId(userId); + msgInfo.setSendTime(new Date()); + msgInfo.setType(MessageType.FRIEND_DEL.code()); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code())); + sendMessage.setRecvId(userId); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendPrivateMessage(sendMessage); + } + + void sendAddTipMessage(Long friendId) { + UserSession session = SessionContext.getSession(); + PrivateMessage msg = new PrivateMessage(); + msg.setSendId(session.getUserId()); + msg.setRecvId(friendId); + msg.setContent("你们已成为好友,现在可以开始聊天了"); + msg.setSendTime(new Date()); + msg.setStatus(MessageStatus.PENDING.code()); + msg.setType(MessageType.TIP_TEXT.code()); + privateMessageMapper.insert(msg); + // 推给对方 + PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(friendId); + sendMessage.setSendToSelf(false); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + // 推给自己 + sendMessage.setRecvId(session.getUserId()); + imClient.sendPrivateMessage(sendMessage); + } + + void sendDelTipMessage(Long friendId){ + UserSession session = SessionContext.getSession(); + // 推送好友状态信息 + PrivateMessage msg = new PrivateMessage(); + msg.setSendId(session.getUserId()); + msg.setRecvId(friendId); + msg.setSendTime(new Date()); + msg.setType(MessageType.TIP_TEXT.code()); + msg.setStatus(MessageStatus.PENDING.code()); + msg.setContent("你们的好友关系已被解除"); + privateMessageMapper.insert(msg); + // 推送 + PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code())); + sendMessage.setRecvId(friendId); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + } + + void sendSyncDndMessage(Long friendId, Boolean isDnd) { + // 同步免打扰状态到其他终端 + UserSession session = SessionContext.getSession(); + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setSendId(session.getUserId()); + msgInfo.setRecvId(friendId); + msgInfo.setSendTime(new Date()); + msgInfo.setType(MessageType.FRIEND_DND.code()); + msgInfo.setContent(isDnd.toString()); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(true); + imClient.sendPrivateMessage(sendMessage); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java new file mode 100644 index 0000000..9302c61 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java @@ -0,0 +1,131 @@ +package com.bx.implatform.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.entity.GroupMember; +import com.bx.implatform.mapper.GroupMemberMapper; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.util.DateTimeUtils; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID) +public class GroupMemberServiceImpl extends ServiceImpl implements GroupMemberService { + @CacheEvict(key = "#member.getGroupId()") + @Override + public boolean save(GroupMember member) { + return super.save(member); + } + + @CacheEvict(key = "#groupId") + @Override + public boolean saveOrUpdateBatch(Long groupId, List members) { + return super.saveOrUpdateBatch(members); + } + + @Override + public GroupMember findByGroupAndUserId(Long groupId, Long userId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(GroupMember::getGroupId, groupId); + wrapper.eq(GroupMember::getUserId, userId); + return this.getOne(wrapper); + } + + + @Override + public List findByUserId(Long userId) { + LambdaQueryWrapper memberWrapper = Wrappers.lambdaQuery(); + memberWrapper.eq(GroupMember::getUserId, userId).eq(GroupMember::getQuit, false); + return this.list(memberWrapper); + } + + @Override + public List findQuitInMonth(Long userId) { + Date monthTime = DateTimeUtils.addMonths(new Date(), -1); + LambdaQueryWrapper memberWrapper = Wrappers.lambdaQuery(); + memberWrapper.eq(GroupMember::getUserId, userId).eq(GroupMember::getQuit, true) + .ge(GroupMember::getQuitTime, monthTime); + return this.list(memberWrapper); + } + + @Override + public List findByGroupId(Long groupId) { + LambdaQueryWrapper memberWrapper = Wrappers.lambdaQuery(); + memberWrapper.eq(GroupMember::getGroupId, groupId); + return this.list(memberWrapper); + } + + @Cacheable(key = "#groupId") + @Override + public List findUserIdsByGroupId(Long groupId) { + LambdaQueryWrapper memberWrapper = Wrappers.lambdaQuery(); + memberWrapper.eq(GroupMember::getGroupId, groupId).eq(GroupMember::getQuit, false) + .select(GroupMember::getUserId); + List members = this.list(memberWrapper); + return members.stream().map(GroupMember::getUserId).collect(Collectors.toList()); + } + + @CacheEvict(key = "#groupId") + @Override + public void removeByGroupId(Long groupId) { + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(GroupMember::getGroupId, groupId).set(GroupMember::getQuit, true) + .set(GroupMember::getQuitTime, new Date()); + this.update(wrapper); + } + + @CacheEvict(key = "#groupId") + @Override + public void removeByGroupAndUserId(Long groupId, Long userId) { + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(GroupMember::getGroupId, groupId); + wrapper.eq(GroupMember::getUserId, userId); + wrapper.set(GroupMember::getQuit, true); + wrapper.set(GroupMember::getQuitTime, new Date()); + this.update(wrapper); + } + + @CacheEvict(key = "#groupId") + @Override + public void removeByGroupAndUserIds(Long groupId, List userId) { + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(GroupMember::getGroupId, groupId); + wrapper.in(GroupMember::getUserId, userId); + wrapper.set(GroupMember::getQuit, true); + wrapper.set(GroupMember::getQuitTime, new Date()); + this.update(wrapper); + } + + + @Override + public Boolean isInGroup(Long groupId, List userIds) { + if (CollectionUtils.isEmpty(userIds)) { + return true; + } + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(GroupMember::getGroupId, groupId); + wrapper.eq(GroupMember::getQuit, false); + wrapper.in(GroupMember::getUserId, userIds); + return userIds.size() == this.count(wrapper); + } + + @Override + public void setDnd(Long groupId, Long userId, Boolean isDnd) { + LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); + wrapper.eq(GroupMember::getGroupId, groupId); + wrapper.eq(GroupMember::getUserId, userId); + wrapper.set(GroupMember::getIsDnd, isDnd); + this.update(wrapper); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java new file mode 100644 index 0000000..81c91d5 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -0,0 +1,470 @@ +package com.bx.implatform.service.impl; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; +import com.bx.imcommon.contant.IMConstant; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMGroupMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.util.CommaTextUtils; +import com.bx.imcommon.util.ThreadPoolExecutorFactory; +import com.bx.implatform.contant.Constant; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.GroupMessageDTO; +import com.bx.implatform.entity.Group; +import com.bx.implatform.entity.GroupMember; +import com.bx.implatform.entity.GroupMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.GroupMessageMapper; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.service.GroupMessageService; +import com.bx.implatform.service.GroupService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.util.SensitiveFilterUtil; +import com.bx.implatform.vo.GroupMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GroupMessageServiceImpl extends ServiceImpl + implements GroupMessageService { + private final GroupService groupService; + private final GroupMemberService groupMemberService; + private final RedisTemplate redisTemplate; + private final IMClient imClient; + private final SensitiveFilterUtil sensitiveFilterUtil; + private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor(); + + @Override + public GroupMessageVO sendMessage(GroupMessageDTO dto) { + UserSession session = SessionContext.getSession(); + Group group = groupService.getAndCheckById(dto.getGroupId()); + // 是否在群聊里面 + GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId()); + if (Objects.isNull(member) || member.getQuit()) { + throw new GlobalException("您已不在群聊里面,无法发送消息"); + } + // 群聊成员列表 + List userIds = groupMemberService.findUserIdsByGroupId(group.getId()); + if (dto.getReceipt() && userIds.size() > Constant.MAX_NORMAL_GROUP_MEMBER) { + // 大群的回执消息过于消耗资源,不允许发送 + throw new GlobalException( + String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_NORMAL_GROUP_MEMBER)); + } + // 不用发给自己 + userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList()); + // 保存消息 + GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); + msg.setSendId(session.getUserId()); + msg.setSendTime(new Date()); + msg.setSendNickName(member.getShowNickName()); + msg.setAtUserIds(CommaTextUtils.asText(dto.getAtUserIds())); + msg.setStatus(MessageStatus.PENDING.code()); + // 过滤内容中的敏感词 + if (MessageType.TEXT.code().equals(dto.getType())) { + msg.setContent(sensitiveFilterUtil.filter(dto.getContent())); + } + this.save(msg); + // 群发 + GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); + msgInfo.setAtUserIds(dto.getAtUserIds()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(userIds); + sendMessage.setSendResult(false); + sendMessage.setData(msgInfo); + imClient.sendGroupMessage(sendMessage); + log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), dto.getGroupId(), dto.getContent()); + return msgInfo; + } + + @Transactional + @Override + public GroupMessageVO recallMessage(Long id) { + UserSession session = SessionContext.getSession(); + GroupMessage msg = this.getById(id); + if (Objects.isNull(msg)) { + throw new GlobalException("消息不存在"); + } + if (!msg.getSendId().equals(session.getUserId())) { + throw new GlobalException("这条消息不是由您发送,无法撤回"); + } + if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { + throw new GlobalException("消息已发送超过5分钟,无法撤回"); + } + // 判断是否在群里 + GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId()); + if (Objects.isNull(member) || Boolean.TRUE.equals(member.getQuit())) { + throw new GlobalException("您已不在群聊里面,无法撤回消息"); + } + // 修改数据库 + msg.setStatus(MessageStatus.RECALL.code()); + this.updateById(msg); + // 生成一条撤回消息 + GroupMessage recallMsg = new GroupMessage(); + recallMsg.setStatus(MessageStatus.PENDING.code()); + recallMsg.setType(MessageType.RECALL.code()); + recallMsg.setGroupId(msg.getGroupId()); + recallMsg.setSendId(session.getUserId()); + recallMsg.setSendNickName(member.getShowNickName()); + recallMsg.setContent(id.toString()); + recallMsg.setSendTime(new Date()); + this.save(recallMsg); + // 群发 + List userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); + GroupMessageVO msgInfo = BeanUtils.copyProperties(recallMsg, GroupMessageVO.class); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(userIds); + sendMessage.setData(msgInfo); + imClient.sendGroupMessage(sendMessage); + log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), msg.getGroupId(), msg.getContent()); + return msgInfo; + } + + @Override + public void pullOfflineMessage(Long minId) { + UserSession session = SessionContext.getSession(); + if (!imClient.isOnline(session.getUserId())) { + throw new GlobalException("网络连接失败,无法拉取离线消息"); + } + // 查询用户加入的群组 + List members = groupMemberService.findByUserId(session.getUserId()); + Map groupMemberMap = CollStreamUtil.toIdentityMap(members, GroupMember::getGroupId); + Set groupIds = groupMemberMap.keySet(); + if (CollectionUtil.isEmpty(groupIds)) { + // 关闭加载中标志 + this.sendLoadingMessage(false, session); + return; + } + // 只拉最近一个月 + Date minDate = DateUtils.addMonths(new Date(), -1); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId); + wrapper.gt(GroupMessage::getSendTime, minDate); + wrapper.in(GroupMessage::getGroupId, groupIds); + wrapper.orderByDesc(GroupMessage::getId); + wrapper.last("limit 50000"); + List messages = this.list(wrapper); + // 通过群聊对消息进行分组 + Map> messageGroupMap = + messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId)); + // 退群前的消息 + List quitMembers = groupMemberService.findQuitInMonth(session.getUserId()); + for (GroupMember quitMember : quitMembers) { + wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId); + wrapper.between(GroupMessage::getSendTime, minDate, quitMember.getQuitTime()); + wrapper.eq(GroupMessage::getGroupId, quitMember.getGroupId()); + wrapper.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); + wrapper.orderByDesc(GroupMessage::getId); + List groupMessages = this.list(wrapper); + messageGroupMap.put(quitMember.getGroupId(), groupMessages); + groupMemberMap.put(quitMember.getGroupId(), quitMember); + } + EXECUTOR.execute(() -> { + // 开启加载中标志 + this.sendLoadingMessage(true, session); + // 推送消息 + int sendCount = 0; + for (Map.Entry> entry : messageGroupMap.entrySet()) { + Long groupId = entry.getKey(); + List groupMessages = entry.getValue(); + // 第一次拉取时,一个群最多推送3000条消息,防止前端接收能力溢出导致卡顿 + List sendMessages = groupMessages; + if (minId <= 0 && groupMessages.size() > 3000) { + sendMessages = groupMessages.subList(0, 3000); + } + // id从小到大排序 + CollectionUtil.reverse(sendMessages); + // 填充消息状态 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString()); + long readedMaxId = Objects.isNull(o) ? -1 : Long.parseLong(o.toString()); + Map maxIdMap = null; + for (GroupMessage m : sendMessages) { + // 推送过程如果用户下线了,则不再推送 + if (!imClient.isOnline(session.getUserId(), IMTerminalType.fromCode(session.getTerminal()))) { + log.info("用户已下线,停止推送离线群聊消息,用户id:{}", session.getUserId()); + return; + } + // 排除加群之前的消息 + GroupMember member = groupMemberMap.get(m.getGroupId()); + if (DateUtil.compare(member.getCreatedTime(), m.getSendTime()) > 0) { + continue; + } + // 排除不需要接收的消息 + List recvIds = CommaTextUtils.asList(m.getRecvIds()); + if (!recvIds.isEmpty() && !recvIds.contains(session.getUserId().toString())) { + continue; + } + // 组装vo + GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class); + // 被@用户列表 + List atIds = CommaTextUtils.asList(m.getAtUserIds()); + vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList())); + // 填充状态 + vo.setStatus(readedMaxId >= m.getId() ? MessageStatus.READED.code() : MessageStatus.PENDING.code()); + // 针对回执消息填充已读人数 + if (m.getReceipt()) { + if (Objects.isNull(maxIdMap)) { + maxIdMap = redisTemplate.opsForHash().entries(key); + } + int count = getReadedUserIds(maxIdMap, m.getId(), m.getSendId()).size(); + vo.setReadedCount(count); + } + // 推送 + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(m.getSendId(), IMTerminalType.WEB.code())); + sendMessage.setRecvIds(Arrays.asList(session.getUserId())); + sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal())); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(false); + sendMessage.setData(vo); + imClient.sendGroupMessage(sendMessage); + sendCount++; + } + } + // 关闭加载中标志 + this.sendLoadingMessage(false, session); + log.info("拉取离线群聊消息,用户id:{},数量:{}", session.getUserId(), sendCount++); + }); + } + + @Override + public List loadOffineMessage(Long minId) { + UserSession session = SessionContext.getSession(); + // 查询用户加入的群组 + List members = groupMemberService.findByUserId(session.getUserId()); + Map groupMemberMap = CollStreamUtil.toIdentityMap(members, GroupMember::getGroupId); + Set groupIds = groupMemberMap.keySet(); + if (groupIds.isEmpty()) { + return Collections.EMPTY_LIST; + } + // 只能拉取最近1个月的消息 + Date minDate = DateUtils.addMonths(new Date(), -1); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId); + wrapper.gt(GroupMessage::getSendTime, minDate); + wrapper.in(GroupMessage::getGroupId, groupIds); + wrapper.orderByDesc(GroupMessage::getId); + wrapper.last("limit 50000"); + List messages = this.list(wrapper); + // 退群前的消息 + List quitMembers = groupMemberService.findQuitInMonth(session.getUserId()); + for (GroupMember quitMember : quitMembers) { + wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId); + wrapper.between(GroupMessage::getSendTime, minDate, quitMember.getQuitTime()); + wrapper.eq(GroupMessage::getGroupId, quitMember.getGroupId()); + wrapper.orderByDesc(GroupMessage::getId); + wrapper.last("limit 1000"); + List groupMessages = this.list(wrapper); + if (!groupMessages.isEmpty()) { + messages.addAll(groupMessages); + groupMemberMap.put(quitMember.getGroupId(), quitMember); + } + } + // 通过群聊对消息进行分组 + Map> messageGroupMap = + messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId)); + List vos = new LinkedList<>(); + for (Map.Entry> entry : messageGroupMap.entrySet()) { + Long groupId = entry.getKey(); + List groupMessages = entry.getValue(); + // 填充消息状态 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString()); + long readedMaxId = Objects.isNull(o) ? -1 : Long.parseLong(o.toString()); + Map maxIdMap = null; + for (GroupMessage m : groupMessages) { + // 排除加群之前的消息 + GroupMember member = groupMemberMap.get(m.getGroupId()); + if (DateUtil.compare(member.getCreatedTime(), m.getSendTime()) > 0) { + continue; + } + // 排除不需要接收的消息 + List recvIds = CommaTextUtils.asList(m.getRecvIds()); + if (!recvIds.isEmpty() && !recvIds.contains(session.getUserId().toString())) { + continue; + } + // 组装vo + GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class); + // 被@用户列表 + List atIds = CommaTextUtils.asList(m.getAtUserIds()); + vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList())); + // 填充状态 + vo.setStatus(readedMaxId >= m.getId() ? MessageStatus.READED.code() : MessageStatus.PENDING.code()); + // 针对回执消息填充已读人数 + if (m.getReceipt()) { + if (Objects.isNull(maxIdMap)) { + maxIdMap = redisTemplate.opsForHash().entries(key); + } + int count = getReadedUserIds(maxIdMap, m.getId(), m.getSendId()).size(); + vo.setReadedCount(count); + } + vos.add(vo); + } + } + // 排序 + return vos.stream().sorted(Comparator.comparing(GroupMessageVO::getId)).collect(Collectors.toList()); + } + + + @Override + public void readedMessage(Long groupId) { + UserSession session = SessionContext.getSession(); + // 取出最后的消息id + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(GroupMessage::getGroupId, groupId).orderByDesc(GroupMessage::getId).last("limit 1") + .select(GroupMessage::getId); + GroupMessage message = this.getOne(wrapper); + if (Objects.isNull(message)) { + return; + } + // 推送消息给自己的其他终端,同步清空会话列表中的未读数量 + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.READED.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setSendId(session.getUserId()); + msgInfo.setGroupId(groupId); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setSendToSelf(true); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(true); + imClient.sendGroupMessage(sendMessage); + // 已读消息key + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + // 原来的已读消息位置 + Object maxReadedId = redisTemplate.opsForHash().get(key, session.getUserId().toString()); + // 记录已读消息位置 + redisTemplate.opsForHash().put(key, session.getUserId().toString(), message.getId()); + // 推送消息回执,刷新已读人数显示 + wrapper = Wrappers.lambdaQuery(); + wrapper.eq(GroupMessage::getGroupId, groupId); + wrapper.gt(!Objects.isNull(maxReadedId), GroupMessage::getId, maxReadedId); + wrapper.le(!Objects.isNull(maxReadedId), GroupMessage::getId, message.getId()); + wrapper.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); + wrapper.eq(GroupMessage::getReceipt, true); + List receiptMessages = this.list(wrapper); + if (CollectionUtil.isNotEmpty(receiptMessages)) { + List userIds = groupMemberService.findUserIdsByGroupId(groupId); + Map maxIdMap = redisTemplate.opsForHash().entries(key); + for (GroupMessage receiptMessage : receiptMessages) { + Integer readedCount = + getReadedUserIds(maxIdMap, receiptMessage.getId(), receiptMessage.getSendId()).size(); + // 如果所有人都已读,记录回执消息完成标记 + if (readedCount >= userIds.size() - 1) { + receiptMessage.setReceiptOk(true); + this.updateById(receiptMessage); + } + msgInfo = new GroupMessageVO(); + msgInfo.setId(receiptMessage.getId()); + msgInfo.setGroupId(groupId); + msgInfo.setReadedCount(readedCount); + msgInfo.setReceiptOk(receiptMessage.getReceiptOk()); + msgInfo.setType(MessageType.RECEIPT.code()); + sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(userIds); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); + } + } + } + + @Override + public List findReadedUsers(Long groupId, Long messageId) { + UserSession session = SessionContext.getSession(); + GroupMessage message = this.getById(messageId); + if (Objects.isNull(message)) { + throw new GlobalException("消息不存在"); + } + // 是否在群聊里面 + GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId()); + if (Objects.isNull(member) || member.getQuit()) { + throw new GlobalException("您已不在群聊里面"); + } + // 已读位置key + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + // 一次获取所有用户的已读位置 + Map maxIdMap = redisTemplate.opsForHash().entries(key); + // 返回已读用户的id集合 + return getReadedUserIds(maxIdMap, message.getId(), message.getSendId()); + } + + @Override + public List findHistoryMessage(Long groupId, Long page, Long size) { + page = page > 0 ? page : 1; + size = size > 0 ? size : 10; + Long userId = SessionContext.getSession().getUserId(); + long stIdx = (page - 1) * size; + // 群聊成员信息 + GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId); + if (Objects.isNull(member) || member.getQuit()) { + throw new GlobalException("您已不在群聊中"); + } + // 查询聊天记录,只查询加入群聊时间之后的消息 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().eq(GroupMessage::getGroupId, groupId).gt(GroupMessage::getSendTime, member.getCreatedTime()) + .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()).orderByDesc(GroupMessage::getId) + .last("limit " + stIdx + "," + size); + List messages = this.list(wrapper); + List messageInfos = + messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); + log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}", userId, groupId, messageInfos.size()); + return messageInfos; + } + + private List getReadedUserIds(Map maxIdMap, Long messageId, Long sendId) { + List userIds = new LinkedList<>(); + maxIdMap.forEach((k, v) -> { + Long userId = Long.valueOf(k.toString()); + long maxId = Long.parseLong(v.toString()); + // 发送者不计入已读人数 + if (!sendId.equals(userId) && maxId >= messageId) { + userIds.add(userId); + } + }); + return userIds; + } + + private void sendLoadingMessage(Boolean isLoadding, UserSession session) { + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.LOADING.code()); + msgInfo.setContent(isLoadding.toString()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(Arrays.asList(session.getUserId())); + sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal())); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java new file mode 100644 index 0000000..34daed1 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java @@ -0,0 +1,418 @@ +package com.bx.implatform.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; +import com.bx.imcommon.model.IMGroupMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.util.CommaTextUtils; +import com.bx.implatform.annotation.RedisLock; +import com.bx.implatform.contant.Constant; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.GroupDndDTO; +import com.bx.implatform.dto.GroupInviteDTO; +import com.bx.implatform.dto.GroupMemberRemoveDTO; +import com.bx.implatform.entity.*; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.GroupMapper; +import com.bx.implatform.mapper.GroupMessageMapper; +import com.bx.implatform.service.FriendService; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.service.GroupService; +import com.bx.implatform.service.UserService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.GroupMemberVO; +import com.bx.implatform.vo.GroupMessageVO; +import com.bx.implatform.vo.GroupVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP) +public class GroupServiceImpl extends ServiceImpl implements GroupService { + private final UserService userService; + private final GroupMemberService groupMemberService; + private final GroupMessageMapper groupMessageMapper; + private final FriendService friendsService; + private final IMClient imClient; + private final RedisTemplate redisTemplate; + + @Override + public GroupVO createGroup(GroupVO vo) { + UserSession session = SessionContext.getSession(); + User user = userService.getById(session.getUserId()); + // 保存群组数据 + Group group = BeanUtils.copyProperties(vo, Group.class); + group.setOwnerId(user.getId()); + this.save(group); + // 把群主加入群 + GroupMember member = new GroupMember(); + member.setGroupId(group.getId()); + member.setUserId(user.getId()); + member.setHeadImage(user.getHeadImageThumb()); + member.setUserNickName(user.getNickName()); + member.setRemarkNickName(vo.getRemarkNickName()); + member.setRemarkGroupName(vo.getRemarkGroupName()); + groupMemberService.save(member); + GroupVO groupVo = findById(group.getId()); + // 推送同步消息给自己的其他终端 + sendAddGroupMessage(groupVo, Lists.newArrayList(), true); + // 返回 + log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); + return groupVo; + } + + @CacheEvict(key = "#vo.getId()") + @Transactional(rollbackFor = Exception.class) + @Override + public GroupVO modifyGroup(GroupVO vo) { + UserSession session = SessionContext.getSession(); + // 校验是不是群主,只有群主能改信息 + Group group = this.getAndCheckById(vo.getId()); + // 更新成员信息 + GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId()); + if (Objects.isNull(member) || member.getQuit()) { + throw new GlobalException("您不是群聊的成员"); + } + member.setRemarkNickName(vo.getRemarkNickName()); + member.setRemarkGroupName(vo.getRemarkGroupName()); + groupMemberService.updateById(member); + // 群主有权修改群基本信息 + if (group.getOwnerId().equals(session.getUserId())) { + group = BeanUtils.copyProperties(vo, Group.class); + this.updateById(group); + } + log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); + return convert(group, member); + } + + @Transactional(rollbackFor = Exception.class) + @CacheEvict(key = "#groupId") + @Override + public void deleteGroup(Long groupId) { + UserSession session = SessionContext.getSession(); + Group group = this.getById(groupId); + if (!group.getOwnerId().equals(session.getUserId())) { + throw new GlobalException("只有群主才有权限解除群聊"); + } + // 群聊用户id + List userIds = groupMemberService.findUserIdsByGroupId(groupId); + // 逻辑删除群数据 + group.setDissolve(true); + this.updateById(group); + // 删除成员数据 + groupMemberService.removeByGroupId(groupId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.delete(key); + // 推送解散群聊提示 + String content = String.format("'%s'解散了群聊", session.getNickName()); + this.sendTipMessage(groupId, userIds, content, true); + // 推送同步消息 + this.sendDelGroupMessage(groupId, userIds); + log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); + } + + @Override + public void quitGroup(Long groupId) { + Long userId = SessionContext.getSession().getUserId(); + Group group = this.getById(groupId); + if (group.getOwnerId().equals(userId)) { + throw new GlobalException("您是群主,不可退出群聊"); + } + // 删除群聊成员 + groupMemberService.removeByGroupAndUserId(groupId, userId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.opsForHash().delete(key, userId.toString()); + // 推送退出群聊提示 + this.sendTipMessage(groupId, List.of(userId), "您已退出群聊", false); + // 推送同步消息 + this.sendDelGroupMessage(groupId, List.of(userId)); + log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); + } + + @Override + public void kickGroup(Long groupId, Long userId) { + UserSession session = SessionContext.getSession(); + Group group = this.getAndCheckById(groupId); + if (!group.getOwnerId().equals(session.getUserId())) { + throw new GlobalException("您不是群主,没有权限踢人"); + } + if (userId.equals(session.getUserId())) { + throw new GlobalException("亲,不能移除自己哟"); + } + // 删除群聊成员 + groupMemberService.removeByGroupAndUserId(groupId, userId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.opsForHash().delete(key, userId.toString()); + // 推送踢出群聊提示 + this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊", false); + // 推送同步消息 + this.sendDelGroupMessage(groupId, List.of(userId)); + log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); + } + + @Override + public void removeGroupMembers(GroupMemberRemoveDTO dto) { + UserSession session = SessionContext.getSession(); + Group group = this.getAndCheckById(dto.getGroupId()); + if (!group.getOwnerId().equals(session.getUserId())) { + throw new GlobalException("您没有权限"); + } + if (dto.getUserIds().contains(group.getOwnerId())) { + throw new GlobalException("不允许移除群主"); + } + if (dto.getUserIds().contains(session.getUserId())) { + throw new GlobalException("不允许移除自己"); + } + // 删除群聊成员 + groupMemberService.removeByGroupAndUserIds(dto.getGroupId(), dto.getUserIds()); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, dto.getGroupId()); + dto.getUserIds().forEach(id -> redisTemplate.opsForHash().delete(key, id.toString())); + // 推送踢出群聊提示 + this.sendTipMessage(dto.getGroupId(), dto.getUserIds(), "您已被移出群聊", false); + // 推送同步消息 + this.sendDelGroupMessage(dto.getGroupId(), dto.getUserIds()); + log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), dto.getUserIds()); + } + + @Override + public GroupVO findById(Long groupId) { + UserSession session = SessionContext.getSession(); + Group group = super.getById(groupId); + if (Objects.isNull(group)) { + throw new GlobalException("群组不存在"); + } + GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId()); + if (Objects.isNull(member)) { + throw new GlobalException("您未加入群聊"); + } + return convert(group, member); + } + + @Cacheable(key = "#groupId") + @Override + public Group getAndCheckById(Long groupId) { + Group group = super.getById(groupId); + if (Objects.isNull(group)) { + throw new GlobalException("群组不存在"); + } + if (group.getDissolve()) { + throw new GlobalException("群组'" + group.getName() + "'已解散"); + } + if (group.getIsBanned()) { + throw new GlobalException("群组'" + group.getName() + "'已被封禁,原因:" + group.getReason()); + } + return group; + } + + @Override + public List findGroups() { + UserSession session = SessionContext.getSession(); + // 查询当前用户的群id列表 + List groupMembers = groupMemberService.findByUserId(session.getUserId()); + // 一个月内退的群可能存在退群前的离线消息,一并返回作为前端缓存 + groupMembers.addAll(groupMemberService.findQuitInMonth(session.getUserId())); + if (groupMembers.isEmpty()) { + return new LinkedList<>(); + } + // 拉取群列表 + List ids = groupMembers.stream().map((GroupMember::getGroupId)).collect(Collectors.toList()); + LambdaQueryWrapper groupWrapper = Wrappers.lambdaQuery(); + groupWrapper.in(Group::getId, ids); + List groups = this.list(groupWrapper); + // 转vo + return groups.stream().map(group -> { + GroupMember member = + groupMembers.stream().filter(m -> group.getId().equals(m.getGroupId())).findFirst().get(); + return convert(group, member); + }).collect(Collectors.toList()); + } + + @RedisLock(prefixKey = RedisKey.IM_LOCK_GROUP_ENTER,key = "#dto.getGroupId()") + @Override + public void invite(GroupInviteDTO dto) { + UserSession session = SessionContext.getSession(); + Group group = this.getAndCheckById(dto.getGroupId()); + GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId()); + if (Objects.isNull(group) || member.getQuit()) { + throw new GlobalException("您不在群聊中,邀请失败"); + } + // 群聊人数校验 + List members = groupMemberService.findByGroupId(dto.getGroupId()); + long size = members.stream().filter(m -> !m.getQuit()).count(); + if (dto.getFriendIds().size() + size > Constant.MAX_LARGE_GROUP_MEMBER) { + throw new GlobalException("群聊人数不能大于" + Constant.MAX_LARGE_GROUP_MEMBER + "人"); + } + // 找出好友信息 + List friends = friendsService.findByFriendIds(dto.getFriendIds()); + if (dto.getFriendIds().size() != friends.size()) { + throw new GlobalException("部分用户不是您的好友,邀请失败"); + } + // 批量保存成员数据 + List groupMembers = friends.stream().map(f -> { + Optional optional = + members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst(); + GroupMember groupMember = optional.orElseGet(GroupMember::new); + groupMember.setGroupId(dto.getGroupId()); + groupMember.setUserId(f.getFriendId()); + groupMember.setUserNickName(f.getFriendNickName()); + groupMember.setHeadImage(f.getFriendHeadImage()); + groupMember.setCreatedTime(new Date()); + groupMember.setQuit(false); + return groupMember; + }).collect(Collectors.toList()); + if (!groupMembers.isEmpty()) { + groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers); + } + // 推送同步消息给被邀请人 + for (GroupMember m : groupMembers) { + GroupVO groupVo = convert(group, m); + sendAddGroupMessage(groupVo, List.of(m.getUserId()), false); + } + // 推送进入群聊消息 + List userIds = groupMemberService.findUserIdsByGroupId(dto.getGroupId()); + String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(",")); + String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames); + this.sendTipMessage(dto.getGroupId(), userIds, content, true); + log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), + dto.getFriendIds()); + } + + @Override + public List findGroupMembers(Long groupId) { + Group group = getAndCheckById(groupId); + List members = groupMemberService.findByGroupId(groupId); + List userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList()); + List onlineUserIds = imClient.getOnlineUser(userIds); + return members.stream().map(m -> { + GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class); + vo.setShowNickName(m.getShowNickName()); + vo.setShowGroupName(StrUtil.blankToDefault(m.getRemarkGroupName(), group.getName())); + vo.setOnline(onlineUserIds.contains(m.getUserId())); + return vo; + }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList()); + } + + @Override + public void setDnd(GroupDndDTO dto) { + UserSession session = SessionContext.getSession(); + groupMemberService.setDnd(dto.getGroupId(), session.getUserId(), dto.getIsDnd()); + // 推送同步消息 + sendSyncDndMessage(dto.getGroupId(), dto.getIsDnd()); + } + + + private void sendTipMessage(Long groupId, List recvIds, String content, Boolean sendToAll) { + UserSession session = SessionContext.getSession(); + // 消息入库 + GroupMessage message = new GroupMessage(); + message.setContent(content); + message.setType(MessageType.TIP_TEXT.code()); + message.setStatus(MessageStatus.PENDING.code()); + message.setSendTime(new Date()); + message.setSendNickName(session.getNickName()); + message.setGroupId(groupId); + message.setSendId(session.getUserId()); + message.setRecvIds(sendToAll ? "" : CommaTextUtils.asText(recvIds)); + groupMessageMapper.insert(message); + // 推送 + GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + if (CollUtil.isEmpty(recvIds)) { + // 为空表示向全体发送 + List userIds = groupMemberService.findUserIdsByGroupId(groupId); + sendMessage.setRecvIds(userIds); + } else { + sendMessage.setRecvIds(recvIds); + } + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(false); + imClient.sendGroupMessage(sendMessage); + } + + private GroupVO convert(Group group, GroupMember member) { + GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class); + vo.setRemarkGroupName(member.getRemarkGroupName()); + vo.setRemarkNickName(member.getRemarkNickName()); + vo.setShowNickName(member.getShowNickName()); + vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName())); + vo.setQuit(member.getQuit()); + vo.setIsDnd(member.getIsDnd()); + return vo; + } + + private void sendAddGroupMessage(GroupVO group, List recvIds, Boolean sendToSelf) { + UserSession session = SessionContext.getSession(); + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setContent(JSON.toJSONString(group)); + msgInfo.setType(MessageType.GROUP_NEW.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setGroupId(group.getId()); + msgInfo.setSendId(session.getUserId()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(recvIds); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(sendToSelf); + imClient.sendGroupMessage(sendMessage); + } + + private void sendDelGroupMessage(Long groupId, List recvIds) { + UserSession session = SessionContext.getSession(); + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.GROUP_DEL.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setGroupId(groupId); + msgInfo.setSendId(session.getUserId()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(recvIds); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(false); + imClient.sendGroupMessage(sendMessage); + } + + private void sendSyncDndMessage(Long groupId, Boolean isDnd) { + UserSession session = SessionContext.getSession(); + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.GROUP_DND.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setGroupId(groupId); + msgInfo.setSendId(session.getUserId()); + msgInfo.setContent(isDnd.toString()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java new file mode 100644 index 0000000..3401f8a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java @@ -0,0 +1,271 @@ +package com.bx.implatform.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; +import com.bx.imcommon.contant.IMConstant; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMPrivateMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.util.ThreadPoolExecutorFactory; +import com.bx.implatform.dto.PrivateMessageDTO; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.PrivateMessageMapper; +import com.bx.implatform.service.FriendService; +import com.bx.implatform.service.PrivateMessageService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.util.SensitiveFilterUtil; +import com.bx.implatform.vo.PrivateMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PrivateMessageServiceImpl extends ServiceImpl + implements PrivateMessageService { + + private final FriendService friendService; + private final IMClient imClient; + private final SensitiveFilterUtil sensitiveFilterUtil; + private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor(); + + @Override + public PrivateMessageVO sendMessage(PrivateMessageDTO dto) { + UserSession session = SessionContext.getSession(); + Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId()); + if (Boolean.FALSE.equals(isFriends)) { + throw new GlobalException("您已不是对方好友,无法发送消息"); + } + // 保存消息 + PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class); + msg.setSendId(session.getUserId()); + msg.setStatus(MessageStatus.PENDING.code()); + msg.setSendTime(new Date()); + // 过滤内容中的敏感词 + if (MessageType.TEXT.code().equals(dto.getType())) { + msg.setContent(sensitiveFilterUtil.filter(dto.getContent())); + } + this.save(msg); + // 推送消息 + PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(msgInfo.getRecvId()); + sendMessage.setSendToSelf(true); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(true); + imClient.sendPrivateMessage(sendMessage); + log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent()); + return msgInfo; + } + + @Transactional + @Override + public PrivateMessageVO recallMessage(Long id) { + UserSession session = SessionContext.getSession(); + PrivateMessage msg = this.getById(id); + if (Objects.isNull(msg)) { + throw new GlobalException("消息不存在"); + } + if (!msg.getSendId().equals(session.getUserId())) { + throw new GlobalException("这条消息不是由您发送,无法撤回"); + } + if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { + throw new GlobalException("消息已发送超过5分钟,无法撤回"); + } + // 修改消息状态 + msg.setStatus(MessageStatus.RECALL.code()); + this.updateById(msg); + // 生成一条撤回消息 + PrivateMessage recallMsg = new PrivateMessage(); + recallMsg.setSendId(session.getUserId()); + recallMsg.setStatus(MessageStatus.PENDING.code()); + recallMsg.setSendTime(new Date()); + recallMsg.setRecvId(msg.getRecvId()); + recallMsg.setType(MessageType.RECALL.code()); + recallMsg.setContent(id.toString()); + this.save(recallMsg); + // 推送消息 + PrivateMessageVO msgInfo = BeanUtils.copyProperties(recallMsg, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(msgInfo.getRecvId()); + sendMessage.setData(msgInfo); + imClient.sendPrivateMessage(sendMessage); + log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent()); + return msgInfo; + } + + @Override + public List findHistoryMessage(Long friendId, Long page, Long size) { + page = page > 0 ? page : 1; + size = size > 0 ? size : 10; + Long userId = SessionContext.getSession().getUserId(); + long stIdx = (page - 1) * size; + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda().and( + wrap -> wrap.and(wp -> wp.eq(PrivateMessage::getSendId, userId).eq(PrivateMessage::getRecvId, friendId)) + .or(wp -> wp.eq(PrivateMessage::getRecvId, userId).eq(PrivateMessage::getSendId, friendId))) + .ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()).orderByDesc(PrivateMessage::getId) + .last("limit " + stIdx + "," + size); + + List messages = this.list(wrapper); + List messageInfos = + messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)) + .collect(Collectors.toList()); + log.info("拉取聊天记录,用户id:{},好友id:{},数量:{}", userId, friendId, messageInfos.size()); + return messageInfos; + } + + @Override + public void pullOfflineMessage(Long minId) { + UserSession session = SessionContext.getSession(); + // 获取当前用户的消息 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + // 只能拉取最近1个月的消息 + Date minDate = DateUtils.addMonths(new Date(), -1); + wrapper.gt(PrivateMessage::getId, minId); + wrapper.ge(PrivateMessage::getSendTime, minDate); + wrapper.and(wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).or() + .eq(PrivateMessage::getRecvId, session.getUserId())); + wrapper.orderByAsc(PrivateMessage::getId); + List messages = this.list(wrapper); + // 异步推送消息 + EXECUTOR.execute(() -> { + // 开启加载中标志 + this.sendLoadingMessage(true, session); + for (PrivateMessage m : messages) { + // 推送过程如果用户下线了,则不再推送 + if (!imClient.isOnline(session.getUserId(), IMTerminalType.fromCode(session.getTerminal()))) { + log.info("用户已下线,停止推送离线私聊消息,用户id:{}", session.getUserId()); + return; + } + PrivateMessageVO vo = BeanUtils.copyProperties(m, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(m.getSendId(), IMTerminalType.WEB.code())); + sendMessage.setRecvId(session.getUserId()); + sendMessage.setRecvTerminals(List.of(session.getTerminal())); + sendMessage.setSendToSelf(false); + sendMessage.setData(vo); + sendMessage.setSendResult(true); + imClient.sendPrivateMessage(sendMessage); + } + // 关闭加载中标志 + this.sendLoadingMessage(false, session); + log.info("拉取私聊消息,用户id:{},数量:{}", session.getUserId(), messages.size()); + }); + } + + @Override + public List loadOfflineMessage(Long minId) { + UserSession session = SessionContext.getSession(); + // 获取当前用户的消息 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + // 只能拉取最近1个月的消息 + Date minDate = DateUtils.addMonths(new Date(), -1); + wrapper.gt(PrivateMessage::getId, minId); + wrapper.ge(PrivateMessage::getSendTime, minDate); + wrapper.and(wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).or() + .eq(PrivateMessage::getRecvId, session.getUserId())); + wrapper.orderByAsc(PrivateMessage::getId); + List messages = this.list(wrapper); + // 更新消息为送达状态 + List messageIds = messages.stream().filter(m -> m.getRecvId().equals(session.getUserId())) + .filter(m -> m.getStatus().equals(MessageStatus.PENDING.code())).map(PrivateMessage::getId) + .collect(Collectors.toList()); + if (!messageIds.isEmpty()) { + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.in(PrivateMessage::getId, messageIds); + updateWrapper.set(PrivateMessage::getStatus, MessageStatus.DELIVERED.code()); + update(updateWrapper); + } + // 转换vo + List vos = messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)) + .collect(Collectors.toList()); + log.info("拉取私聊消息,用户id:{},数量:{},minId:{}", session.getUserId(), messages.size(), minId); + return vos; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void readedMessage(Long friendId) { + UserSession session = SessionContext.getSession(); + // 推送消息给自己,清空会话列表上的已读数量 + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setType(MessageType.READED.code()); + msgInfo.setSendId(session.getUserId()); + msgInfo.setRecvId(friendId); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setData(msgInfo); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setSendToSelf(true); + sendMessage.setSendResult(false); + imClient.sendPrivateMessage(sendMessage); + // 推送回执消息给对方,更新已读状态 + msgInfo = new PrivateMessageVO(); + msgInfo.setType(MessageType.RECEIPT.code()); + msgInfo.setSendId(session.getUserId()); + msgInfo.setRecvId(friendId); + sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(friendId); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + sendMessage.setData(msgInfo); + imClient.sendPrivateMessage(sendMessage); + // 修改消息状态为已读 + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.eq(PrivateMessage::getSendId, friendId).eq(PrivateMessage::getRecvId, session.getUserId()) + .eq(PrivateMessage::getStatus, MessageStatus.DELIVERED.code()) + .set(PrivateMessage::getStatus, MessageStatus.READED.code()); + this.update(updateWrapper); + log.info("消息已读,接收方id:{},发送方id:{}", session.getUserId(), friendId); + } + + @Override + public Long getMaxReadedId(Long friendId) { + UserSession session = SessionContext.getSession(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(PrivateMessage::getSendId, session.getUserId()).eq(PrivateMessage::getRecvId, friendId) + .eq(PrivateMessage::getStatus, MessageStatus.READED.code()).orderByDesc(PrivateMessage::getId) + .select(PrivateMessage::getId).last("limit 1"); + PrivateMessage message = this.getOne(wrapper); + if (Objects.isNull(message)) { + return -1L; + } + return message.getId(); + } + + private void sendLoadingMessage(Boolean isLoadding, UserSession session) { + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setType(MessageType.LOADING.code()); + msgInfo.setContent(isLoadding.toString()); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(session.getUserId()); + sendMessage.setRecvTerminals(List.of(session.getTerminal())); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendPrivateMessage(sendMessage); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/SensitiveWordServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/SensitiveWordServiceImpl.java new file mode 100644 index 0000000..572e5bd --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/SensitiveWordServiceImpl.java @@ -0,0 +1,30 @@ +package com.bx.implatform.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.implatform.entity.SensitiveWord; +import com.bx.implatform.mapper.SensitiveWordMapper; +import com.bx.implatform.service.SensitiveWordService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SensitiveWordServiceImpl extends ServiceImpl implements + SensitiveWordService { + + @Override + public List findAllEnabledWords() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(SensitiveWord::getEnabled,true); + wrapper.select(SensitiveWord::getContent); + List words = this.list(wrapper); + return words.stream().map(SensitiveWord::getContent).collect(Collectors.toList()); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..938dd07 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java @@ -0,0 +1,225 @@ +package com.bx.implatform.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bx.imclient.IMClient; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.util.JwtUtil; +import com.bx.implatform.config.props.JwtProperties; +import com.bx.implatform.dto.LoginDTO; +import com.bx.implatform.dto.ModifyPwdDTO; +import com.bx.implatform.dto.RegisterDTO; +import com.bx.implatform.entity.Friend; +import com.bx.implatform.entity.GroupMember; +import com.bx.implatform.entity.User; +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.mapper.UserMapper; +import com.bx.implatform.service.FriendService; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.service.UserService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.util.SensitiveFilterUtil; +import com.bx.implatform.vo.LoginVO; +import com.bx.implatform.vo.OnlineTerminalVO; +import com.bx.implatform.vo.UserVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl extends ServiceImpl implements UserService { + + private final PasswordEncoder passwordEncoder; + private final GroupMemberService groupMemberService; + private final FriendService friendService; + private final JwtProperties jwtProperties; + private final IMClient imClient; + private final SensitiveFilterUtil sensitiveFilterUtil; + + @Override + public LoginVO login(LoginDTO dto) { + User user = this.findUserByUserName(dto.getUserName()); + if (Objects.isNull(user)) { + throw new GlobalException("用户不存在"); + } + if (user.getIsBanned()) { + String tip = String.format("您的账号因'%s'已被管理员封禁,请联系客服!",user.getReason()); + throw new GlobalException(tip); + } + if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) { + throw new GlobalException(ResultCode.PASSWOR_ERROR); + } + // 生成token + UserSession session = BeanUtils.copyProperties(user, UserSession.class); + session.setUserId(user.getId()); + session.setTerminal(dto.getTerminal()); + String strJson = JSON.toJSONString(session); + String accessToken = JwtUtil.sign(user.getId(), strJson, jwtProperties.getAccessTokenExpireIn(), + jwtProperties.getAccessTokenSecret()); + String refreshToken = JwtUtil.sign(user.getId(), strJson, jwtProperties.getRefreshTokenExpireIn(), + jwtProperties.getRefreshTokenSecret()); + LoginVO vo = new LoginVO(); + vo.setAccessToken(accessToken); + vo.setAccessTokenExpiresIn(jwtProperties.getAccessTokenExpireIn()); + vo.setRefreshToken(refreshToken); + vo.setRefreshTokenExpiresIn(jwtProperties.getRefreshTokenExpireIn()); + return vo; + } + + @Override + public LoginVO refreshToken(String refreshToken) { + //验证 token + if (!JwtUtil.checkSign(refreshToken, jwtProperties.getRefreshTokenSecret())) { + throw new GlobalException("您的登录信息已过期,请重新登录"); + } + String strJson = JwtUtil.getInfo(refreshToken); + Long userId = JwtUtil.getUserId(refreshToken); + User user = this.getById(userId); + if (Objects.isNull(user)) { + throw new GlobalException("用户不存在"); + } + if (user.getIsBanned()) { + String tip = String.format("您的账号因'%s'被管理员封禁,请联系客服!",user.getReason()); + throw new GlobalException(tip); + } + String accessToken = + JwtUtil.sign(userId, strJson, jwtProperties.getAccessTokenExpireIn(), jwtProperties.getAccessTokenSecret()); + String newRefreshToken = JwtUtil.sign(userId, strJson, jwtProperties.getRefreshTokenExpireIn(), + jwtProperties.getRefreshTokenSecret()); + LoginVO vo = new LoginVO(); + vo.setAccessToken(accessToken); + vo.setAccessTokenExpiresIn(jwtProperties.getAccessTokenExpireIn()); + vo.setRefreshToken(newRefreshToken); + vo.setRefreshTokenExpiresIn(jwtProperties.getRefreshTokenExpireIn()); + return vo; + } + + @Override + public void register(RegisterDTO dto) { + // 昵称默认跟用户名保持一致 + if(StrUtil.isEmpty(dto.getNickName())){ + dto.setUserName(dto.getUserName()); + } + User user = this.findUserByUserName(dto.getUserName()); + if(!dto.getUserName().equals(sensitiveFilterUtil.filter(dto.getUserName()))){ + throw new GlobalException("用户名包含敏感字符"); + } + if(!dto.getNickName().equals(sensitiveFilterUtil.filter(dto.getNickName()))){ + throw new GlobalException("昵称包含敏感字符"); + } + if (!Objects.isNull(user)) { + throw new GlobalException(ResultCode.USERNAME_ALREADY_REGISTER); + } + user = BeanUtils.copyProperties(dto, User.class); + user.setPassword(passwordEncoder.encode(user.getPassword())); + this.save(user); + log.info("注册用户,用户id:{},用户名:{},昵称:{}", user.getId(), dto.getUserName(), dto.getNickName()); + } + + @Override + public void modifyPassword(ModifyPwdDTO dto) { + UserSession session = SessionContext.getSession(); + User user = this.getById(session.getUserId()); + if (!passwordEncoder.matches(dto.getOldPassword(), user.getPassword())) { + throw new GlobalException("旧密码不正确"); + } + user.setPassword(passwordEncoder.encode(dto.getNewPassword())); + this.updateById(user); + log.info("用户修改密码,用户id:{},用户名:{},昵称:{}", user.getId(), user.getUserName(), user.getNickName()); + } + + @Override + public User findUserByUserName(String username) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(User::getUserName, username); + return this.getOne(queryWrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void update(UserVO vo) { + UserSession session = SessionContext.getSession(); + if(!vo.getNickName().equals(sensitiveFilterUtil.filter(vo.getNickName()))){ + throw new GlobalException("昵称包含敏感字符"); + } + if (!session.getUserId().equals(vo.getId())) { + throw new GlobalException("不允许修改其他用户的信息"); + } + User user = this.getById(vo.getId()); + if (Objects.isNull(user)) { + throw new GlobalException("用户不存在"); + } + if (!user.getNickName().equals(vo.getNickName()) || !user.getHeadImageThumb().equals(vo.getHeadImageThumb())) { + // 更新好友昵称和头像 + LambdaUpdateWrapper wrapper1 = Wrappers.lambdaUpdate(); + wrapper1.eq(Friend::getFriendId, session.getUserId()); + wrapper1.set(Friend::getFriendNickName,vo.getNickName()); + wrapper1.set(Friend::getFriendHeadImage,vo.getHeadImageThumb()); + friendService.update(wrapper1); + // 更新群聊中的昵称和头像 + LambdaUpdateWrapper wrapper2 = Wrappers.lambdaUpdate(); + wrapper2.eq(GroupMember::getUserId, session.getUserId()); + wrapper2.set(GroupMember::getHeadImage,vo.getHeadImageThumb()); + wrapper2.set(GroupMember::getUserNickName,vo.getNickName()); + groupMemberService.update(wrapper2); + } + // 更新用户信息 + user.setNickName(vo.getNickName()); + user.setSex(vo.getSex()); + user.setSignature(vo.getSignature()); + user.setHeadImage(vo.getHeadImage()); + user.setHeadImageThumb(vo.getHeadImageThumb()); + this.updateById(user); + log.info("用户信息更新,用户:{}}", user); + } + + @Override + public UserVO findUserById(Long id) { + User user = this.getById(id); + UserVO vo = BeanUtils.copyProperties(user, UserVO.class); + vo.setOnline(imClient.isOnline(id)); + return vo; + } + + @Override + public List findUserByName(String name) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.like(User::getUserName, name).or().like(User::getNickName, name).last("limit 20"); + List users = this.list(queryWrapper); + List userIds = users.stream().map(User::getId).collect(Collectors.toList()); + List onlineUserIds = imClient.getOnlineUser(userIds); + return users.stream().map(u -> { + UserVO vo = BeanUtils.copyProperties(u, UserVO.class); + vo.setOnline(onlineUserIds.contains(u.getId())); + return vo; + }).collect(Collectors.toList()); + } + + @Override + public List getOnlineTerminals(String userIds) { + List userIdList = Arrays.stream(userIds.split(",")).map(Long::parseLong).collect(Collectors.toList()); + // 查询在线的终端 + Map> terminalMap = imClient.getOnlineTerminal(userIdList); + // 组装vo + List vos = new LinkedList<>(); + terminalMap.forEach((userId, types) -> { + List terminals = types.stream().map(IMTerminalType::code).collect(Collectors.toList()); + vos.add(new OnlineTerminalVO(userId, terminals)); + }); + return vos; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java new file mode 100644 index 0000000..374c740 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java @@ -0,0 +1,337 @@ +package com.bx.implatform.service.impl; + +import com.bx.imclient.IMClient; +import com.bx.imcommon.model.IMPrivateMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.implatform.annotation.OnlineCheck; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.entity.PrivateMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.enums.WebrtcMode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.service.PrivateMessageService; +import com.bx.implatform.service.WebrtcPrivateService; +import com.bx.implatform.session.SessionContext; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.session.WebrtcPrivateSession; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.util.UserStateUtils; +import com.bx.implatform.vo.PrivateMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.Collections; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WebrtcPrivateServiceImpl implements WebrtcPrivateService { + + private final IMClient imClient; + private final RedisTemplate redisTemplate; + private final PrivateMessageService privateMessageService; + private final UserStateUtils userStateUtils; + + @OnlineCheck + @Override + public void call(Long uid, String mode, String offer) { + UserSession session = SessionContext.getSession(); + log.info("发起呼叫,sid:{},uid:{}", session.getUserId(), uid); + // 创建webrtc会话 + WebrtcPrivateSession webrtcSession = new WebrtcPrivateSession(); + webrtcSession.setCallerId(session.getUserId()); + webrtcSession.setCallerTerminal(session.getTerminal()); + webrtcSession.setAcceptorId(uid); + webrtcSession.setMode(mode); + // 校验 + if (!imClient.isOnline(uid)) { + this.sendActMessage(webrtcSession, MessageStatus.PENDING, "未接通"); + log.info("对方不在线,uid:{}", uid); + throw new GlobalException("对方目前不在线"); + } + if (userStateUtils.isBusy(uid)) { + this.sendActMessage(webrtcSession, MessageStatus.PENDING, "未接通"); + log.info("对方正忙,uid:{}", uid); + throw new GlobalException("对方正忙"); + } + // 保存rtc session + String key = getWebRtcSessionKey(session.getUserId(), uid); + redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS); + // 设置用户忙线状态 + userStateUtils.setBusy(uid); + userStateUtils.setBusy(session.getUserId()); + // 向对方所有终端发起呼叫 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + MessageType messageType = + mode.equals(WebrtcMode.VIDEO.getValue()) ? MessageType.RTC_CALL_VIDEO : MessageType.RTC_CALL_VOICE; + messageInfo.setType(messageType.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + messageInfo.setContent(offer); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + + } + + @Override + public void accept(Long uid, @RequestBody String answer) { + UserSession session = SessionContext.getSession(); + log.info("接受通话,sid:{},uid:{}", session.getUserId(), uid); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 更新接受者信息 + webrtcSession.setAcceptorId(session.getUserId()); + webrtcSession.setAcceptorTerminal(session.getTerminal()); + webrtcSession.setChatTimeStamp(System.currentTimeMillis()); + String key = getWebRtcSessionKey(session.getUserId(), uid); + redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS); + // 向发起人推送接受通话信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_ACCEPT.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + messageInfo.setContent(answer); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + // 告知其他终端已经接受会话,中止呼叫 + sendMessage.setSendToSelf(true); + sendMessage.setSendResult(false); + sendMessage.setRecvTerminals((Collections.singletonList(webrtcSession.getCallerTerminal()))); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + } + + @Override + public void reject(Long uid) { + UserSession session = SessionContext.getSession(); + log.info("拒绝通话,sid:{},uid:{}", session.getUserId(), uid); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 删除会话信息 + removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); + // 向发起人推送拒绝通话信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_REJECT.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + // 告知其他终端已经拒绝会话,中止呼叫 + sendMessage.setSendToSelf(true); + sendMessage.setSendResult(false); + sendMessage.setRecvTerminals(Collections.singletonList(webrtcSession.getCallerTerminal())); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + // 生成通话消息 + sendActMessage(webrtcSession, MessageStatus.READED, "已拒绝"); + } + + @Override + public void cancel(Long uid) { + UserSession session = SessionContext.getSession(); + log.info("取消通话,sid:{},uid:{}", session.getUserId(), uid); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 删除会话信息 + removeWebrtcSession(session.getUserId(), uid); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); + // 向对方所有终端推送取消通话信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_CANCEL.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + sendMessage.setData(messageInfo); + // 通知对方取消会话 + imClient.sendPrivateMessage(sendMessage); + // 生成通话消息 + sendActMessage(webrtcSession, MessageStatus.PENDING, "已取消"); + } + + @Override + public void failed(Long uid, String reason) { + UserSession session = SessionContext.getSession(); + log.info("通话失败,sid:{},uid:{},reason:{}", session.getUserId(), uid, reason); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 删除会话信息 + removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); + // 向发起方推送通话失败信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_FAILED.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + messageInfo.setContent(reason); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + sendMessage.setRecvTerminals(Collections.singletonList(webrtcSession.getCallerTerminal())); + sendMessage.setData(messageInfo); + // 通知对方取消会话 + imClient.sendPrivateMessage(sendMessage); + // 生成消息 + sendActMessage(webrtcSession, MessageStatus.READED, "未接通"); + } + + @Override + public void handup(Long uid) { + UserSession session = SessionContext.getSession(); + log.info("挂断通话,sid:{},uid:{}", session.getUserId(), uid); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 删除会话信息 + removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); + // 向对方推送挂断通话信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_HANDUP.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + Integer terminal = getTerminalType(uid, webrtcSession); + sendMessage.setRecvTerminals(Collections.singletonList(terminal)); + sendMessage.setData(messageInfo); + // 通知对方取消会话 + imClient.sendPrivateMessage(sendMessage); + // 生成通话消息 + sendActMessage(webrtcSession, MessageStatus.READED, "通话时长 " + chatTimeText(webrtcSession)); + } + + @Override + public void candidate(Long uid, String candidate) { + UserSession session = SessionContext.getSession(); + // 查询webrtc会话 + WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); + // 向发起方推送同步candidate信令 + PrivateMessageVO messageInfo = new PrivateMessageVO(); + messageInfo.setType(MessageType.RTC_CANDIDATE.code()); + messageInfo.setRecvId(uid); + messageInfo.setSendId(session.getUserId()); + messageInfo.setContent(candidate); + + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(uid); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + Integer terminal = getTerminalType(uid, webrtcSession); + sendMessage.setRecvTerminals(Collections.singletonList(terminal)); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + } + + @Override + public void heartbeat(Long uid) { + UserSession session = SessionContext.getSession(); + // 会话续命 + String key = getWebRtcSessionKey(session.getUserId(), uid); + redisTemplate.expire(key, 60, TimeUnit.SECONDS); + // 用户状态续命 + userStateUtils.expire(session.getUserId()); + } + + private WebrtcPrivateSession getWebrtcSession(Long userId, Long uid) { + String key = getWebRtcSessionKey(userId, uid); + WebrtcPrivateSession webrtcSession = (WebrtcPrivateSession)redisTemplate.opsForValue().get(key); + if (webrtcSession == null) { + throw new GlobalException("通话已结束"); + } + return webrtcSession; + } + + private void removeWebrtcSession(Long userId, Long uid) { + String key = getWebRtcSessionKey(userId, uid); + redisTemplate.delete(key); + } + + private String getWebRtcSessionKey(Long id1, Long id2) { + Long minId = id1 > id2 ? id2 : id1; + Long maxId = id1 > id2 ? id1 : id2; + return String.join(":", RedisKey.IM_WEBRTC_PRIVATE_SESSION, minId.toString(), maxId.toString()); + } + + private Integer getTerminalType(Long uid, WebrtcPrivateSession webrtcSession) { + if (uid.equals(webrtcSession.getCallerId())) { + return webrtcSession.getCallerTerminal(); + } + return webrtcSession.getAcceptorTerminal(); + } + + private void sendActMessage(WebrtcPrivateSession rtcSession, MessageStatus status, String content) { + // 保存消息 + PrivateMessage msg = new PrivateMessage(); + msg.setSendId(rtcSession.getCallerId()); + msg.setRecvId(rtcSession.getAcceptorId()); + msg.setContent(content); + msg.setSendTime(new Date()); + msg.setStatus(status.code()); + MessageType type = rtcSession.getMode().equals(WebrtcMode.VIDEO.getValue()) ? MessageType.ACT_RT_VIDEO + : MessageType.ACT_RT_VOICE; + msg.setType(type.code()); + privateMessageService.save(msg); + // 推给发起人 + PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(rtcSession.getCallerId(), rtcSession.getCallerTerminal())); + sendMessage.setRecvId(rtcSession.getCallerId()); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(true); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + // 推给接听方 + sendMessage.setRecvId(rtcSession.getAcceptorId()); + imClient.sendPrivateMessage(sendMessage); + } + + private String chatTimeText(WebrtcPrivateSession rtcSession) { + long chatTime = (System.currentTimeMillis() - rtcSession.getChatTimeStamp()) / 1000; + int min = Math.abs((int)chatTime / 60); + int sec = Math.abs((int)chatTime % 60); + String strTime = min < 10 ? "0" : ""; + strTime += min; + strTime += ":"; + strTime += sec < 10 ? "0" : ""; + strTime += sec; + return strTime; + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java b/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java new file mode 100644 index 0000000..ce61dbb --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java @@ -0,0 +1,21 @@ +package com.bx.implatform.session; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/* + * @Description + * @Author Blue + * @Date 2022/10/21 + */ +public class SessionContext { + + public static UserSession getSession() { + // 从请求上下文里获取Request对象 + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes.getRequest(); + return (UserSession) request.getAttribute("session"); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/session/UserSession.java b/im-platform/src/main/java/com/bx/implatform/session/UserSession.java new file mode 100644 index 0000000..8b8815f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/session/UserSession.java @@ -0,0 +1,20 @@ +package com.bx.implatform.session; + +import com.bx.imcommon.model.IMSessionInfo; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class UserSession extends IMSessionInfo { + + /** + * 用户名称 + */ + private String userName; + + /** + * 用户昵称 + */ + private String nickName; +} diff --git a/im-platform/src/main/java/com/bx/implatform/session/WebrtcGroupSession.java b/im-platform/src/main/java/com/bx/implatform/session/WebrtcGroupSession.java new file mode 100644 index 0000000..098c724 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/session/WebrtcGroupSession.java @@ -0,0 +1,33 @@ +package com.bx.implatform.session; + +import com.bx.imcommon.model.IMUserInfo; +import lombok.Data; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author: Blue + * @date: 2024-06-01 + * @version: 1.0 + */ +@Data +public class WebrtcGroupSession { + + /** + * 通话发起者 + */ + private IMUserInfo host; + + /** + * 所有被邀请的用户列表 + */ + private List userInfos; + + /** + * 已经进入通话的用户列表 + */ + private List inChatUsers = new LinkedList<>(); + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java b/im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java new file mode 100644 index 0000000..3d74cd1 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/session/WebrtcPrivateSession.java @@ -0,0 +1,39 @@ +package com.bx.implatform.session; + +import lombok.Data; + +/* + * webrtc 会话信息 + * @Author Blue + * @Date 2022/10/21 + */ +@Data +public class WebrtcPrivateSession { + /** + * 发起者id + */ + private Long callerId; + /** + * 发起者终端类型 + */ + private Integer callerTerminal; + + /** + * 接受者id + */ + private Long acceptorId; + + /** + * 接受者终端类型 + */ + private Integer acceptorTerminal; + + /** + * 通话模式 + */ + private String mode; + /** + * 开始聊天时间戳 + */ + private Long chatTimeStamp; +} diff --git a/im-platform/src/main/java/com/bx/implatform/session/WebrtcUserInfo.java b/im-platform/src/main/java/com/bx/implatform/session/WebrtcUserInfo.java new file mode 100644 index 0000000..03c6b2d --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/session/WebrtcUserInfo.java @@ -0,0 +1,28 @@ +package com.bx.implatform.session; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author: Blue + * @date: 2024-06-02 + * @version: 1.0 + */ +@Data +@Schema(description = "用户信息") +public class WebrtcUserInfo { + @Schema(description = "用户id") + private Long id; + + @Schema(description = "用户昵称") + private String nickName; + + @Schema(description = "用户头像") + private String headImage; + + @Schema(description = "是否开启摄像头") + private Boolean isCamera; + + @Schema(description = "是否开启麦克风") + private Boolean isMicroPhone; +} diff --git a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java new file mode 100644 index 0000000..44712d9 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java @@ -0,0 +1,68 @@ +package com.bx.implatform.task.consumer; + +import com.bx.imclient.IMClient; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMGroupMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.mq.RedisMQConsumer; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.implatform.contant.Constant; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.GroupBanDTO; +import com.bx.implatform.entity.GroupMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.service.GroupMessageService; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.GroupMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +/** + * @author: Blue + * @date: 2024-07-15 + * @version: 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = RedisKey.IM_QUEUE_GROUP_BANNED) +public class GroupBannedConsumerTask extends RedisMQConsumer { + + private final IMClient imClient; + + private final GroupMessageService groupMessageService; + + private final GroupMemberService groupMemberService; + + @Override + public void onMessage(GroupBanDTO dto) { + log.info("群聊被封禁处理,群id:{},原因:{}", dto.getId(), dto.getReason()); + // 群聊成员列表 + List userIds = groupMemberService.findUserIdsByGroupId(dto.getId()); + // 保存消息 + GroupMessage msg = new GroupMessage(); + msg.setGroupId(dto.getId()); + String tip = "本群聊已被管理员封禁,原因:" + dto.getReason(); + msg.setContent(tip); + msg.setSendId(Constant.SYS_USER_ID); + msg.setSendTime(new Date()); + msg.setStatus(MessageStatus.PENDING.code()); + msg.setSendNickName("系统管理员"); + msg.setType(MessageType.TIP_TEXT.code()); + groupMessageService.save(msg); + // 推送提示语到群聊中 + GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(Constant.SYS_USER_ID, IMTerminalType.PC.code())); + sendMessage.setRecvIds(userIds); + sendMessage.setSendToSelf(false); + sendMessage.setData(msgInfo); + imClient.sendGroupMessage(sendMessage); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java new file mode 100644 index 0000000..ec7c2da --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java @@ -0,0 +1,67 @@ +package com.bx.implatform.task.consumer; + +import com.bx.imclient.IMClient; +import com.bx.imcommon.enums.IMTerminalType; +import com.bx.imcommon.model.IMGroupMessage; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.mq.RedisMQConsumer; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.implatform.contant.Constant; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.GroupUnbanDTO; +import com.bx.implatform.entity.GroupMessage; +import com.bx.implatform.enums.MessageStatus; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.service.GroupMemberService; +import com.bx.implatform.service.GroupMessageService; +import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.vo.GroupMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +/** + * @author: Blue + * @date: 2024-07-15 + * @version: 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = RedisKey.IM_QUEUE_GROUP_UNBAN) +public class GroupUnbanConsumerTask extends RedisMQConsumer { + + private final IMClient imClient; + + private final GroupMessageService groupMessageService; + + private final GroupMemberService groupMemberService; + + @Override + public void onMessage(GroupUnbanDTO dto) { + log.info("群聊解除封禁处理,群id:{}",dto.getId()); + // 群聊成员列表 + List userIds = groupMemberService.findUserIdsByGroupId(dto.getId()); + // 保存消息 + GroupMessage msg = new GroupMessage(); + msg.setGroupId(dto.getId()); + msg.setContent("已解除封禁"); + msg.setSendId(Constant.SYS_USER_ID); + msg.setSendTime(new Date()); + msg.setStatus(MessageStatus.PENDING.code()); + msg.setSendNickName("系统管理员"); + msg.setType(MessageType.TIP_TEXT.code()); + groupMessageService.save(msg); + // 推送提示语到群聊中 + GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(Constant.SYS_USER_ID, IMTerminalType.PC.code())); + sendMessage.setRecvIds(userIds); + sendMessage.setSendToSelf(false); + sendMessage.setData(msgInfo); + imClient.sendGroupMessage(sendMessage); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/task/consumer/UserBannedConsumerTask.java b/im-platform/src/main/java/com/bx/implatform/task/consumer/UserBannedConsumerTask.java new file mode 100644 index 0000000..c84b986 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/task/consumer/UserBannedConsumerTask.java @@ -0,0 +1,42 @@ +package com.bx.implatform.task.consumer; + +import com.bx.imclient.IMClient; +import com.bx.imcommon.model.IMSystemMessage; +import com.bx.imcommon.mq.RedisMQConsumer; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.dto.UserBanDTO; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.vo.SystemMessageVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Collections; + +/** + * @author: Blue + * @date: 2024-07-15 + * @version: 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = RedisKey.IM_QUEUE_USER_BANNED) +public class UserBannedConsumerTask extends RedisMQConsumer { + + private final IMClient imClient; + @Override + public void onMessage(UserBanDTO dto) { + log.info("用户被封禁处理,userId:{},原因:{}",dto.getId(),dto.getReason()); + // 推送消息将用户赶下线 + SystemMessageVO msgInfo = new SystemMessageVO(); + msgInfo.setType(MessageType.USER_BANNED.code()); + msgInfo.setContent(dto.getReason()); + IMSystemMessage sendMessage = new IMSystemMessage<>(); + sendMessage.setRecvIds(Collections.singletonList(dto.getId())); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(true); + imClient.sendSystemMessage(sendMessage); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java b/im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java new file mode 100644 index 0000000..babcca4 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/task/schedule/FileExpireTask.java @@ -0,0 +1,78 @@ +package com.bx.implatform.task.schedule; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bx.implatform.annotation.RedisLock; +import com.bx.implatform.config.props.MinioProperties; +import com.bx.implatform.contant.RedisKey; +import com.bx.implatform.entity.FileInfo; +import com.bx.implatform.service.FileService; +import com.bx.implatform.thirdparty.MinioService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +/** + * 过期文件清理任务 + * + * @author Blue + * @version 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class FileExpireTask { + + private final FileService fileService; + private final MinioService minioService; + private final MinioProperties minioProps; + + @RedisLock(prefixKey = RedisKey.IM_LOCK_FILE_TASK) + @Scheduled(cron = "0 0 3 * * ?") + public void run() { + log.info("【定时任务】过期文件处理..."); + int batchSize = 100; + List files = loadBatch(batchSize); + while (true) { + for (FileInfo fileInfo : files) { + String url = fileInfo.getFilePath(); + String relativePath = url.substring(fileInfo.getFilePath().indexOf(minioProps.getBucketName())); + String[] arr = relativePath.split("/"); + String bucket = minioProps.getBucketName(); + String path = arr[1]; + String fileNme = StrUtil.join("/", arr[2], arr[3]); + if (minioService.isExist(bucket, path, fileNme)) { + if (!minioService.remove(bucket, path, fileNme)) { + // 删除失败,不再往下执行 + log.error("删除过期文件异常, id:{},文件名:{}", fileInfo.getId(), fileInfo.getFileName()); + return; + } + // 删除文件信息 + fileService.removeById(fileInfo.getId()); + } + } + if (files.size() < batchSize) { + break; + } + // 下一批 + files = loadBatch(batchSize); + } + } + + List loadBatch(int size) { + Date minDate = DateUtils.addDays(new Date(), -minioProps.getExpireIn()); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(FileInfo::getIsPermanent, false); + wrapper.le(FileInfo::getUploadTime, minDate); + wrapper.orderByAsc(FileInfo::getId); + wrapper.last("limit " + size); + return fileService.list(wrapper); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/task/schedule/ReloadSensitiveWordTask.java b/im-platform/src/main/java/com/bx/implatform/task/schedule/ReloadSensitiveWordTask.java new file mode 100644 index 0000000..2ff15ac --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/task/schedule/ReloadSensitiveWordTask.java @@ -0,0 +1,26 @@ +package com.bx.implatform.task.schedule; + +import com.bx.implatform.util.SensitiveFilterUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * @author: Blue + * @date: 2024-09-01 + * @version: 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ReloadSensitiveWordTask { + + private final SensitiveFilterUtil sensitiveFilterUtil; + + @Scheduled(fixedRate = 60000) + public void run() { + log.info("【定时任务】重新装载敏感词..."); + sensitiveFilterUtil.reload(); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java b/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java new file mode 100644 index 0000000..d5ad8da --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java @@ -0,0 +1,156 @@ +package com.bx.implatform.thirdparty; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.RandomUtil; +import com.bx.implatform.util.DateTimeUtils; +import com.bx.implatform.util.FileUtil; +import io.minio.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Date; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MinioService { + + private final MinioClient minioClient; + + /** + * 查看存储bucket是否存在 + * + * @return boolean + */ + public Boolean bucketExists(String bucketName) { + try { + return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + } catch (Exception e) { + log.error("查询bucket失败", e); + return false; + } + } + + /** + * 创建存储bucket + */ + public void makeBucket(String bucketName) { + try { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + } catch (Exception e) { + log.error("创建bucket失败,", e); + } + } + + /** + * 设置bucket权限为public + */ + public void setBucketPublic(String bucketName) { + try { + // 设置公开 + String sb = + "{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}"; + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(sb).build()); + } catch (Exception e) { + log.error("创建bucket失败,", e); + } + } + + /** + * 文件上传 + * + * @param bucketName bucket名称 + * @param path 路径 + * @param file 文件 + * @return Boolean + */ + public String upload(String bucketName, String path, MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (StringUtils.isBlank(originalFilename)) { + throw new RuntimeException(); + } + // 文件名使用雪花算法生成,防止重名以及出现一些特殊字符 + String fileName = IdUtil.getSnowflakeNextIdStr() + "." + FileUtil.getFileExtension(originalFilename); + String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; + try { + InputStream stream = new ByteArrayInputStream(file.getBytes()); + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) + .stream(stream, file.getSize(), -1).contentType(file.getContentType()).build(); + //文件名称相同会覆盖 + minioClient.putObject(objectArgs); + } catch (Exception e) { + log.error("上传图片失败,", e); + return null; + } + return objectName; + } + + /** + * 文件上传 + * + * @param bucketName bucket名称 + * @param path 路径 + * @param name 文件名 + * @param fileByte 文件内容 + * @param contentType contentType + * @return objectName + */ + public String upload(String bucketName, String path, String name, byte[] fileByte, String contentType) { + // 文件名使用雪花算法生成,防止重名以及出现一些特殊字符 + String fileName = IdUtil.getSnowflakeNextIdStr() + "." + FileUtil.getFileExtension(name); + String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; + try { + InputStream stream = new ByteArrayInputStream(fileByte); + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) + .stream(stream, fileByte.length, -1).contentType(contentType).build(); + //文件名称相同会覆盖 + minioClient.putObject(objectArgs); + } catch (Exception e) { + log.error("上传文件失败,", e); + return null; + } + return objectName; + } + + /** + * 删除 + * + * @param bucketName bucket名称 + * @param path 路径 + * @param fileName 文件名 + * @return true/false + */ + public boolean remove(String bucketName, String path, String fileName) { + try { + minioClient.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); + } catch (Exception e) { + log.error("删除文件失败,", e); + return false; + } + return true; + } + + /** + * 判断文件是否存在 + * + * @param bucketName bucket名称 + * @param path 路径 + * @param fileName 文件名 + * @return + */ + public Boolean isExist(String bucketName, String path, String fileName) { + try { + minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); + } catch (Exception e) { + return false; + } + return true; + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java b/im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java new file mode 100644 index 0000000..f532292 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java @@ -0,0 +1,45 @@ +package com.bx.implatform.util; + +import org.springframework.util.ReflectionUtils; + +public final class BeanUtils { + + private BeanUtils() { + } + + private static void handleReflectionException(Exception e) { + ReflectionUtils.handleReflectionException(e); + } + + + /** + * 属性拷贝 + * + * @param orig 源对象 + * @param destClass 目标 + * @return T + */ + public static T copyProperties(Object orig, Class destClass) { + try { + T target = destClass.newInstance(); + if (orig == null) { + return null; + } + copyProperties(orig, target); + return target; + } catch (Exception e) { + handleReflectionException(e); + return null; + } + } + + + public static void copyProperties(Object orig, Object dest) { + try { + org.springframework.beans.BeanUtils.copyProperties(orig, dest); + } catch (Exception e) { + handleReflectionException(e); + } + } + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java b/im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java new file mode 100644 index 0000000..455ea23 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java @@ -0,0 +1,35 @@ +package com.bx.implatform.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 日期处理工具类 + * + * @version 1.0 + */ +public final class DateTimeUtils extends DateUtils { + + public static final String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final String PARTDATEFORMAT = "yyyyMMdd"; + + + /** + * 将日期类型转换为字符串 + * + * @param date 日期 + * @param xFormat 格式 + * @return 日期 + */ + public static String getFormatDate(Date date, String xFormat) { + date = date == null ? new Date() : date; + xFormat = StringUtils.isNotEmpty(xFormat) ? xFormat : FULL_DATE_FORMAT; + SimpleDateFormat sdf = new SimpleDateFormat(xFormat); + return sdf.format(date); + } + + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java b/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java new file mode 100644 index 0000000..89aeddd --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java @@ -0,0 +1,43 @@ +package com.bx.implatform.util; + +public final class FileUtil { + + + /** + * 获取文件后缀 + * + * @param fileName 文件名 + * @return boolean + */ + public static String getFileExtension(String fileName) { + return fileName.substring(fileName.lastIndexOf(".") + 1); + } + + /** + * 去除文件扩展名 + * + * @param fileName 文件名 + * @return + */ + public static String excludeExtension(String fileName) { + return fileName.substring(0,fileName.lastIndexOf(".")); + } + + /** + * 判断文件是否图片类型 + * + * @param fileName 文件名 + * @return boolean + */ + public static boolean isImage(String fileName) { + String extension = getFileExtension(fileName); + String[] imageExtension = new String[]{"jpeg", "jpg", "bmp", "png", "webp", "gif"}; + for (String e : imageExtension) { + if (extension.toLowerCase().equals(e)) { + return true; + } + } + + return false; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/util/ImageUtil.java b/im-platform/src/main/java/com/bx/implatform/util/ImageUtil.java new file mode 100644 index 0000000..5c08170 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/ImageUtil.java @@ -0,0 +1,78 @@ +package com.bx.implatform.util; + +import lombok.extern.slf4j.Slf4j; +import net.coobird.thumbnailator.Thumbnails; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +@Slf4j +public final class ImageUtil { + + //以下是常量,按照阿里代码开发规范,不允许代码中出现魔法值 + private static final Integer ZERO = 0; + private static final Integer ONE_ZERO_TWO_FOUR = 1024; + private static final Integer NINE_ZERO_ZERO = 900; + private static final Integer THREE_TWO_SEVEN_FIVE = 3275; + private static final Integer TWO_ZERO_FOUR_SEVEN = 2047; + private static final Double ZERO_EIGHT_FIVE = 0.85; + private static final Double ZERO_SIX = 0.6; + private static final Double ZERO_FOUR_FOUR = 0.44; + private static final Double ZERO_FOUR = 0.4; + + /** + * 根据指定大小压缩图片 + * + * @param imageBytes 源图片字节数组 + * @param desFileSize 指定图片大小,单位kb + * @return 压缩质量后的图片字节数组 + */ + public static byte[] compressForScale(byte[] imageBytes, long desFileSize) { + if (imageBytes == null || imageBytes.length <= ZERO || imageBytes.length < desFileSize * ONE_ZERO_TWO_FOUR) { + return imageBytes; + } + long srcSize = imageBytes.length; + double accuracy = getAccuracy(srcSize / ONE_ZERO_TWO_FOUR); + try { + while (imageBytes.length > desFileSize * ONE_ZERO_TWO_FOUR) { + ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length); + Thumbnails.of(inputStream) + .scale(accuracy) + .outputQuality(accuracy) + .toOutputStream(outputStream); + imageBytes = outputStream.toByteArray(); + } + log.info("图片原大小={}kb | 压缩后大小={}kb", + srcSize / ONE_ZERO_TWO_FOUR, imageBytes.length / ONE_ZERO_TWO_FOUR); + } catch (Exception e) { + log.error("【图片压缩】msg=图片压缩失败!", e); + } + return imageBytes; + } + + + /** + * 自动调节精度(经验数值) + * + * @param size 源图片大小 + * @return 图片压缩质量比 + */ + private static double getAccuracy(long size) { + double accuracy; + if (size < NINE_ZERO_ZERO) { + accuracy = ZERO_EIGHT_FIVE; + } else if (size < TWO_ZERO_FOUR_SEVEN) { + accuracy = ZERO_SIX; + } else if (size < THREE_TWO_SEVEN_FIVE) { + accuracy = ZERO_FOUR_FOUR; + } else { + accuracy = ZERO_FOUR; + } + return accuracy; + } + +} + + + diff --git a/im-platform/src/main/java/com/bx/implatform/util/SensitiveFilterUtil.java b/im-platform/src/main/java/com/bx/implatform/util/SensitiveFilterUtil.java new file mode 100644 index 0000000..c3e83d7 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/SensitiveFilterUtil.java @@ -0,0 +1,207 @@ +package com.bx.implatform.util; + +import cn.hutool.core.util.StrUtil; +import com.bx.imcommon.util.ThreadPoolExecutorFactory; +import com.bx.implatform.service.SensitiveWordService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.CharUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + * 敏感词过滤器——SensitiveFilter + * + * @author Andrews + * @date 2023/12/4 11:12 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public final class SensitiveFilterUtil { + + /** + * 替换符 + */ + private static final String REPLACE_MENT = "***"; + + /** + * 根节点 + */ + private static TrieNode ROOT_NODE = new TrieNode(); + + /** + * 线程池 + */ + private static final ScheduledThreadPoolExecutor EXECUTOR_SERVICE = + ThreadPoolExecutorFactory.getThreadPoolExecutor(); + + private final SensitiveWordService sensitiveWordService; + + /** + * 1、 前缀树 前缀树某一个节点 + * + * @author NXY + * @date 2023/12/4 11:17 + */ + private static class TrieNode { + // 关键词结束标识 + private boolean isKeywordEnd = false; + + // 子节点(key是下级字符,value是下级节点) + // 当前节点的子节点 + private final Map subNodes = new HashMap<>(); + + public boolean isKeywordEnd() { + return isKeywordEnd; + } + + public void setKeywordEnd(boolean keywordEnd) { + isKeywordEnd = keywordEnd; + } + + // 添加子节点 + public void addSubNode(Character c, TrieNode node) { + subNodes.put(c, node); + } + + // 获取子节点 + public TrieNode getSubNode(Character c) { + return subNodes.get(c); + } + + } + + /** + * 2、初始化方法,服务器启动时初始化 + * + * @author NXY + * @date 2023/12/4 11:18 + */ + @PostConstruct + public void reload() { + // 使用copy on write的方式,防止出现并发问题 + TrieNode newNode = new TrieNode(); + List keywords = sensitiveWordService.findAllEnabledWords(); + keywords.forEach(keyword -> { + if (StrUtil.isNotEmpty(keyword)) { + // 添加到前缀树 + addKeyword(newNode,keyword); + } + }); + ROOT_NODE = newNode; + } + + /** + * 3、将一个敏感词添加到前缀树中 + * + * @param node + * @param keyword + * @author NXY + * @date 2023/12/4 11:15 + */ + private void addKeyword(TrieNode node, String keyword) { + for (int i = 0; i < keyword.length(); i++) { + char c = keyword.charAt(i); + TrieNode subNode = node.getSubNode(c); + if (subNode == null) { + // 初始化子节点 + subNode = new TrieNode(); + node.addSubNode(c, subNode); + } + // 指向子节点,进入下一轮循环 + node = subNode; + // 设置结束标识 + if (i == keyword.length() - 1) { + node.setKeywordEnd(true); + } + } + } + + /** + * 过滤敏感词 + * + * @param text 待过滤的文本 + * @return 过滤后的文本 + */ + public String filter(String text) { + if (StringUtils.isBlank(text)) { + return null; + } + // 结果 + StringBuilder sb = new StringBuilder(); + try { + // 指针1 + TrieNode tempNode = ROOT_NODE; + // 指针2 + int begin = 0; + // 指针3 + int position = 0; + while (begin < text.length()) { + if (position < text.length()) { + char c = text.charAt(position); + // 跳过符号 + if (isSymbol(c)) { + // 若指针1处于根节点,将此符号计入结果,让指针2向下走一步 + if (tempNode == ROOT_NODE) { + sb.append(c); + begin++; + } + // 无论符号在开头或中间,指针3都向下走一步 + position++; + continue; + } + // 检查下级节点 + tempNode = tempNode.getSubNode(c); + if (tempNode == null) { + // 以begin开头的字符串不是敏感词 + sb.append(text.charAt(begin)); + // 进入下一个位置 + position = ++begin; + // 重新指向根节点 + tempNode = ROOT_NODE; + } else if (tempNode.isKeywordEnd()) { + // 发现敏感词,将begin~position字符串替换掉 + sb.append(REPLACE_MENT); + // 进入下一个位置 + begin = ++position; + // 重新指向根节点 + tempNode = ROOT_NODE; + } else { + // 检查下一个字符 + position++; + } + } + // position遍历越界仍未匹配到敏感词 + else { + sb.append(text.charAt(begin)); + position = ++begin; + tempNode = ROOT_NODE; + } + } + } catch (Exception e) { + sb = new StringBuilder(text); + } + return sb.toString(); + } + + /** + * 判断是否为符号 ——特殊符号 + * + * @return boolean + * @author NXY + * @date 2023/12/4 11:17 + */ + private boolean isSymbol(Character c) { + // 0x2E80~0x9FFF 是东亚文字范围 + return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF); + } +} + + diff --git a/im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java b/im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java new file mode 100644 index 0000000..567737e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/UserStateUtils.java @@ -0,0 +1,44 @@ +package com.bx.implatform.util; + +import cn.hutool.core.util.StrUtil; +import com.bx.implatform.contant.RedisKey; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * @author: Blue + * @date: 2024-06-10 + * @version: 1.0 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class UserStateUtils { + + private final RedisTemplate redisTemplate; + + public void setBusy(Long userId){ + String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId); + redisTemplate.opsForValue().set(key,1,30, TimeUnit.SECONDS); + } + + public void expire(Long userId){ + String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId); + redisTemplate.expire(key,30, TimeUnit.SECONDS); + } + + public void setFree(Long userId){ + String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId); + redisTemplate.delete(key); + } + + public Boolean isBusy(Long userId){ + String key = StrUtil.join(":", RedisKey.IM_USER_STATE,userId); + return redisTemplate.hasKey(key); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/util/XssUtil.java b/im-platform/src/main/java/com/bx/implatform/util/XssUtil.java new file mode 100644 index 0000000..8178b09 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/XssUtil.java @@ -0,0 +1,21 @@ +package com.bx.implatform.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class XssUtil { + + private XssUtil() { + } + + private static final String XSS_PATTERN = "((?i))|((?i)alert\\s*\\()|((?i)prompt\\s*\\()|((?i)document\\.cookie)|((?i)location\\.href)|((?i)window\\.location)|((?i)onerror\\s*\\()|((?i)eval\\s*\\()|((?i)window\\.open\\s*\\()|((?i)innerHTML)|((?i)onclick\\s*\\()|((?i)onmouseover\\s*\\()|((?i)onsubmit\\s*\\()|((?i)onload\\s*\\()|((?i)onfocus\\s*\\()|((?i)onblur\\s*\\()|((?i)onkeyup\\s*\\()|((?i)onkeydown\\s*\\()|((?i)onkeypress\\s*\\()|((?i)onmouseout\\s*\\()|((?i)src=)|((?i)href=)|((?i)style=)|((?i)background=)|((?i)expression\\s*\\()|((?i)XMLHttpRequest\\s*\\()|((?i)ActiveXObject\\s*\\()|((?i)iframe)|((?i)document\\.write\\s*\\()|((?i)document\\.writeln\\s*\\()|((?i)setTimeout\\s*\\()|((?i)setInterval\\s*\\()|((?i)onreadystatechange\\s*\\()|((?i)appendChild\\s*\\()|((?i)createTextNode\\s*\\()|((?i)createElement\\s*\\()|((?i)getElementsByTagName\\s*\\()|((?i)getElementsByClassName\\s*\\()|((?i)querySelector\\s*\\()|((?i)querySelectorAll\\s*\\()|((?i)document\\.location)|((?i)document\\.body\\.innerHTML)|((?i)document\\.forms)|((?i)document\\.images)|((?i)document\\.links)|((?i)document\\.URL)|((?i)document\\.domain)|((?i)document\\.referrer)|((?i)history\\.back\\s*\\()"; + private static final Pattern PATTERN = Pattern.compile(XSS_PATTERN); + + public static boolean checkXss(String inputString) { + if (inputString != null) { + Matcher matcher = PATTERN.matcher(inputString); + return matcher.find(); + } + return false; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java b/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java new file mode 100644 index 0000000..cf65b05 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java @@ -0,0 +1,29 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +@Schema(description = "好友信息VO") +public class FriendVO { + + @NotNull(message = "好友id不可为空") + @Schema(description = "好友id") + private Long id; + + @NotNull(message = "好友昵称不可为空") + @Schema(description = "好友昵称") + private String nickName; + + + @Schema(description = "好友头像") + private String headImage; + + @Schema(description = "是否开启免打扰") + private Boolean isDnd; + + + @Schema(description = "是否已删除") + private Boolean deleted; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java new file mode 100644 index 0000000..fd2a876 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java @@ -0,0 +1,34 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "群成员信息VO") +public class GroupMemberVO { + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "群内显示名称") + private String showNickName; + + @Schema(description = "群内昵称备注") + private String remarkNickName; + + @Schema(description = "头像") + private String headImage; + + @Schema(description = "是否已退出") + private Boolean quit; + + @Schema(description = "是否在线") + private Boolean online; + + @Schema(description = "群名显示名称") + private String showGroupName; + + @Schema(description = "群名备注") + private String remarkGroupName; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java new file mode 100644 index 0000000..9613a1f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java @@ -0,0 +1,53 @@ +package com.bx.implatform.vo; + +import com.bx.imcommon.serializer.DateToLongSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +public class GroupMessageVO { + + @Schema(description = "消息id") + private Long id; + + @Schema(description = "临时id") + private String tmpId; + + @Schema(description = "群聊id") + private Long groupId; + + @Schema(description = " 发送者id") + private Long sendId; + + @Schema(description = " 发送者昵称") + private String sendNickName; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "消息内容类型 具体枚举值由应用层定义") + private Integer type; + + @Schema(description = "是否回执消息") + private Boolean receipt; + + @Schema(description = "回执消息是否完成") + private Boolean receiptOk; + + @Schema(description = "已读消息数量") + private Integer readedCount = 0; + + @Schema(description = "@用户列表") + private List atUserIds; + + @Schema(description = " 状态") + private Integer status; + + @Schema(description = "发送时间") + @JsonSerialize(using = DateToLongSerializer.class) + private Date sendTime; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java new file mode 100644 index 0000000..1673ee2 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java @@ -0,0 +1,63 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +@Data +@Schema(description = "群信息VO") +public class GroupVO { + + @Schema(description = "群id") + private Long id; + + @Length(max = 20, message = "群名称长度不能大于20") + @NotEmpty(message = "群名称不可为空") + @Schema(description = "群名称") + private String name; + + @Schema(description = "群主id") + private Long ownerId; + + @Schema(description = "头像") + private String headImage; + + @Schema(description = "头像缩略图") + private String headImageThumb; + + @Length(max = 1024, message = "群聊显示长度不能大于1024") + @Schema(description = "群公告") + private String notice; + + @Length(max = 20, message = "显示昵称长度不能大于20") + @Schema(description = "用户在群显示昵称") + private String remarkNickName; + + @Schema(description = "群内显示名称") + private String showNickName; + + @Schema(description = "群名显示名称") + private String showGroupName; + + @Length(max = 20, message = "群备注长度不能大于20") + @Schema(description = "群名备注") + private String remarkGroupName; + + @Schema(description = "是否已解散") + private Boolean dissolve; + + @Schema(description = "是否已退出") + private Boolean quit; + + @Schema(description = "账号是否被封禁") + private Boolean isBanned; + + @Schema(description = "被封禁原因") + private String reason; + + @Schema(description = "是否开启免打扰") + private Boolean isDnd; + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java b/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java new file mode 100644 index 0000000..ba9f9a2 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java @@ -0,0 +1,22 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "用户登录VO") +public class LoginVO { + + @Schema(description = "每次请求都必须在header中携带accessToken") + private String accessToken; + + @Schema(description = "accessToken过期时间(秒)") + private Integer accessTokenExpiresIn; + + @Schema(description = "accessToken过期后,通过refreshToken换取新的token") + private String refreshToken; + + @Schema(description = "refreshToken过期时间(秒)") + private Integer refreshTokenExpiresIn; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java b/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java new file mode 100644 index 0000000..da40ec6 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java @@ -0,0 +1,24 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + * @author: Blue + * @date: 2023-10-28 21:17:59 + * @version: 1.0 + */ +@Data +@AllArgsConstructor +public class OnlineTerminalVO { + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "在线终端类型") + private List terminals; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java new file mode 100644 index 0000000..7777a5f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java @@ -0,0 +1,38 @@ +package com.bx.implatform.vo; + +import com.bx.imcommon.serializer.DateToLongSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +@Data +@Schema(description = "私聊消息VO") +public class PrivateMessageVO { + + @Schema(description = " 消息id") + private Long id; + + @Schema(description = "临时id") + private String tmpId; + + @Schema(description = " 发送者id") + private Long sendId; + + @Schema(description = " 接收者id") + private Long recvId; + + @Schema(description = " 发送内容") + private String content; + + @Schema(description = "消息内容类型 MessageType") + private Integer type; + + @Schema(description = " 状态") + private Integer status; + + @Schema(description = " 发送时间") + @JsonSerialize(using = DateToLongSerializer.class) + private Date sendTime; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java b/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java new file mode 100644 index 0000000..aa92709 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java @@ -0,0 +1,21 @@ +package com.bx.implatform.vo; + +import com.bx.implatform.config.WebrtcConfig; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author: blue + * @date: 2024-06-10 + * @version: 1.0 + */ +@Data +@Schema(description = "系统配置VO") +@AllArgsConstructor +public class SystemConfigVO { + + @Schema(description = "webrtc配置") + private WebrtcConfig webrtc; + +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/SystemMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/SystemMessageVO.java new file mode 100644 index 0000000..d1ee164 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/SystemMessageVO.java @@ -0,0 +1,26 @@ +package com.bx.implatform.vo; + +import com.bx.imcommon.serializer.DateToLongSerializer; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +@Data +@Schema(description = "系统消息VO") +public class SystemMessageVO { + + @Schema(description = " 消息id") + private Long id; + + @Schema(description = " 发送内容") + private String content; + + @Schema(description = "消息内容类型 MessageType") + private Integer type; + + @Schema(description = " 发送时间") + @JsonSerialize(using = DateToLongSerializer.class) + private Date sendTime; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/UploadImageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/UploadImageVO.java new file mode 100644 index 0000000..671fe9a --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/UploadImageVO.java @@ -0,0 +1,21 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "图片上传VO") +public class UploadImageVO { + + @Schema(description = "原图") + private String originUrl; + + @Schema(description = "缩略图") + private String thumbUrl; + + @Schema(description = "图片宽度") + private int width; + + @Schema(description = "图片高度") + private int height; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/UserVO.java b/im-platform/src/main/java/com/bx/implatform/vo/UserVO.java new file mode 100644 index 0000000..64ccae5 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/UserVO.java @@ -0,0 +1,51 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +@Data +@Schema(description = "用户信息VO") +public class UserVO { + + @NotNull(message = "用户id不能为空") + @Schema(description = "id") + private Long id; + + @NotEmpty(message = "用户名不能为空") + @Length(max = 20, message = "用户名不能大于20字符") + @Schema(description = "用户名") + private String userName; + + @NotEmpty(message = "用户昵称不能为空") + @Length(max = 20, message = "昵称不能大于20字符") + @Schema(description = "用户昵称") + private String nickName; + + @Schema(description = "性别") + private Integer sex; + + @Schema(description = "用户类型 1:普通用户 2:审核账户") + private Integer type; + + @Length(max = 128, message = "个性签名不能大于128个字符") + @Schema(description = "个性签名") + private String signature; + + @Schema(description = "头像") + private String headImage; + + @Schema(description = "头像缩略图") + private String headImageThumb; + + @Schema(description = "是否在线") + private Boolean online; + + @Schema(description = "账号是否被封禁") + private Boolean isBanned; + + @Schema(description = "被封禁原因") + private String reason; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupFailedVO.java b/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupFailedVO.java new file mode 100644 index 0000000..7cd4482 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupFailedVO.java @@ -0,0 +1,22 @@ +package com.bx.implatform.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * @author: Blue + * @date: 2024-06-09 + * @version: 1.0 + */ +@Data +@Schema(description = "用户加入群通话失败VO") +public class WebrtcGroupFailedVO { + + @Schema(description = "失败用户列表") + private List userIds; + + @Schema(description = "失败原因") + private String reason; +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java b/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java new file mode 100644 index 0000000..7bed11f --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/WebrtcGroupInfoVO.java @@ -0,0 +1,27 @@ +package com.bx.implatform.vo; + +import com.bx.implatform.session.WebrtcUserInfo; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * @author: Blue + * @date: 2024-06-09 + * @version: 1.0 + */ +@Data +@Schema(description = "群通话信息VO") +public class WebrtcGroupInfoVO { + + + @Schema(description = "是否在通话中") + private Boolean isChating; + + @Schema(description = "通话发起人") + WebrtcUserInfo host; + + @Schema(description = "通话用户列表") + private List userInfos; +} diff --git a/im-platform/src/main/resources/application-dev.yml b/im-platform/src/main/resources/application-dev.yml new file mode 100644 index 0000000..5fbc810 --- /dev/null +++ b/im-platform/src/main/resources/application-dev.yml @@ -0,0 +1,28 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://123.157.250.162:3306/im_platform?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: im_platform + password: 3tKHbkSHfycbriN7 + data: + redis: + host: localhost + port: 6379 + +minio: + endpoint: https://minioapi.zianxin.com #内网地址 + domain: https://minioapi.zianxin.com #外网访问地址 + accessKey: EnBCxtxZ9j0tJJKqjRjG + secretKey: imxrsyk1hM3lZ8dSbzz2QQSrw43rH33uZvnHXFzb + bucketName: im-platform + imagePath: image + filePath: file + videoPath: video + expireIn: 180 # 文件过期时间,单位:天 + +webrtc: + max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16 + iceServers: + - urls: stun:stun.l.google.com:19302 + + diff --git a/im-platform/src/main/resources/application-prod.yml b/im-platform/src/main/resources/application-prod.yml new file mode 100644 index 0000000..ee43fe1 --- /dev/null +++ b/im-platform/src/main/resources/application-prod.yml @@ -0,0 +1,33 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/im_platform?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: im_platform + password: 3tKHbkSHfycbriN7 + data: + redis: + host: localhost + port: 6379 + password: 179965 + +minio: + endpoint: http://192.168.0.6:9000 #内网地址 + domain: https://minioapi.zianxin.com #外网访问地址 + accessKey: EnBCxtxZ9j0tJJKqjRjG + secretKey: imxrsyk1hM3lZ8dSbzz2QQSrw43rH33uZvnHXFzb + bucketName: im-platform + imagePath: image + filePath: file + videoPath: video + expireIn: 180 # 文件过期时间,单位:天 + +webrtc: + max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16 + iceServers: #coturn配置 + - urls: stun:www.boxim.online:3478 + username: admin + credential: UrHHKNvE7nFvBTMV + - urls: turn:www.boxim.online:3478 + username: admin + credential: UrHHKNvE7nFvBTMV + diff --git a/im-platform/src/main/resources/application-test.yml b/im-platform/src/main/resources/application-test.yml new file mode 100644 index 0000000..8be6393 --- /dev/null +++ b/im-platform/src/main/resources/application-test.yml @@ -0,0 +1,32 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/im_platform?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: root + password: naQRLdDPbGv2j7PX_ + data: + redis: + host: localhost + port: 6379 + password: PmEpfRjpBnTN6CgW + +minio: + endpoint: http://127.0.0.1:9001 #内网地址 + domain: https://www.boxim.online/file #外网访问地址 + accessKey: admin + secretKey: 3fBSt6AkgFuD77D6 + bucketName: box-im + imagePath: image + filePath: file + videoPath: video + expireIn: 180 # 文件过期时间,单位:天 + +webrtc: + max-channel: 9 # 多人通话最大通道数量,最大不能超过16,建议值:4,9,16 + iceServers: #coturn配置 + - urls: stun:www.boxim.online:3478 + username: admin + credential: UrHHKNvE7nFvBTMV + - urls: turn:www.boxim.online:3478 + username: admin + credential: UrHHKNvE7nFvBTMV diff --git a/im-platform/src/main/resources/application.yml b/im-platform/src/main/resources/application.yml new file mode 100644 index 0000000..fd187ff --- /dev/null +++ b/im-platform/src/main/resources/application.yml @@ -0,0 +1,33 @@ +server: + port: 8888 +spring: + profiles: + active: prod # 环境 dev|test|prod + application: + name: im-platform + mvc: + pathmatch: + matching-strategy: ant_path_matcher + servlet: + multipart: + max-file-size: 50MB + max-request-size: 50MB + +mybatis-plus: + global-config: + db-config: + id-type: AUTO # ID自增 + configuration: + map-underscore-to-camel-case: true #开启自动驼峰命名规则 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +jwt: + accessToken: + expireIn: 1800 #半个小时 + secret: MIIBIjANBgkq + refreshToken: + expireIn: 604800 #7天 + secret: IKDiqVmn0VFU + + + diff --git a/im-platform/src/main/resources/logback.xml b/im-platform/src/main/resources/logback.xml new file mode 100644 index 0000000..a1084bb --- /dev/null +++ b/im-platform/src/main/resources/logback.xml @@ -0,0 +1,48 @@ + + + + + + + + info + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${LOG_PATH}/${APP_NAME}.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log + 100MB + 60 + 20GB + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/im-platform/src/main/resources/static/favicon.ico b/im-platform/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d31f507d20d05ffccc17cafaeedd689bda925c17 GIT binary patch literal 22710 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 WUt;Xwa-JOk0000 + + + box-im + com.bx + 3.0.0 + + 4.0.0 + + im-server + + + + + com.bx + im-common + 3.0.0 + + + io.netty + netty-all + 4.1.42.Final + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + \ No newline at end of file diff --git a/im-server/src/main/java/com/bx/imserver/IMServerApp.java b/im-server/src/main/java/com/bx/imserver/IMServerApp.java new file mode 100644 index 0000000..95796b2 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/IMServerApp.java @@ -0,0 +1,19 @@ +package com.bx.imserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +@EnableAsync +@EnableScheduling +@ComponentScan(basePackages = {"com.bx"}) +@SpringBootApplication +public class IMServerApp { + + public static void main(String[] args) { + SpringApplication.run(IMServerApp.class, args); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java b/im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java new file mode 100644 index 0000000..552aee5 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java @@ -0,0 +1,21 @@ +package com.bx.imserver.constant; + +public final class ChannelAttrKey { + + private ChannelAttrKey() { + } + + /** + * 用户ID + */ + public static final String USER_ID = "USER_ID"; + /** + * 终端类型 + */ + public static final String TERMINAL_TYPE = "TERMINAL_TYPE"; + /** + * 心跳次数 + */ + public static final String HEARTBEAT_TIMES = "HEARTBEAT_TIMES"; + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java new file mode 100644 index 0000000..298f1c3 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java @@ -0,0 +1,99 @@ +package com.bx.imserver.netty; + +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imserver.constant.ChannelAttrKey; +import com.bx.imserver.netty.processor.AbstractMessageProcessor; +import com.bx.imserver.netty.processor.ProcessorFactory; +import com.bx.imserver.util.SpringContextHolder; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import io.netty.util.AttributeKey; +import lombok.extern.slf4j.Slf4j; + +/** + * WebSocket 长连接下 文本帧的处理器 + * 实现浏览器发送文本回写 + * 浏览器连接状态监控 + */ +@Slf4j +public class IMChannelHandler extends SimpleChannelInboundHandler { + + /** + * 读取到消息后进行处理 + * + * @param ctx channel上下文 + * @param sendInfo 发送消息 + */ + @Override + protected void channelRead0(ChannelHandlerContext ctx, IMSendInfo sendInfo) { + // 创建处理器进行处理 + AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.fromCode(sendInfo.getCmd())); + processor.process(ctx, processor.transForm(sendInfo.getData())); + } + + /** + * 出现异常的处理 打印报错日志 + * + * @param ctx channel上下文 + * @param cause 异常信息 + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error(cause.getMessage()); + //关闭上下文 + //ctx.close(); + } + + /** + * 监控浏览器上线 + * + * @param ctx channel上下文 + */ + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + log.info(ctx.channel().id().asLongText() + "连接"); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + AttributeKey userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); + Long userId = ctx.channel().attr(userIdAttr).get(); + AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); + Integer terminal = ctx.channel().attr(terminalAttr).get(); + ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId, terminal); + // 判断一下,避免异地登录导致的误删 + if (context != null && ctx.channel().id().equals(context.channel().id())) { + // 移除channel + UserChannelCtxMap.removeChannelCtx(userId, terminal); + // 用户下线 + RedisMQTemplate redisTemplate = SpringContextHolder.getBean(RedisMQTemplate.class); + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); + redisTemplate.delete(key); + log.info("断开连接,userId:{},终端类型:{},{}", userId, terminal, ctx.channel().id().asLongText()); + } + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + IdleState state = ((IdleStateEvent) evt).state(); + if (state == IdleState.READER_IDLE) { + // 在规定时间内没有收到客户端的上行数据, 主动断开连接 + AttributeKey attr = AttributeKey.valueOf("USER_ID"); + Long userId = ctx.channel().attr(attr).get(); + AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); + Integer terminal = ctx.channel().attr(terminalAttr).get(); + log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ", userId, terminal); + ctx.channel().close(); + } + } else { + super.userEventTriggered(ctx, evt); + } + + } +} \ No newline at end of file diff --git a/im-server/src/main/java/com/bx/imserver/netty/IMServer.java b/im-server/src/main/java/com/bx/imserver/netty/IMServer.java new file mode 100644 index 0000000..286e356 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/IMServer.java @@ -0,0 +1,10 @@ +package com.bx.imserver.netty; + +public interface IMServer { + + boolean isReady(); + + void start(); + + void stop(); +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java b/im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java new file mode 100644 index 0000000..8c5a36a --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java @@ -0,0 +1,55 @@ +package com.bx.imserver.netty; + +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.mq.RedisMQTemplate; +import jakarta.annotation.PreDestroy; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +@AllArgsConstructor +public class IMServerGroup implements CommandLineRunner { + + public static volatile long serverId = 0; + + private final RedisMQTemplate redisMQTemplate; + + private final List imServers; + + /*** + * 判断服务器是否就绪 + * + **/ + public boolean isReady() { + for (IMServer imServer : imServers) { + if (!imServer.isReady()) { + return false; + } + } + return true; + } + + @Override + public void run(String... args) { + // 初始化SERVER_ID + String key = IMRedisKey.IM_MAX_SERVER_ID; + serverId = redisMQTemplate.opsForValue().increment(key, 1); + // 启动服务 + for (IMServer imServer : imServers) { + imServer.start(); + } + } + + @PreDestroy + public void destroy() { + // 停止服务 + for (IMServer imServer : imServers) { + imServer.stop(); + } + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java b/im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java new file mode 100644 index 0000000..8841de1 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java @@ -0,0 +1,46 @@ +package com.bx.imserver.netty; + +import io.netty.channel.ChannelHandlerContext; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class UserChannelCtxMap { + + /** + * 维护userId和ctx的关联关系,格式:Map> + */ + private static Map> channelMap = new ConcurrentHashMap(); + + public static void addChannelCtx(Long userId, Integer channel, ChannelHandlerContext ctx) { + channelMap.computeIfAbsent(userId, key -> new ConcurrentHashMap()).put(channel, ctx); + } + + public static void removeChannelCtx(Long userId, Integer terminal) { + if (userId != null && terminal != null && channelMap.containsKey(userId)) { + Map userChannelMap = channelMap.get(userId); + userChannelMap.remove(terminal); + if (userChannelMap.isEmpty()) { + channelMap.remove(userId); + } + } + } + + public static ChannelHandlerContext getChannelCtx(Long userId, Integer terminal) { + if (userId != null && terminal != null && channelMap.containsKey(userId)) { + Map userChannelMap = channelMap.get(userId); + if (userChannelMap.containsKey(terminal)) { + return userChannelMap.get(terminal); + } + } + return null; + } + + public static Map getChannelCtx(Long userId) { + if (userId == null) { + return null; + } + return channelMap.get(userId); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java new file mode 100644 index 0000000..dde5665 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java @@ -0,0 +1,18 @@ +package com.bx.imserver.netty.processor; + +import io.netty.channel.ChannelHandlerContext; + +public abstract class AbstractMessageProcessor { + + public void process(ChannelHandlerContext ctx, T data) { + } + + public void process(T data) { + } + + public T transForm(Object o) { + return (T) o; + } + + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java new file mode 100644 index 0000000..8aaaa37 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java @@ -0,0 +1,71 @@ +package com.bx.imserver.netty.processor; + +import cn.hutool.core.util.StrUtil; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imserver.netty.UserChannelCtxMap; +import io.netty.channel.ChannelHandlerContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GroupMessageProcessor extends AbstractMessageProcessor { + + private final RedisMQTemplate redisMQTemplate; + + @Override + public void process(IMRecvInfo recvInfo) { + IMUserInfo sender = recvInfo.getSender(); + List receivers = recvInfo.getReceivers(); + log.info("接收到群消息,发送者:{},接收用户数量:{},内容:{}", sender.getId(), receivers.size(), recvInfo.getData()); + for (IMUserInfo receiver : receivers) { + try { + ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(receiver.getId(), receiver.getTerminal()); + if (!Objects.isNull(channelCtx)) { + // 推送消息到用户 + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.GROUP_MESSAGE.code()); + sendInfo.setData(recvInfo.getData()); + channelCtx.channel().writeAndFlush(sendInfo); + // 消息发送成功确认 + sendResult(recvInfo, receiver, IMSendCode.SUCCESS); + + } else { + // 消息发送成功确认 + sendResult(recvInfo, receiver, IMSendCode.NOT_FIND_CHANNEL); + log.error("未找到channel,发送者:{},接收id:{},内容:{}", sender.getId(), receiver.getId(), recvInfo.getData()); + } + } catch (Exception e) { + // 消息发送失败确认 + sendResult(recvInfo, receiver, IMSendCode.UNKONW_ERROR); + log.error("发送消息异常,发送者:{},接收id:{},内容:{}", sender.getId(), receiver.getId(), recvInfo.getData()); + } + } + } + + + private void sendResult(IMRecvInfo recvInfo, IMUserInfo receiver, IMSendCode sendCode) { + if (recvInfo.getSendResult()) { + IMSendResult result = new IMSendResult<>(); + result.setSender(recvInfo.getSender()); + result.setReceiver(receiver); + result.setCode(sendCode.code()); + result.setData(recvInfo.getData()); + // 推送到结果队列 + String key = StrUtil.join(":",IMRedisKey.IM_RESULT_GROUP_QUEUE,recvInfo.getServiceName()); + redisMQTemplate.opsForList().rightPush(key, result); + } + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java new file mode 100644 index 0000000..01f032d --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java @@ -0,0 +1,56 @@ +package com.bx.imserver.netty.processor; + +import cn.hutool.core.bean.BeanUtil; +import com.bx.imcommon.contant.IMConstant; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMHeartbeatInfo; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imserver.constant.ChannelAttrKey; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AttributeKey; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class HeartbeatProcessor extends AbstractMessageProcessor { + + private final RedisMQTemplate redisMQTemplate; + + @Override + public void process(ChannelHandlerContext ctx, IMHeartbeatInfo beatInfo) { + // 响应ws + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.HEART_BEAT.code()); + ctx.channel().writeAndFlush(sendInfo); + // 设置属性 + AttributeKey heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES); + Long heartbeatTimes = ctx.channel().attr(heartBeatAttr).get(); + ctx.channel().attr(heartBeatAttr).set(++heartbeatTimes); + if (heartbeatTimes % 10 == 0) { + // 每心跳10次,用户在线状态续一次命 + AttributeKey userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); + Long userId = ctx.channel().attr(userIdAttr).get(); + AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); + Integer terminal = ctx.channel().attr(terminalAttr).get(); + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); + redisMQTemplate.expire(key, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); + } + AttributeKey userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); + Long userId = ctx.channel().attr(userIdAttr).get(); + log.debug("心跳,userId:{},{}",userId,ctx.channel().id().asLongText()); + } + + @Override + public IMHeartbeatInfo transForm(Object o) { + HashMap map = (HashMap) o; + return BeanUtil.fillBeanWithMap(map, new IMHeartbeatInfo(), false); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java new file mode 100644 index 0000000..b5480f5 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java @@ -0,0 +1,83 @@ +package com.bx.imserver.netty.processor; + +import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson.JSON; +import com.bx.imcommon.contant.IMConstant; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMLoginInfo; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.model.IMSessionInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imcommon.util.JwtUtil; +import com.bx.imserver.constant.ChannelAttrKey; +import com.bx.imserver.netty.IMServerGroup; +import com.bx.imserver.netty.UserChannelCtxMap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AttributeKey; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class LoginProcessor extends AbstractMessageProcessor { + + private final RedisMQTemplate redisMQTemplate; + + @Value("${jwt.accessToken.secret}") + private String accessTokenSecret; + + @Override + public void process(ChannelHandlerContext ctx, IMLoginInfo loginInfo) { + if (!JwtUtil.checkSign(loginInfo.getAccessToken(), accessTokenSecret)) { + ctx.channel().close(); + log.warn("用户token校验不通过,强制下线,token:{}", loginInfo.getAccessToken()); + return; + } + String strInfo = JwtUtil.getInfo(loginInfo.getAccessToken()); + IMSessionInfo sessionInfo = JSON.parseObject(strInfo, IMSessionInfo.class); + Long userId = sessionInfo.getUserId(); + Integer terminal = sessionInfo.getTerminal(); + log.info("用户登录,userId:{}", userId); + ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId, terminal); + if (context != null && !ctx.channel().id().equals(context.channel().id())) { + // 不允许多地登录,强制下线 + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.FORCE_LOGUT.code()); + sendInfo.setData("您已在其他地方登录,将被强制下线"); + context.channel().writeAndFlush(sendInfo); + log.info("异地登录,强制下线,userId:{}", userId); + } + // 绑定用户和channel + UserChannelCtxMap.addChannelCtx(userId, terminal, ctx); + // 设置用户id属性 + AttributeKey userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); + ctx.channel().attr(userIdAttr).set(userId); + // 设置用户终端类型 + AttributeKey terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE); + ctx.channel().attr(terminalAttr).set(terminal); + // 初始化心跳次数 + AttributeKey heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES); + ctx.channel().attr(heartBeatAttr).set(0L); + // 在redis上记录每个user的channelId,15秒没有心跳,则自动过期 + String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); + redisMQTemplate.opsForValue().set(key, IMServerGroup.serverId, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS); + // 响应ws + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.LOGIN.code()); + ctx.channel().writeAndFlush(sendInfo); + } + + + @Override + public IMLoginInfo transForm(Object o) { + HashMap map = (HashMap) o; + return BeanUtil.fillBeanWithMap(map, new IMLoginInfo(), false); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java new file mode 100644 index 0000000..8ab5101 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java @@ -0,0 +1,67 @@ +package com.bx.imserver.netty.processor; + +import cn.hutool.core.util.StrUtil; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imserver.netty.UserChannelCtxMap; +import io.netty.channel.ChannelHandlerContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PrivateMessageProcessor extends AbstractMessageProcessor { + + private final RedisMQTemplate redisMQTemplate; + + @Override + public void process(IMRecvInfo recvInfo) { + IMUserInfo sender = recvInfo.getSender(); + IMUserInfo receiver = recvInfo.getReceivers().get(0); + log.info("接收到私聊消息,发送者:{},接收者:{},内容:{}", sender.getId(), receiver.getId(), recvInfo.getData()); + try { + ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(receiver.getId(), receiver.getTerminal()); + if (!Objects.isNull(channelCtx)) { + // 推送消息到用户 + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code()); + sendInfo.setData(recvInfo.getData()); + channelCtx.channel().writeAndFlush(sendInfo); + // 消息发送成功确认 + sendResult(recvInfo, IMSendCode.SUCCESS); + } else { + // 消息推送失败确认 + sendResult(recvInfo, IMSendCode.NOT_FIND_CHANNEL); + log.error("未找到channel,发送者:{},接收者:{},内容:{}", sender.getId(), receiver.getId(), recvInfo.getData()); + } + } catch (Exception e) { + // 消息推送失败确认 + sendResult(recvInfo, IMSendCode.UNKONW_ERROR); + log.error("发送异常,发送者:{},接收者:{},内容:{}", sender.getId(), receiver.getId(), recvInfo.getData(), e); + } + + } + + private void sendResult(IMRecvInfo recvInfo, IMSendCode sendCode) { + if (recvInfo.getSendResult()) { + IMSendResult result = new IMSendResult<>(); + result.setSender(recvInfo.getSender()); + result.setReceiver(recvInfo.getReceivers().get(0)); + result.setCode(sendCode.code()); + result.setData(recvInfo.getData()); + // 推送到结果队列 + String key = StrUtil.join(":",IMRedisKey.IM_RESULT_PRIVATE_QUEUE,recvInfo.getServiceName()); + redisMQTemplate.opsForList().rightPush(key, result); + } + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java b/im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java new file mode 100644 index 0000000..f68e0f9 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java @@ -0,0 +1,19 @@ +package com.bx.imserver.netty.processor; + +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imserver.util.SpringContextHolder; + +public class ProcessorFactory { + + public static AbstractMessageProcessor createProcessor(IMCmdType cmd) { + return switch (cmd) { + case LOGIN->SpringContextHolder.getApplicationContext().getBean(LoginProcessor.class); + case HEART_BEAT -> SpringContextHolder.getApplicationContext().getBean(HeartbeatProcessor.class); + case PRIVATE_MESSAGE->SpringContextHolder.getApplicationContext().getBean(PrivateMessageProcessor.class); + case GROUP_MESSAGE->SpringContextHolder.getApplicationContext().getBean(GroupMessageProcessor.class); + case SYSTEM_MESSAGE->SpringContextHolder.getApplicationContext().getBean(SystemMessageProcessor.class); + default -> null; + }; + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/SystemMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/SystemMessageProcessor.java new file mode 100644 index 0000000..906e4eb --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/SystemMessageProcessor.java @@ -0,0 +1,67 @@ +package com.bx.imserver.netty.processor; + +import cn.hutool.core.util.StrUtil; +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.enums.IMSendCode; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.model.IMSendInfo; +import com.bx.imcommon.model.IMSendResult; +import com.bx.imcommon.model.IMUserInfo; +import com.bx.imcommon.mq.RedisMQTemplate; +import com.bx.imserver.netty.UserChannelCtxMap; +import io.netty.channel.ChannelHandlerContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SystemMessageProcessor extends AbstractMessageProcessor { + + private final RedisMQTemplate redisMQTemplate; + + @Override + public void process(IMRecvInfo recvInfo) { + log.info("接收到系统消息,接收用户数量:{},内容:{}", recvInfo.getReceivers().size(), recvInfo.getData()); + for (IMUserInfo receiver : recvInfo.getReceivers()) { + try { + ChannelHandlerContext channelCtx = + UserChannelCtxMap.getChannelCtx(receiver.getId(), receiver.getTerminal()); + if (!Objects.isNull(channelCtx)) { + // 推送消息到用户 + IMSendInfo sendInfo = new IMSendInfo<>(); + sendInfo.setCmd(IMCmdType.SYSTEM_MESSAGE.code()); + sendInfo.setData(recvInfo.getData()); + channelCtx.channel().writeAndFlush(sendInfo); + // 消息发送成功确认 + sendResult(recvInfo, IMSendCode.SUCCESS); + } else { + // 消息推送失败确认 + sendResult(recvInfo, IMSendCode.NOT_FIND_CHANNEL); + log.error("未找到channel,接收者:{},内容:{}", receiver.getId(), recvInfo.getData()); + } + } catch (Exception e) { + // 消息推送失败确认 + sendResult(recvInfo, IMSendCode.UNKONW_ERROR); + log.error("发送异常,,接收者:{},内容:{}", receiver.getId(), recvInfo.getData(), e); + } + } + + } + + private void sendResult(IMRecvInfo recvInfo, IMSendCode sendCode) { + if (recvInfo.getSendResult()) { + IMSendResult result = new IMSendResult<>(); + result.setReceiver(recvInfo.getReceivers().get(0)); + result.setCode(sendCode.code()); + result.setData(recvInfo.getData()); + // 推送到结果队列 + String key = StrUtil.join(":",IMRedisKey.IM_RESULT_SYSTEM_QUEUE,recvInfo.getServiceName()); + redisMQTemplate.opsForList().rightPush(key, result); + } + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java b/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java new file mode 100644 index 0000000..01f03ea --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java @@ -0,0 +1,98 @@ +package com.bx.imserver.netty.tcp; + +import com.bx.imserver.netty.IMChannelHandler; +import com.bx.imserver.netty.IMServer; +import com.bx.imserver.netty.tcp.endecode.MessageProtocolDecoder; +import com.bx.imserver.netty.tcp.endecode.MessageProtocolEncoder; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * TCP服务器,用于连接非网页的客户端,协议格式: 8字节内容的长度+IMSendInfo的JSON序列化 + * + * @author Blue + * @date 2022-11-20 + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "tcpsocket", value = "enable", havingValue = "true", matchIfMissing = false) +public class TcpSocketServer implements IMServer { + + private volatile boolean ready = false; + + @Value("${tcpsocket.port}") + private int port; + + private EventLoopGroup bossGroup; + private EventLoopGroup workGroup; + + @Override + public boolean isReady() { + return ready; + } + + @Override + public void start() { + ServerBootstrap bootstrap = new ServerBootstrap(); + bossGroup = new NioEventLoopGroup(); + workGroup = new NioEventLoopGroup(); + // 设置为主从线程模型 + bootstrap.group(bossGroup, workGroup) + // 设置服务端NIO通信类型 + .channel(NioServerSocketChannel.class) + // 设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理 + .childHandler(new ChannelInitializer() { + // 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等 + @Override + protected void initChannel(Channel ch) throws Exception { + // 获取职责链 + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS)); + pipeline.addLast("encode", new MessageProtocolEncoder()); + pipeline.addLast("decode", new MessageProtocolDecoder()); + pipeline.addLast("handler", new IMChannelHandler()); + } + }) + // bootstrap 还可以设置TCP参数,根据需要可以分别设置主线程池和从线程池参数,来优化服务端性能。 + // 其中主线程池使用option方法来设置,从线程池使用childOption方法设置。 + // backlog表示主线程池中在套接口排队的最大数量,队列由未连接队列(三次握手未完成的)和已连接队列 + .option(ChannelOption.SO_BACKLOG, 5) + // 表示连接保活,相当于心跳机制,默认为7200s + .childOption(ChannelOption.SO_KEEPALIVE, true); + + try { + // 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理 + Channel channel = bootstrap.bind(port).sync().channel(); + // 就绪标志 + this.ready = true; + log.info("tcp server 初始化完成,端口:{}", port); + // 等待服务端口关闭 + //channel.closeFuture().sync(); + } catch (InterruptedException e) { + log.info("tcp server 初始化异常", e); + } + } + + @Override + public void stop() { + if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) { + bossGroup.shutdownGracefully(); + } + if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isShutdown()) { + workGroup.shutdownGracefully(); + } + this.ready = false; + log.info("tcp server 停止"); + } + + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolDecoder.java b/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolDecoder.java new file mode 100644 index 0000000..906d6e5 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolDecoder.java @@ -0,0 +1,30 @@ +package com.bx.imserver.netty.tcp.endecode; + +import com.bx.imcommon.model.IMSendInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.util.CharsetUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public class MessageProtocolDecoder extends ReplayingDecoder { + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { + if (byteBuf.readableBytes() < 4) { + return; + } + // 获取到包的长度 + long length = byteBuf.readLong(); + // 转成IMSendInfo + ByteBuf contentBuf = byteBuf.readBytes((int) length); + String content = contentBuf.toString(CharsetUtil.UTF_8); + ObjectMapper objectMapper = new ObjectMapper(); + IMSendInfo sendInfo = objectMapper.readValue(content, IMSendInfo.class); + list.add(sendInfo); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolEncoder.java b/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolEncoder.java new file mode 100644 index 0000000..84d1cdd --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/tcp/endecode/MessageProtocolEncoder.java @@ -0,0 +1,24 @@ +package com.bx.imserver.netty.tcp.endecode; + +import com.bx.imcommon.model.IMSendInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +import java.nio.charset.StandardCharsets; + +public class MessageProtocolEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, IMSendInfo sendInfo, ByteBuf byteBuf) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + String content = objectMapper.writeValueAsString(sendInfo); + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + // 写入长度 + byteBuf.writeLong(bytes.length); + // 写入命令体 + byteBuf.writeBytes(bytes); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java b/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java new file mode 100644 index 0000000..14af9c4 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java @@ -0,0 +1,107 @@ +package com.bx.imserver.netty.ws; + +import com.bx.imserver.netty.IMChannelHandler; +import com.bx.imserver.netty.IMServer; +import com.bx.imserver.netty.ws.endecode.MessageProtocolDecoder; +import com.bx.imserver.netty.ws.endecode.MessageProtocolEncoder; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.timeout.IdleStateHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * WS服务器,用于连接网页的客户端,协议格式: 直接IMSendInfo的JSON序列化 + * + * @author Blue + * @date 2022-11-20 + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "websocket", value = "enable", havingValue = "true", matchIfMissing = false) +public class WebSocketServer implements IMServer { + + @Value("${websocket.port}") + private int port; + + private volatile boolean ready = false; + + private EventLoopGroup bossGroup; + private EventLoopGroup workGroup; + + + @Override + public boolean isReady() { + return ready; + } + + @Override + public void start() { + ServerBootstrap bootstrap = new ServerBootstrap(); + bossGroup = new NioEventLoopGroup(); + workGroup = new NioEventLoopGroup(); + // 设置为主从线程模型 + bootstrap.group(bossGroup, workGroup) + // 设置服务端NIO通信类型 + .channel(NioServerSocketChannel.class) + // 设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理 + .childHandler(new ChannelInitializer() { + // 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等 + @Override + protected void initChannel(Channel ch) { + // 获取职责链 + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)); + pipeline.addLast("http-codec", new HttpServerCodec()); + pipeline.addLast("aggregator", new HttpObjectAggregator(65535)); + pipeline.addLast("http-chunked", new ChunkedWriteHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/im")); + pipeline.addLast("encode", new MessageProtocolEncoder()); + pipeline.addLast("decode", new MessageProtocolDecoder()); + pipeline.addLast("handler", new IMChannelHandler()); + } + }) + // bootstrap 还可以设置TCP参数,根据需要可以分别设置主线程池和从线程池参数,来优化服务端性能。 + // 其中主线程池使用option方法来设置,从线程池使用childOption方法设置。 + // backlog表示主线程池中在套接口排队的最大数量,队列由未连接队列(三次握手未完成的)和已连接队列 + .option(ChannelOption.SO_BACKLOG, 5) + // 表示连接保活,相当于心跳机制,默认为7200s + .childOption(ChannelOption.SO_KEEPALIVE, true); + + try { + // 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理 + bootstrap.bind(port).sync().channel(); + // 就绪标志 + this.ready = true; + log.info("websocket server 初始化完成,端口:{}", port); + // 等待服务端口关闭 + //channel.closeFuture().sync(); + } catch (InterruptedException e) { + log.info("websocket server 初始化异常", e); + } + } + + @Override + public void stop() { + if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) { + bossGroup.shutdownGracefully(); + } + if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isShutdown()) { + workGroup.shutdownGracefully(); + } + this.ready = false; + log.info("websocket server 停止"); + } + + +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolDecoder.java b/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolDecoder.java new file mode 100644 index 0000000..7374048 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolDecoder.java @@ -0,0 +1,19 @@ +package com.bx.imserver.netty.ws.endecode; + +import com.bx.imcommon.model.IMSendInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +import java.util.List; + +public class MessageProtocolDecoder extends MessageToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame, List list) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + IMSendInfo sendInfo = objectMapper.readValue(textWebSocketFrame.text(), IMSendInfo.class); + list.add(sendInfo); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java b/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java new file mode 100644 index 0000000..3c1a3ba --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java @@ -0,0 +1,20 @@ +package com.bx.imserver.netty.ws.endecode; + +import com.bx.imcommon.model.IMSendInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +import java.util.List; + +public class MessageProtocolEncoder extends MessageToMessageEncoder { + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, IMSendInfo sendInfo, List list) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + String text = objectMapper.writeValueAsString(sendInfo); + TextWebSocketFrame frame = new TextWebSocketFrame(text); + list.add(frame); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java new file mode 100644 index 0000000..c6b2290 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java @@ -0,0 +1,23 @@ +package com.bx.imserver.task; + +import com.bx.imcommon.mq.RedisMQConsumer; +import com.bx.imserver.netty.IMServerGroup; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public abstract class AbstractPullMessageTask extends RedisMQConsumer { + + @Autowired + private IMServerGroup serverGroup; + + @Override + public String generateKey() { + return String.join(":", super.generateKey(), IMServerGroup.serverId + ""); + } + + @Override + public Boolean isReady() { + return serverGroup.isReady(); + } +} diff --git a/im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java new file mode 100644 index 0000000..2d0db6b --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java @@ -0,0 +1,25 @@ +package com.bx.imserver.task; + +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.imserver.netty.processor.AbstractMessageProcessor; +import com.bx.imserver.netty.processor.ProcessorFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = IMRedisKey.IM_MESSAGE_GROUP_QUEUE, batchSize = 10) +public class PullGroupMessageTask extends AbstractPullMessageTask { + + @Override + public void onMessage(IMRecvInfo recvInfo) { + AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.GROUP_MESSAGE); + processor.process(recvInfo); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/task/PullPrivateMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/PullPrivateMessageTask.java new file mode 100644 index 0000000..b9b3545 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/task/PullPrivateMessageTask.java @@ -0,0 +1,25 @@ +package com.bx.imserver.task; + +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.imserver.netty.processor.AbstractMessageProcessor; +import com.bx.imserver.netty.processor.ProcessorFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +@RedisMQListener(queue = IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, batchSize = 10) +public class PullPrivateMessageTask extends AbstractPullMessageTask { + + @Override + public void onMessage(IMRecvInfo recvInfo) { + AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.PRIVATE_MESSAGE); + processor.process(recvInfo); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/task/PullSystemMessageTask.java b/im-server/src/main/java/com/bx/imserver/task/PullSystemMessageTask.java new file mode 100644 index 0000000..c1d6a2b --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/task/PullSystemMessageTask.java @@ -0,0 +1,28 @@ +package com.bx.imserver.task; + +import com.bx.imcommon.contant.IMRedisKey; +import com.bx.imcommon.enums.IMCmdType; +import com.bx.imcommon.model.IMRecvInfo; +import com.bx.imcommon.mq.RedisMQListener; +import com.bx.imserver.netty.processor.AbstractMessageProcessor; +import com.bx.imserver.netty.processor.ProcessorFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * @author: Blue + * @date: 2024-07-16 + * @version: 1.0 + */ +@Slf4j +@Component +@RedisMQListener(queue = IMRedisKey.IM_MESSAGE_SYSTEM_QUEUE,batchSize = 10) +public class PullSystemMessageTask extends AbstractPullMessageTask { + + @Override + public void onMessage(IMRecvInfo recvInfo) { + AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.SYSTEM_MESSAGE); + processor.process(recvInfo); + } + +} diff --git a/im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java b/im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java new file mode 100644 index 0000000..1a1b2e2 --- /dev/null +++ b/im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java @@ -0,0 +1,39 @@ +package com.bx.imserver.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringContextHolder implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContextHolder.applicationContext = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + assertApplicationContext(); + return applicationContext; + } + + public static T getBean(String beanName) { + assertApplicationContext(); + return (T) applicationContext.getBean(beanName); + } + + public static T getBean(Class requiredType) { + assertApplicationContext(); + return applicationContext.getBean(requiredType); + } + + private static void assertApplicationContext() { + if (SpringContextHolder.applicationContext == null) { + throw new RuntimeException("applicaitonContext属性为null,请检查是否注入了SpringContextHolder!"); + } + } + +} diff --git a/im-server/src/main/resources/application-dev.yml b/im-server/src/main/resources/application-dev.yml new file mode 100644 index 0000000..a06ec18 --- /dev/null +++ b/im-server/src/main/resources/application-dev.yml @@ -0,0 +1,5 @@ +spring: + data: + redis: + host: 127.0.0.1 + port: 6379 \ No newline at end of file diff --git a/im-server/src/main/resources/application-prod.yml b/im-server/src/main/resources/application-prod.yml new file mode 100644 index 0000000..a17e518 --- /dev/null +++ b/im-server/src/main/resources/application-prod.yml @@ -0,0 +1,6 @@ +spring: + data: + redis: + host: 127.0.0.1 + port: 6379 + password: 179965 \ No newline at end of file diff --git a/im-server/src/main/resources/application-test.yml b/im-server/src/main/resources/application-test.yml new file mode 100644 index 0000000..79724c4 --- /dev/null +++ b/im-server/src/main/resources/application-test.yml @@ -0,0 +1,6 @@ +spring: + data: + redis: + host: 127.0.0.1 + port: 6379 + password: PmEpfRjpBnTN6CgW \ No newline at end of file diff --git a/im-server/src/main/resources/application.yml b/im-server/src/main/resources/application.yml new file mode 100644 index 0000000..6c3f01b --- /dev/null +++ b/im-server/src/main/resources/application.yml @@ -0,0 +1,17 @@ +spring: + profiles: + active: prod + application: + name: im-server + +websocket: + enable: true + port: 8878 + +tcpsocket: + enable: false # 暂时不开启 + port: 8879 + +jwt: + accessToken: + secret: MIIBIjANBgkq # 跟im-platfrom的secret必须一致 diff --git a/im-server/src/main/resources/logback.xml b/im-server/src/main/resources/logback.xml new file mode 100644 index 0000000..ede4d60 --- /dev/null +++ b/im-server/src/main/resources/logback.xml @@ -0,0 +1,48 @@ + + + + + + + + info + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${LOG_PATH}/${APP_NAME}.log + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + ${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log + 100MB + 60 + 20GB + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/im-uniapp/.env.js b/im-uniapp/.env.js new file mode 100644 index 0000000..489c3d3 --- /dev/null +++ b/im-uniapp/.env.js @@ -0,0 +1,27 @@ +//设置环境(打包前修改此变量) +const ENV = "DEV"; +const UNI_APP = {} +// 表情包路径 +UNI_APP.EMO_URL = "/static/emoji/"; +// #ifdef MP-WEIXIN +// 微信小程序的本地表情包经常莫名失效,建议将表情放到服务器中 +// UNI_APP.EMO_URL = "https://www.boxim.online/emoji/"; +// #endif + +if(ENV=="DEV"){ + // UNI_APP.BASE_URL = "http://127.0.0.1:8888"; + // UNI_APP.WS_URL = "ws://127.0.0.1:8878/im"; + + UNI_APP.BASE_URL = "http://chatsever.zianxin.com" + UNI_APP.WS_URL = "ws://chatapp.zianxin.com/im" + + // H5 走本地代理解决跨域问题 + // #ifdef H5 + UNI_APP.BASE_URL = "/api"; + // #endif +} +if(ENV=="PROD"){ + UNI_APP.BASE_URL = "https://www.boxim.online/api"; + UNI_APP.WS_URL = "wss://www.boxim.online/im"; +} +export default UNI_APP \ No newline at end of file diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue new file mode 100644 index 0000000..df3332c --- /dev/null +++ b/im-uniapp/App.vue @@ -0,0 +1,551 @@ + + + \ No newline at end of file diff --git a/im-uniapp/common/date.js b/im-uniapp/common/date.js new file mode 100644 index 0000000..cd4fc17 --- /dev/null +++ b/im-uniapp/common/date.js @@ -0,0 +1,66 @@ +let toTimeText = (timeStamp, simple) => { + var dateTime = new Date(timeStamp) + var currentTime = Date.parse(new Date()); //当前时间 + var timeDiff = currentTime - dateTime; //与当前时间误差 + var timeText = ''; + if (timeDiff <= 60000) { //一分钟内 + timeText = '刚刚'; + } else if (timeDiff > 60000 && timeDiff < 3600000) { + //1小时内 + timeText = Math.floor(timeDiff / 60000) + '分钟前'; + } else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) { + //今日 + timeText = formatDateTime(dateTime).substr(11, 5); + } else if (isYestday(dateTime)) { + //昨天 + timeText = '昨天' + formatDateTime(dateTime).substr(11, 5); + } else if (isYear(dateTime)) { + //今年 + timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14); + } else { + //不属于今年 + timeText = formatDateTime(dateTime); + if (simple) { + timeText = timeText.substr(2, 8); + } + } + return timeText; +} + +let isYestday = (date) => { + var yesterday = new Date(new Date() - 1000 * 60 * 60 * 24); + return yesterday.getYear() === date.getYear() && + yesterday.getMonth() === date.getMonth() && + yesterday.getDate() === date.getDate(); +} + +let isYear = (date) => { + return date.getYear() === new Date().getYear(); +} + +let formatDateTime = (date) => { + if (date === '' || !date) { + return '' + } + var dateObject = new Date(date) + var y = dateObject.getFullYear() + var m = dateObject.getMonth() + 1 + m = m < 10 ? ('0' + m) : m + var d = dateObject.getDate() + d = d < 10 ? ('0' + d) : d + var h = dateObject.getHours() + h = h < 10 ? ('0' + h) : h + var minute = dateObject.getMinutes() + minute = minute < 10 ? ('0' + minute) : minute + var second = dateObject.getSeconds() + second = second < 10 ? ('0' + second) : second + return y + '/' + m + '/' + d + ' ' + h + ':' + minute + ':' + second +} + + +export { + toTimeText, + isYestday, + isYear, + formatDateTime +} \ No newline at end of file diff --git a/im-uniapp/common/emotion.js b/im-uniapp/common/emotion.js new file mode 100644 index 0000000..f7dfe37 --- /dev/null +++ b/im-uniapp/common/emotion.js @@ -0,0 +1,43 @@ +import UNI_APP from '@/.env.js' + + +const emoTextList = ['憨笑', '媚眼', '开心', '坏笑', '可怜', '爱心', '笑哭', '拍手', '惊喜', '打气', + '大哭', '流泪', '饥饿', '难受', '健身', '示爱', '色色', '眨眼', '暴怒', '惊恐', + '思考', '头晕', '大吐', '酷笑', '翻滚', '享受', '鼻涕', '快乐', '雀跃', '微笑', + '贪婪', '红心', '粉心', '星星', '大火', '眼睛', '音符', "叹号", "问号", "绿叶", + "燃烧", "喇叭", "警告", "信封", "房子", "礼物", "点赞", "举手", "拍手", "点头", + "摇头", "偷瞄", "庆祝", "疾跑", "打滚", "惊吓", "起跳" +]; + +const regex = /\#[\u4E00-\u9FA5]{1,3}\;/gi; + +let containEmoji = (content) => { + return regex.test(content) +} + +let transform = (content, extClass) => { + return content.replace(regex, (emoText)=>{ + // 将匹配结果替换表情图片 + let word = emoText.replace(/\#|\;/gi, ''); + let idx = emoTextList.indexOf(word); + if (idx == -1) { + return emoText; + } + let path = textToPath(emoText); + let img = ``; + return img; + }); +} + +let textToPath = (emoText) => { + let word = emoText.replace(/\#|\;/gi, ''); + let idx = emoTextList.indexOf(word); + return UNI_APP.EMO_URL + idx + ".gif"; +} + +export default { + containEmoji, + emoTextList, + transform, + textToPath +} \ No newline at end of file diff --git a/im-uniapp/common/enums.js b/im-uniapp/common/enums.js new file mode 100644 index 0000000..fa7ada0 --- /dev/null +++ b/im-uniapp/common/enums.js @@ -0,0 +1,70 @@ + +const MESSAGE_TYPE = { + TEXT: 0, + IMAGE: 1, + FILE: 2, + AUDIO: 3, + VIDEO: 4, + RECALL: 10, + READED: 11, + RECEIPT: 12, + TIP_TIME: 20, + TIP_TEXT: 21, + LOADING: 30, + ACT_RT_VOICE: 40, + ACT_RT_VIDEO: 41, + USER_BANNED: 50, + FRIEND_NEW: 80, + FRIEND_DEL: 81, + FRIEND_DND: 82, + GROUP_NEW: 90, + GROUP_DEL: 91, + GROUP_DND: 92, + RTC_CALL_VOICE: 100, + RTC_CALL_VIDEO: 101, + RTC_ACCEPT: 102, + RTC_REJECT: 103, + RTC_CANCEL: 104, + RTC_FAILED: 105, + RTC_HANDUP: 106, + RTC_CANDIDATE: 107, + RTC_GROUP_SETUP: 200, + RTC_GROUP_ACCEPT: 201, + RTC_GROUP_REJECT: 202, + RTC_GROUP_FAILED: 203, + RTC_GROUP_CANCEL: 204, + RTC_GROUP_QUIT: 205, + RTC_GROUP_INVITE: 206, + RTC_GROUP_JOIN: 207, + RTC_GROUP_OFFER: 208, + RTC_GROUP_ANSWER: 209, + RTC_GROUP_CANDIDATE: 210, + RTC_GROUP_DEVICE: 211 +} + +const USER_STATE = { + OFFLINE: 0, + FREE: 1, + BUSY: 2 +} + +const TERMINAL_TYPE = { + WEB: 0, + APP: 1 +} + +const MESSAGE_STATUS = { + FAILED: -2, // 发送失败 + SENDING: -1, // 发送中(消息没到服务器) + PENDING: 0, // 未送达(消息已到服务器,但对方没收到) + DELIVERED: 1, // 已送达(对方已收到,但是未读消息) + RECALL: 2, // 已撤回 + READED: 3, // 消息已读 +} + +export { + MESSAGE_TYPE, + USER_STATE, + TERMINAL_TYPE, + MESSAGE_STATUS +} diff --git a/im-uniapp/common/messageType.js b/im-uniapp/common/messageType.js new file mode 100644 index 0000000..f6b1caf --- /dev/null +++ b/im-uniapp/common/messageType.js @@ -0,0 +1,40 @@ + +// 是否普通消息 +let isNormal = function (type) { + return type >= 0 && type < 10; +} + +// 是否状态消息 +let isStatus = function (type) { + return type >= 10 && type < 20; +} + +// 是否提示消息 +let isTip = function (type) { + return type >= 20 && type < 30; +} + +// 操作交互类消息 +let isAction = function (type) { + return type >= 40 && type < 50; +} + +// 单人通话信令 +let isRtcPrivate = function (type) { + return type >= 100 && type < 200; +} + +// 多人通话信令 +let isRtcGroup = function (type) { + return type >= 200 && type < 300; +} + + +export { + isNormal, + isStatus, + isTip, + isAction, + isRtcPrivate, + isRtcGroup +} \ No newline at end of file diff --git a/im-uniapp/common/recorder-app.js b/im-uniapp/common/recorder-app.js new file mode 100644 index 0000000..f114bbb --- /dev/null +++ b/im-uniapp/common/recorder-app.js @@ -0,0 +1,67 @@ +import UNI_APP from '@/.env.js'; + +const rc = uni.getRecorderManager(); +// 录音开始时间 +let startTime = null; +let checkIsEnable = ()=>{ + return true; +} + +let start = () => { + return new Promise((resolve, reject) => { + rc.onStart(() => { + startTime = new Date(); + resolve() + }); + rc.onError((e) => { + console.log(e); + reject(e) + }) + rc.start({ + format: 'mp3' // 录音格式,可选值:aac/mp3 + }); + }) +} + +let close = () => { + rc.stop(); +} + +let upload = () => { + return new Promise((resolve, reject) => { + rc.onStop((wavFile, a, b) => { + uni.uploadFile({ + url: UNI_APP.BASE_URL + '/file/upload', + header: { + accessToken: uni.getStorageSync("loginInfo").accessToken + }, + filePath: wavFile.tempFilePath, + name: 'file', + success: (res) => { + let r = JSON.parse(res.data); + if (r.code != 200) { + reject(r.message); + } else { + const duration = (new Date().getTime() - startTime.getTime()) / + 1000 + const data = { + duration: Math.round(duration), + url: r.data + } + resolve(data); + } + }, + fail: (e) => { + reject(e); + } + }) + }); + }) +} + +export { + checkIsEnable, + start, + close, + upload +} \ No newline at end of file diff --git a/im-uniapp/common/recorder-h5.js b/im-uniapp/common/recorder-h5.js new file mode 100644 index 0000000..3855942 --- /dev/null +++ b/im-uniapp/common/recorder-h5.js @@ -0,0 +1,92 @@ +import UNI_APP from '@/.env.js'; + +let rc = null; +let duration = 0; +let chunks = []; +let stream = null; +let startTime = null; +let checkIsEnable = () => { + if (origin.indexOf('https') === -1 && origin.indexOf('localhost') === -1 && + origin.indexOf('127.0.0.1') === -1) { + uni.showToast({ + title: '请在https环境中使用录音功能', + icon: 'error' + }) + return false; + } + if (!navigator.mediaDevices || !window.MediaRecorder) { + uni.showToast({ + title: '当前浏览器不支持录音', + icon: 'error' + }) + return false; + } + return true; +} + +let start = () => { + return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => { + startTime = new Date().getTime(); + chunks = []; + stream = audioStream; + rc = new MediaRecorder(stream) + rc.start() + }) +} + +let close = () => { + stream.getTracks().forEach((track) => { + track.stop() + }) + rc.stop() +} + + +let upload = () => { + return new Promise((resolve, reject) => { + rc.ondataavailable = (e) => { + chunks.push(e.data) + } + rc.onstop = () => { + if(!chunks[0].size){ + chunks = []; + return; + } + duration = (new Date().getTime() - startTime) / 1000; + const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' }); + const name = new Date().getDate() + '.mp3'; + const file = new File([newbolb], name) + uni.uploadFile({ + url: UNI_APP.BASE_URL + '/file/upload', + header: { + accessToken: uni.getStorageSync("loginInfo").accessToken + }, + file: file, + name: 'file', + success: (res) => { + let r = JSON.parse(res.data); + if (r.code != 200) { + console.log(res) + reject(r.message); + } else { + const data = { + duration: parseInt(duration), + url: r.data + } + resolve(data); + } + }, + fail: (e) => { + reject(e); + } + }) + } + }) +} + +export { + checkIsEnable, + start, + close, + upload +} \ No newline at end of file diff --git a/im-uniapp/common/request.js b/im-uniapp/common/request.js new file mode 100644 index 0000000..09cfd6b --- /dev/null +++ b/im-uniapp/common/request.js @@ -0,0 +1,93 @@ +import UNI_APP from '@/.env.js' + +// 请求队列 +let requestList = []; +// 是否正在刷新中 +let isRefreshToken = false; + +const request = (options) => { + const header = options.header || {}; + const loginInfo = uni.getStorageSync("loginInfo"); + if (loginInfo) { + header.accessToken = loginInfo.accessToken; + } + return new Promise(function (resolve, reject) { + uni.request({ + url: UNI_APP.BASE_URL + options.url, + method: options.method || 'GET', + header: header, + data: options.data || {}, + timeout: options.timeout || 3000, + async success(res) { + if (res.data.code == 200) { + return resolve(res.data.data) + } else if (res.data.code == 400) { + getApp().$vm.exit(); + } else if (res.data.code == 401) { + console.log("token失效,尝试重新获取") + if (isRefreshToken) { + // 正在刷新token,把其他请求存起来 + requestList.push(() => { + resolve(request(options)) + }) + return; + } + isRefreshToken = true; + // 发送请求, 进行刷新token操作, 获取新的token + const res = await reqRefreshToken(loginInfo); + if (!res || res.data.code != 200) { + requestList = []; + isRefreshToken = false; + console.log("刷新token失败") + getApp().$vm.exit(); + return; + } + let newInfo = res.data.data; + uni.setStorageSync("loginInfo", newInfo); + requestList.forEach(cb => cb()); + requestList = []; + isRefreshToken = false; + // 重新发送刚才的请求 + return resolve(request(options)) + + } else { + uni.showToast({ + icon: "none", + title: res.data.message, + duration: 1500 + }) + return reject(res.data) + } + }, + fail(error) { + uni.showToast({ + title: "网络似乎有点不给力哟", + icon: "none", + duration: 1500 + }) + return reject(error) + } + }); + }); +} + + +const reqRefreshToken = (loginInfo) => { + return new Promise(function (resolve, reject) { + uni.request({ + method: 'PUT', + url: UNI_APP.BASE_URL + '/refreshToken', + header: { + refreshToken: loginInfo.refreshToken + }, + success: (res) => { + resolve(res); + }, + fail: (res) => { + reject(res); + } + }); + }); +} + +export default request; \ No newline at end of file diff --git a/im-uniapp/common/str.js b/im-uniapp/common/str.js new file mode 100644 index 0000000..3f6de92 --- /dev/null +++ b/im-uniapp/common/str.js @@ -0,0 +1,14 @@ +let html2Escape = (strText) => { + return strText.replace(/[<>&"]/g, function(c) { + return { + '<': '<', + '>': '>', + '&': '&', + '"': '"' + } [c]; + }); +} + +export default { + html2Escape +} \ No newline at end of file diff --git a/im-uniapp/common/url.js b/im-uniapp/common/url.js new file mode 100644 index 0000000..c95d834 --- /dev/null +++ b/im-uniapp/common/url.js @@ -0,0 +1,23 @@ + +// 使用正则表达式匹配更广泛的URL格式(此正则由deepseek生成) +const regex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]|\bwww\.[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + +let containUrl = (content) => { + return regex.test(content) +} + + +let replaceURLWithHTMLLinks = (content, color) => { + return content.replace(regex, (url) => { + // 如果URL不以http(s)://开头,则添加http://前缀 + if (!url.startsWith("http")) { + url = "http://" + url; + } + return `${url}`; + }); +} + +export default { + containUrl, + replaceURLWithHTMLLinks +} \ No newline at end of file diff --git a/im-uniapp/common/wssocket.js b/im-uniapp/common/wssocket.js new file mode 100644 index 0000000..5747dc5 --- /dev/null +++ b/im-uniapp/common/wssocket.js @@ -0,0 +1,154 @@ +let accessToken = ""; +let messageCallBack = null; +let closeCallBack = null; +let connectCallBack = null; +let isConnect = false; //连接标识 避免重复连接 +let rec = null; +let lastConnectTime = new Date(); // 最后一次连接时间 +let socketTask = null; + +let connect = (wsurl, token) => { + accessToken = token; + if (isConnect) { + return; + } + lastConnectTime = new Date(); + socketTask = uni.connectSocket({ + url: wsurl, + success: (res) => {}, + fail: (e) => { + console.log(e); + console.log("WebSocket连接失败,10s后重连"); + setTimeout(() => { + connect(); + }, 10000) + } + }); + + socketTask.onOpen((res) => { + console.log("WebSocket连接成功"); + isConnect = true; + // 发送登录命令 + let loginInfo = { + cmd: 0, + data: { + accessToken: accessToken + } + }; + socketTask.send({ + data: JSON.stringify(loginInfo) + }); + }) + + socketTask.onMessage((res) => { + let sendInfo = JSON.parse(res.data) + if (sendInfo.cmd == 0) { + heartCheck.start() + connectCallBack && connectCallBack(); + console.log('WebSocket登录成功') + } else if (sendInfo.cmd == 1) { + // 重新开启心跳定时 + heartCheck.reset(); + } else { + // 其他消息转发出去 + console.log("接收到消息", sendInfo); + messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data) + } + }) + + socketTask.onClose((res) => { + console.log('WebSocket连接关闭') + isConnect = false; + closeCallBack && closeCallBack(res); + }) + + socketTask.onError(e => { + console.log("ws错误:", e) + close(1006); + isConnect = false; + closeCallBack && closeCallBack(e); + }) +} + +//定义重连函数 +let reconnect = (wsurl, accessToken) => { + console.log("尝试重新连接"); + if (isConnect) { + return; + } + // 延迟10秒重连 避免过多次过频繁请求重连 + let timeDiff = new Date().getTime() - lastConnectTime.getTime() + let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; + rec && clearTimeout(rec); + rec = setTimeout(function() { + connect(wsurl, accessToken); + }, delay); +}; + +//设置关闭连接 +let close = (code) => { + if (!isConnect) { + return; + } + socketTask.close({ + code: code, + complete: (res) => { + isConnect = false; + console.log("关闭websocket连接"); + }, + fail: (e) => { + console.log("关闭websocket连接失败", e); + } + }); +}; + + +// 心跳设置 +let heartCheck = { + timeout: 20000, //每段时间发送一次心跳包 这里设置为20s + timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) + start: function() { + if (isConnect) { + console.log('发送WebSocket心跳') + let heartBeat = { + cmd: 1, + data: {} + }; + sendMessage(JSON.stringify(heartBeat)) + } + }, + reset: function() { + clearTimeout(this.timeoutObj); + this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); + } +}; + +let sendMessage = (message) => { + socketTask.send({ data: message }) +} + +let onConnect = (callback) => { + connectCallBack = callback; +} + + +let onMessage = (callback) => { + messageCallBack = callback; +} + + +let onClose = (callback) => { + closeCallBack = callback; +} + + +// 将方法暴露出去 +export { + connect, + reconnect, + close, + sendMessage, + onConnect, + onMessage, + onClose +} \ No newline at end of file diff --git a/im-uniapp/components/bar/arrow-bar.vue b/im-uniapp/components/bar/arrow-bar.vue new file mode 100644 index 0000000..6ecf189 --- /dev/null +++ b/im-uniapp/components/bar/arrow-bar.vue @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/bar/bar-group.vue b/im-uniapp/components/bar/bar-group.vue new file mode 100644 index 0000000..570965f --- /dev/null +++ b/im-uniapp/components/bar/bar-group.vue @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/bar/btn-bar.vue b/im-uniapp/components/bar/btn-bar.vue new file mode 100644 index 0000000..cc9408c --- /dev/null +++ b/im-uniapp/components/bar/btn-bar.vue @@ -0,0 +1,68 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/bar/switch-bar.vue b/im-uniapp/components/bar/switch-bar.vue new file mode 100644 index 0000000..3616229 --- /dev/null +++ b/im-uniapp/components/bar/switch-bar.vue @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-at-box/chat-at-box.vue b/im-uniapp/components/chat-at-box/chat-at-box.vue new file mode 100644 index 0000000..8f7c257 --- /dev/null +++ b/im-uniapp/components/chat-at-box/chat-at-box.vue @@ -0,0 +1,173 @@ + + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-group-readed/chat-group-readed.vue b/im-uniapp/components/chat-group-readed/chat-group-readed.vue new file mode 100644 index 0000000..9e2d7d6 --- /dev/null +++ b/im-uniapp/components/chat-group-readed/chat-group-readed.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-item/chat-item.vue b/im-uniapp/components/chat-item/chat-item.vue new file mode 100644 index 0000000..ddd79da --- /dev/null +++ b/im-uniapp/components/chat-item/chat-item.vue @@ -0,0 +1,200 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-message-item/chat-message-item.vue b/im-uniapp/components/chat-message-item/chat-message-item.vue new file mode 100644 index 0000000..dbcecc3 --- /dev/null +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -0,0 +1,546 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-record/chat-record.vue b/im-uniapp/components/chat-record/chat-record.vue new file mode 100644 index 0000000..cb390d8 --- /dev/null +++ b/im-uniapp/components/chat-record/chat-record.vue @@ -0,0 +1,243 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/file-upload/file-upload.vue b/im-uniapp/components/file-upload/file-upload.vue new file mode 100644 index 0000000..c364ffd --- /dev/null +++ b/im-uniapp/components/file-upload/file-upload.vue @@ -0,0 +1,122 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/friend-item/friend-item.vue b/im-uniapp/components/friend-item/friend-item.vue new file mode 100644 index 0000000..543ef87 --- /dev/null +++ b/im-uniapp/components/friend-item/friend-item.vue @@ -0,0 +1,82 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/group-item/group-item.vue b/im-uniapp/components/group-item/group-item.vue new file mode 100644 index 0000000..48a7481 --- /dev/null +++ b/im-uniapp/components/group-item/group-item.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/im-uniapp/components/group-member-selector/group-member-selector.vue b/im-uniapp/components/group-member-selector/group-member-selector.vue new file mode 100644 index 0000000..58d9cd8 --- /dev/null +++ b/im-uniapp/components/group-member-selector/group-member-selector.vue @@ -0,0 +1,180 @@ + + + + + + + \ No newline at end of file diff --git a/im-uniapp/components/group-rtc-join/group-rtc-join.vue b/im-uniapp/components/group-rtc-join/group-rtc-join.vue new file mode 100644 index 0000000..6ee2459 --- /dev/null +++ b/im-uniapp/components/group-rtc-join/group-rtc-join.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/head-image/head-image.vue b/im-uniapp/components/head-image/head-image.vue new file mode 100644 index 0000000..9fa8bc2 --- /dev/null +++ b/im-uniapp/components/head-image/head-image.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/image-upload/image-upload.vue b/im-uniapp/components/image-upload/image-upload.vue new file mode 100644 index 0000000..74410da --- /dev/null +++ b/im-uniapp/components/image-upload/image-upload.vue @@ -0,0 +1,100 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/loading/loading.vue b/im-uniapp/components/loading/loading.vue new file mode 100644 index 0000000..b0f0c98 --- /dev/null +++ b/im-uniapp/components/loading/loading.vue @@ -0,0 +1,71 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/long-press-menu/long-press-menu.vue b/im-uniapp/components/long-press-menu/long-press-menu.vue new file mode 100644 index 0000000..22b7c8a --- /dev/null +++ b/im-uniapp/components/long-press-menu/long-press-menu.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/nav-bar/nav-bar.vue b/im-uniapp/components/nav-bar/nav-bar.vue new file mode 100644 index 0000000..5ec1b65 --- /dev/null +++ b/im-uniapp/components/nav-bar/nav-bar.vue @@ -0,0 +1,121 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/virtual-scroller/virtual-scroller.vue b/im-uniapp/components/virtual-scroller/virtual-scroller.vue new file mode 100644 index 0000000..48a99e4 --- /dev/null +++ b/im-uniapp/components/virtual-scroller/virtual-scroller.vue @@ -0,0 +1,60 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/hybrid/html/rtc-group/index.html b/im-uniapp/hybrid/html/rtc-group/index.html new file mode 100644 index 0000000..dc38d0f --- /dev/null +++ b/im-uniapp/hybrid/html/rtc-group/index.html @@ -0,0 +1,23 @@ + + + + + + + + 语音通话 + + +
+ 音视频通话功能属于付费功能,如有需要请联系作者购买商业版源码... +
+
+ 点击下方文档了解详细信息: +
+ + + \ No newline at end of file diff --git a/im-uniapp/hybrid/html/rtc-private/index.html b/im-uniapp/hybrid/html/rtc-private/index.html new file mode 100644 index 0000000..6ebde9b --- /dev/null +++ b/im-uniapp/hybrid/html/rtc-private/index.html @@ -0,0 +1,23 @@ + + + + + + + + 视频通话 + + +
+ 音视频通话功能属于付费功能,如有需要请联系作者购买商业版源码... +
+
+ 点击下方文档了解详细信息: +
+ + + \ No newline at end of file diff --git a/im-uniapp/im-var.scss b/im-uniapp/im-var.scss new file mode 100644 index 0000000..abd9878 --- /dev/null +++ b/im-uniapp/im-var.scss @@ -0,0 +1,64 @@ +// 颜色 +$im-color-primary: #3e45d7; +$im-color-primary-light-1: mix(#fff, $im-color-primary, 10%); +$im-color-primary-light-2: mix(#fff, $im-color-primary, 20%); +$im-color-primary-light-3: mix(#fff, $im-color-primary, 30%); +$im-color-primary-light-4: mix(#fff, $im-color-primary, 40%); +$im-color-primary-light-5: mix(#fff, $im-color-primary, 50%); +$im-color-primary-light-6: mix(#fff, $im-color-primary, 60%); +$im-color-primary-light-7: mix(#fff, $im-color-primary, 70%); +$im-color-primary-light-8: mix(#fff, $im-color-primary, 80%); +$im-color-primary-light-9: mix(#fff, $im-color-primary, 90%); +$im-color-primary-dark-1: mix(#000, $im-color-primary, 10%); +$im-color-primary-dark-2: mix(#000, $im-color-primary, 20%); +$im-color-primary-dark-3: mix(#000, $im-color-primary, 30%); +$im-color-primary-dark-4: mix(#000, $im-color-primary, 40%); + +$im-color-success: #18bc37; +$im-color-warning: #f3a73f; +$im-color-danger: #e43d33; +$im-color-info: #8f939c; + +// 文字颜色 +$im-text-color: #000000; +$im-text-color-light: #6a6a6a; +$im-text-color-lighter: #909399; +$im-text-color-lighter-extra: #c7c7c7; + +// 边框颜色 +$im-border: #F0F0F0; +$im-border-light: #EDEDED; +$im-border-lighter: #DCDCDC; +$im-border-lighter-extra: #B9B9B9; + +// 文字大小 +$im-font-size: 30rpx; +$im-font-size-small: 28rpx; +$im-font-size-smaller: 26rpx; +$im-font-size-smaller-extra: 24rpx; +$im-font-size-large: 32rpx; +$im-font-size-larger: 34rpx; +$im-font-size-larger-extra: 36rpx; + +// 阴影 +$im-box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); +$im-box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1); +$im-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .12); +$im-box-shadow-dark: 0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 背景 +$im-bg: #f8f9ff; +$im-bg-linear: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%); +$im-bg-active: #f7f8fc; +$im-bg-active-dark: $im-color-primary-light-9; +$im-bg-input: #fafafa; + + +// 标题 +$im-title-size: 26px; +$im-title-size-1: 22px; +$im-title-size-2: 18px; + +$font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; + +$im-nav-bar-height: 50px; \ No newline at end of file diff --git a/im-uniapp/im.scss b/im-uniapp/im.scss new file mode 100644 index 0000000..f063472 --- /dev/null +++ b/im-uniapp/im.scss @@ -0,0 +1,258 @@ +/** 原生button样式 **/ +uni-button { + font-size: $im-font-size !important; + border-radius: 40rpx !important; +} +uni-button[type='primary'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important; +} + +uni-button[type='primary'][plain] { + color: $im-color-primary !important; + border: 1px solid $im-color-primary; + background-color: transparent; +} + +uni-button[type='warn'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important; +} + +uni-button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background: transparent !important; +} + +uni-button[size='mini'] { + font-size: $im-font-size-smaller !important; +} + +// #ifdef MP-WEIXIN +// wx小程序只有button,没有uni-botton +button { + font-size: $im-font-size !important; +} + +button[type='primary'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important; +} + +button[type='primary'][plain] { + color: $im-color-primary !important; + border: 2px solid $im-color-primary; + background: transparent; +} + +button[type='warn'] { + color: #fff !important; + background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important; +} + +button[type='warn'][plain] { + color: $im-color-danger !important; + border: 1px solid $im-color-danger !important; + background: transparent !important; +} + +button[size='mini'] { + font-size: $im-font-size-smaller !important; +} +// #endif + +.button-hover[type='primary'] { + color: #fff !important; + background-color: $im-color-primary-dark-1 !important; +} + +/** uni-ui input激活后边框、图标颜色 **/ +.uni-easyinput__content.is-focused:not(.is-input-error-border) { + border-color: $im-color-primary-light-2 !important; + + .content-clear-icon { + color: $im-color-primary-light-2 !important; + } +} + +/** 底部导航 **/ +.uni-tabbar-bottom .uni-tabbar { + box-shadow: $im-box-shadow; +} + +.uni-tabbar-border { + display: none; +} + +.segmented-control { + border-color: $im-color-primary !important; + + .segmented-control__item--button { + border-color: $im-color-primary !important; + } + .segmented-control__item--button--active { + background-color: $im-color-primary !important; + + .segmented-control__text{ + color: #fff !important; + } + } + .segmented-control__text{ + color: $im-color-primary !important; + } +} + +.uni-radio-input svg{ + border-color: white !important; + background-color: $im-color-primary !important; +} + +.uni-radio-input svg { + background-color: $im-color-primary !important; + border-color: $im-color-primary !important; + background-clip: content-box !important; + box-sizing: border-box; + border-radius: 50%; + transform: translate(-50%, -50%) scale(0.7)!important; +} + +.uni-radio-input svg path{ + fill: $im-color-primary !important; +} + +.uni-radio-input { + background-color: white !important; + border-color: $im-color-primary !important; +} + +.uni-radio-input-disabled { + background-color: rgb(225, 225, 225) !important; + border-color: rgb(209, 209, 209) !important; + opacity: 0.5; +} + +.uni-section__content-title { + font-size: $im-font-size !important; + color: $im-text-color-light; +} + +.uni-forms-item__label { + color: $im-text-color; + font-size: $im-font-size !important; +} +.uni-forms-item { + //margin-bottom: 8px !important; +} +.uni-easyinput__content-input { + font-size: $im-font-size !important; +} +.uni-easyinput__placeholder-class { + color: $im-text-color-lighter; + font-size: $im-font-size !important;; +} +.uni-easyinput__content-textarea { + font-size: $im-font-size !important;; +} +.uni-input-input:disabled { + color: $im-text-color-light; +} + +.uni-forms-item.is-direction-top .uni-forms-item__label { + padding: 0 !important; +} +.uni-data-checklist .checklist-group .checklist-box .checklist-content .checklist-text { + font-size: $im-font-size !important; +} +.uni-card .uni-card__content { + color: unset !important; + padding: 10px 0 !important; +} + +.uni-tag{ + transform: scale(0.9); + margin-left: 5rpx; + border-radius: 12rpx !important; + font-weight: 600 !important; + white-space: nowrap; +} + +.uni-switch-input-checked { + background-color: $im-color-primary-light-1 !important; + border-color: $im-color-primary-light-1 !important; +} +.uni-modal__title { + font-size: $im-font-size-larger !important; +} +.uni-modal__bd { + font-size: $im-font-size; +} +.uni-modal__ft { + font-size: $im-font-size; + line-height: 90rpx !important; + + .uni-modal__btn_primary { + color: $im-color-primary !important; + } +} + + + + +.nav-bar { + height: 100rpx; + padding: 0 20rpx; + display: flex; + align-items: center; + border-bottom: 1rpx solid $im-border; + + .nav-search { + flex: 1; + } + + .nav-add { + cursor: pointer; + } +} + + +.bottom-btn { + margin: 40rpx 40rpx; + + uni-button + uni-button { + margin-top: 20rpx; + } +} + +.emoji-large { + width: 64rpx !important; + height: 64rpx !important; + vertical-align: bottom !important; +} + +.emoji-normal { + width: 54rpx !important; + height: 54rpx !important; + vertical-align: bottom !important; +} + +.emoji-small { + width: 36rpx !important; + height: 36rpx !important; + vertical-align: bottom !important; +} + +.none-pointer-events { + uni-image img { + // 阻止微信默认长按菜单 + pointer-events: none; + -webkit-pointer-events: none; + -ms-pointer-events: none; + -moz-pointer-events: none; + } +} + +p { + margin-block-start: 1em; + margin-block-end: 1em; +} diff --git a/im-uniapp/index.html b/im-uniapp/index.html new file mode 100644 index 0000000..c3ff205 --- /dev/null +++ b/im-uniapp/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/im-uniapp/main.js b/im-uniapp/main.js new file mode 100644 index 0000000..8fa350a --- /dev/null +++ b/im-uniapp/main.js @@ -0,0 +1,65 @@ +import App from './App' +import request from './common/request'; +import emotion from './common/emotion.js'; +import url from './common/url.js'; +import str from './common/str.js'; +import * as enums from './common/enums.js'; +import * as date from './common/date'; +import * as socketApi from './common/wssocket'; +import * as messageType from './common/messageType'; +import { createSSRApp } from 'vue' +import uviewPlus from '@/uni_modules/uview-plus' +import * as pinia from 'pinia'; +import useChatStore from '@/store/chatStore.js' +import useFriendStore from '@/store/friendStore.js' +import useGroupStore from '@/store/groupStore.js' +import useConfigStore from '@/store/configStore.js' +import useUserStore from '@/store/userStore.js' +import barGroup from '@/components/bar/bar-group' +import arrowBar from '@/components/bar/arrow-bar' +import btnBar from '@/components/bar/btn-bar' +import switchBar from '@/components/bar/switch-bar' +// #ifdef H5 +import * as recorder from './common/recorder-h5'; +import ImageResize from "quill-image-resize-mp"; +import Quill from "quill"; +// 以下组件用于兼容部分手机聊天边框无法输入的问题 +window.Quill = Quill; +window.ImageResize = { default: ImageResize }; +// 调试器 +// import VConsole from 'vconsole' +// new VConsole(); +// #endif +// #ifndef H5 +import * as recorder from './common/recorder-app'; +// #endif +export function createApp() { + const app = createSSRApp(App) + app.use(uviewPlus); + app.use(pinia.createPinia()); + app.component('bar-group', barGroup); + app.component('arrow-bar', arrowBar); + app.component('btn-bar', btnBar); + app.component('switch-bar', switchBar); + app.config.globalProperties.$http = request; + app.config.globalProperties.$wsApi = socketApi; + app.config.globalProperties.$msgType = messageType; + app.config.globalProperties.$emo = emotion; + app.config.globalProperties.$url = url; + app.config.globalProperties.$str = str; + app.config.globalProperties.$enums = enums; + app.config.globalProperties.$date = date; + app.config.globalProperties.$rc = recorder; + // 初始化时再挂载store对象 + app.config.globalProperties.$mountStore = () => { + app.config.globalProperties.chatStore = useChatStore(); + app.config.globalProperties.friendStore = useFriendStore(); + app.config.globalProperties.groupStore = useGroupStore(); + app.config.globalProperties.configStore = useConfigStore(); + app.config.globalProperties.userStore = useUserStore(); + } + return { + app, + pinia + } +} \ No newline at end of file diff --git a/im-uniapp/manifest.json b/im-uniapp/manifest.json new file mode 100644 index 0000000..ff617c0 --- /dev/null +++ b/im-uniapp/manifest.json @@ -0,0 +1,136 @@ +{ + "name" : "盒子IM", + "appid" : "__UNI__69DD57A", + "description" : "", + "versionName" : "3.4.0", + "versionCode" : 3400, + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : false, + "waiting" : false, + "autoclose" : false, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : { + "Camera" : {}, + "Record" : {}, + "Bluetooth" : {} + }, + "softinput" : { + "mode" : "adjustResize" + }, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], + "minSdkVersion" : 21 + }, + /* ios打包配置 */ + "ios" : { + "dSYMs" : false, + "privacyDescription" : { + "NSMicrophoneUsageDescription" : "", + "NSCameraUsageDescription" : "" + }, + "idfa" : false + }, + /* SDK配置 */ + "sdkConfigs" : { + "ad" : {}, + "speech" : {} + }, + "icons" : { + "android" : { + "xhdpi" : "unpackage/res/icons/96x96.png", + "hdpi" : "unpackage/res/icons/72x72.png", + "xxhdpi" : "unpackage/res/icons/144x144.png", + "xxxhdpi" : "unpackage/res/icons/192x192.png" + }, + "ios" : { + "appstore" : "unpackage/res/icons/1024x1024.png", + "ipad" : { + "app" : "unpackage/res/icons/76x76.png", + "app@2x" : "unpackage/res/icons/152x152.png", + "notification" : "unpackage/res/icons/20x20.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "proapp@2x" : "unpackage/res/icons/167x167.png", + "settings" : "unpackage/res/icons/29x29.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "spotlight" : "unpackage/res/icons/40x40.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png" + }, + "iphone" : { + "app@2x" : "unpackage/res/icons/120x120.png", + "app@3x" : "unpackage/res/icons/180x180.png", + "notification@2x" : "unpackage/res/icons/40x40.png", + "notification@3x" : "unpackage/res/icons/60x60.png", + "settings@2x" : "unpackage/res/icons/58x58.png", + "settings@3x" : "unpackage/res/icons/87x87.png", + "spotlight@2x" : "unpackage/res/icons/80x80.png", + "spotlight@3x" : "unpackage/res/icons/120x120.png" + } + } + } + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "wxda94f40bfad0262c", + "libVersion" : "latest", + "setting" : { + "urlCheck" : false, + "minified" : true + }, + "usingComponents" : true, + "lazyCodeLoading" : "requiredComponents" + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "3", + "h5" : { + "title" : "盒子IM", + "router" : { + "base" : "/h5/" + } + } +} +/* ios打包配置 *//* SDK配置 */ + diff --git a/im-uniapp/package.json b/im-uniapp/package.json new file mode 100644 index 0000000..602b736 --- /dev/null +++ b/im-uniapp/package.json @@ -0,0 +1,12 @@ +{ + "name": "boxim", + "uni-app": { + "scripts": {} + }, + "dependencies": { + "pinyin-pro": "^3.23.1", + "quill": "^1.3.7", + "quill-image-resize-mp": "^3.0.1", + "vconsole": "^3.15.1" + } +} \ No newline at end of file diff --git a/im-uniapp/pages.json b/im-uniapp/pages.json new file mode 100644 index 0000000..e4df795 --- /dev/null +++ b/im-uniapp/pages.json @@ -0,0 +1,114 @@ +{ + "easycom": { + "autoscan": true, + "custom": { + "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", + "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", + "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue" + } + }, + "pages": [{ + "path": "pages/chat/chat" + }, { + "path": "pages/login/login" + }, + { + "path": "pages/register/register" + }, + { + "path": "pages/friend/friend" + }, + { + "path": "pages/group/group" + }, + { + "path": "pages/mine/mine" + }, + { + "path": "pages/common/user-info" + }, + { + "path": "pages/chat/chat-box", + "style": { + "mp-weixin": { + "disableScroll": true + }, + "app-plus": { + // adjustPan窗体高度不变,但窗体上推、adjustResize屏幕高度=webview窗体高度+软键盘高度 + "softinputMode": "adjustResize" + } + } + }, + { + "path": "pages/chat/chat-private-video" + }, + { + "path": "pages/chat/chat-group-video" + }, + { + "path": "pages/friend/friend-add" + }, + { + "path": "pages/group/group-info" + }, + { + "path": "pages/group/group-edit" + }, + { + "path": "pages/group/group-invite" + }, + { + "path": "pages/group/group-member" + }, + { + "path": "pages/mine/mine-edit" + }, + { + "path": "pages/mine/mine-password" + } + ], + "globalStyle": { + "navigationBarTitleText": "盒子IM", + "navigationBarTextStyle": "black", + /* #ifndef MP-WEIXIN */ + "navigationStyle": "custom", + /* #endif */ + /* #ifdef MP-WEIXIN */ + "navigationStyle": "default", + /* #endif */ + "navigationBarBackgroundColor": "#f7f7f7", + "backgroundColor": "#f7f7f7" + }, + "tabBar": { + "color": "#000000", + "selectedColor": "#587ff0", + "borderStyle": "black", + "backgroundColor": "#ffffff", + "list": [{ + "pagePath": "pages/chat/chat", + "iconPath": "static/tab/chat.png", + "selectedIconPath": "static/tab/chat_active.png", + "text": "消息" + }, + { + "pagePath": "pages/friend/friend", + "iconPath": "static/tab/friend.png", + "selectedIconPath": "static/tab/friend_active.png", + "text": "好友" + }, + { + "pagePath": "pages/group/group", + "iconPath": "static/tab/group.png", + "selectedIconPath": "static/tab/group_active.png", + "text": "群聊" + }, + { + "pagePath": "pages/mine/mine", + "iconPath": "static/tab/mine.png", + "selectedIconPath": "static/tab/mine_active.png", + "text": "我的" + } + ] + }, + "uniIdRouter": {} +} \ No newline at end of file diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue new file mode 100644 index 0000000..ff3b7e7 --- /dev/null +++ b/im-uniapp/pages/chat/chat-box.vue @@ -0,0 +1,1358 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/chat/chat-group-video.vue b/im-uniapp/pages/chat/chat-group-video.vue new file mode 100644 index 0000000..434771f --- /dev/null +++ b/im-uniapp/pages/chat/chat-group-video.vue @@ -0,0 +1,138 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/chat/chat-private-video.vue b/im-uniapp/pages/chat/chat-private-video.vue new file mode 100644 index 0000000..0677af7 --- /dev/null +++ b/im-uniapp/pages/chat/chat-private-video.vue @@ -0,0 +1,100 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/chat/chat.vue b/im-uniapp/pages/chat/chat.vue new file mode 100644 index 0000000..a8df83e --- /dev/null +++ b/im-uniapp/pages/chat/chat.vue @@ -0,0 +1,207 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/common/external-link.vue b/im-uniapp/pages/common/external-link.vue new file mode 100644 index 0000000..678df4a --- /dev/null +++ b/im-uniapp/pages/common/external-link.vue @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/common/user-info.vue b/im-uniapp/pages/common/user-info.vue new file mode 100644 index 0000000..396cd88 --- /dev/null +++ b/im-uniapp/pages/common/user-info.vue @@ -0,0 +1,254 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/friend/friend-add.vue b/im-uniapp/pages/friend/friend-add.vue new file mode 100644 index 0000000..b8c312a --- /dev/null +++ b/im-uniapp/pages/friend/friend-add.vue @@ -0,0 +1,146 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/friend/friend.vue b/im-uniapp/pages/friend/friend.vue new file mode 100644 index 0000000..713df63 --- /dev/null +++ b/im-uniapp/pages/friend/friend.vue @@ -0,0 +1,192 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/group/group-edit.vue b/im-uniapp/pages/group/group-edit.vue new file mode 100644 index 0000000..fd7cdee --- /dev/null +++ b/im-uniapp/pages/group/group-edit.vue @@ -0,0 +1,199 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/group/group-info.vue b/im-uniapp/pages/group/group-info.vue new file mode 100644 index 0000000..df78bae --- /dev/null +++ b/im-uniapp/pages/group/group-info.vue @@ -0,0 +1,377 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/group/group-invite.vue b/im-uniapp/pages/group/group-invite.vue new file mode 100644 index 0000000..a0256df --- /dev/null +++ b/im-uniapp/pages/group/group-invite.vue @@ -0,0 +1,176 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/group/group-member.vue b/im-uniapp/pages/group/group-member.vue new file mode 100644 index 0000000..8d2cacf --- /dev/null +++ b/im-uniapp/pages/group/group-member.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/group/group.vue b/im-uniapp/pages/group/group.vue new file mode 100644 index 0000000..ed71653 --- /dev/null +++ b/im-uniapp/pages/group/group.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/login/login.vue b/im-uniapp/pages/login/login.vue new file mode 100644 index 0000000..03c9e5b --- /dev/null +++ b/im-uniapp/pages/login/login.vue @@ -0,0 +1,293 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/mine/mine-edit.vue b/im-uniapp/pages/mine/mine-edit.vue new file mode 100644 index 0000000..f3e6bc2 --- /dev/null +++ b/im-uniapp/pages/mine/mine-edit.vue @@ -0,0 +1,140 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/mine/mine-password.vue b/im-uniapp/pages/mine/mine-password.vue new file mode 100644 index 0000000..4b7f8f0 --- /dev/null +++ b/im-uniapp/pages/mine/mine-password.vue @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pages/mine/mine.vue b/im-uniapp/pages/mine/mine.vue new file mode 100644 index 0000000..ecc8654 --- /dev/null +++ b/im-uniapp/pages/mine/mine.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/im-uniapp/pages/register/register.vue b/im-uniapp/pages/register/register.vue new file mode 100644 index 0000000..d511311 --- /dev/null +++ b/im-uniapp/pages/register/register.vue @@ -0,0 +1,351 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/pnpm-lock.yaml b/im-uniapp/pnpm-lock.yaml new file mode 100644 index 0000000..2bf897b --- /dev/null +++ b/im-uniapp/pnpm-lock.yaml @@ -0,0 +1,373 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + pinyin-pro: + specifier: ^3.23.1 + version: 3.27.0 + quill: + specifier: ^1.3.7 + version: 1.3.7 + quill-image-resize-mp: + specifier: ^3.0.1 + version: 3.0.1 + vconsole: + specifier: ^3.15.1 + version: 3.15.1 + +packages: + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + copy-text-to-clipboard@3.2.2: + resolution: {integrity: sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==} + engines: {node: '>=12'} + + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + + deep-equal@1.1.2: + resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} + engines: {node: '>= 0.4'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + eventemitter3@2.0.3: + resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-diff@1.1.2: + resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mutation-observer@1.0.3: + resolution: {integrity: sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + parchment@1.1.4: + resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} + + pinyin-pro@3.27.0: + resolution: {integrity: sha512-Osdgjwe7Rm17N2paDMM47yW+jUIUH3+0RGo8QP39ZTLpTaJVDK0T58hOLaMQJbcMmAebVuK2ePunTEVEx1clNQ==} + + quill-delta@3.6.3: + resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==} + engines: {node: '>=0.10'} + + quill-image-resize-mp@3.0.1: + resolution: {integrity: sha512-etBtUbNH3XY21PVwfclMNGkifjWKSVXei4Dc4GWVeUyxOoympcNaV+Bk7Ocjc6Y8VlDKs2tcf3zyJ9syIFL/KA==} + + quill@1.3.7: + resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + vconsole@3.15.1: + resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==} + +snapshots: + + '@babel/runtime@7.28.4': {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + clone@2.1.2: {} + + copy-text-to-clipboard@3.2.2: {} + + core-js@3.47.0: {} + + deep-equal@1.1.2: + dependencies: + is-arguments: 1.2.0 + is-date-object: 1.1.0 + is-regex: 1.2.1 + object-is: 1.1.6 + object-keys: 1.1.1 + regexp.prototype.flags: 1.5.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + eventemitter3@2.0.3: {} + + extend@3.0.2: {} + + fast-diff@1.1.2: {} + + function-bind@1.1.2: {} + + functions-have-names@1.2.3: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + lodash@4.17.21: {} + + math-intrinsics@1.1.0: {} + + mutation-observer@1.0.3: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + parchment@1.1.4: {} + + pinyin-pro@3.27.0: {} + + quill-delta@3.6.3: + dependencies: + deep-equal: 1.1.2 + extend: 3.0.2 + fast-diff: 1.1.2 + + quill-image-resize-mp@3.0.1: + dependencies: + lodash: 4.17.21 + + quill@1.3.7: + dependencies: + clone: 2.1.2 + deep-equal: 1.1.2 + eventemitter3: 2.0.3 + extend: 3.0.2 + parchment: 1.1.4 + quill-delta: 3.6.3 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + vconsole@3.15.1: + dependencies: + '@babel/runtime': 7.28.4 + copy-text-to-clipboard: 3.2.2 + core-js: 3.47.0 + mutation-observer: 1.0.3 diff --git a/im-uniapp/static/emoji/0.gif b/im-uniapp/static/emoji/0.gif new file mode 100644 index 0000000000000000000000000000000000000000..e7848ec7ddb79d984bc02b550963385afd84c442 GIT binary patch literal 17131 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 IneCqc0DN%wdjJ3c literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/1.gif b/im-uniapp/static/emoji/1.gif new file mode 100644 index 0000000000000000000000000000000000000000..cb5b26b38f8f73c244bf3ced0581abe583da3311 GIT binary patch literal 14377 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/10.gif b/im-uniapp/static/emoji/10.gif new file mode 100644 index 0000000000000000000000000000000000000000..53a6c672bd36132e0686c107284015e50de157d6 GIT binary patch literal 17811 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

eat{1|V1B~v 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/12.gif b/im-uniapp/static/emoji/12.gif new file mode 100644 index 0000000000000000000000000000000000000000..2362b4540f5f5ab4521d33103074572fbe2dbbf2 GIT binary patch literal 13938 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/14.gif b/im-uniapp/static/emoji/14.gif new file mode 100644 index 0000000000000000000000000000000000000000..131c3d97896420a98838d481f65918d7ab1663ea GIT binary patch literal 12888 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/15.gif b/im-uniapp/static/emoji/15.gif new file mode 100644 index 0000000000000000000000000000000000000000..25c57aee0b8ae66c151291f5dd13373338444c16 GIT binary patch literal 5214 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/16.gif b/im-uniapp/static/emoji/16.gif new file mode 100644 index 0000000000000000000000000000000000000000..271165675a49117441fd8c714f0ee493c5c74ffd GIT binary patch literal 17080 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 aMV@;3>#F+{G?Qx|<83c0Nd12YzxrP;tCyw# literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/18.gif b/im-uniapp/static/emoji/18.gif new file mode 100644 index 0000000000000000000000000000000000000000..478a42136d7d8cb7155857778b929fe77ec6d62f GIT binary patch literal 19485 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/19.gif b/im-uniapp/static/emoji/19.gif new file mode 100644 index 0000000000000000000000000000000000000000..59261eaf8363f9239b67200ad6d46df6ce8960ae GIT binary patch literal 13048 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/2.gif b/im-uniapp/static/emoji/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..b01cbcf364760a36258b2fe1409edc91ed16432d GIT binary patch literal 18762 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 ZeJK@!;vim11uvz7ms0URA*Eu+e*nwAZb|?E literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/20.gif b/im-uniapp/static/emoji/20.gif new file mode 100644 index 0000000000000000000000000000000000000000..45becb459899c6e92fa44fa6a5299c6f302e45b5 GIT binary patch literal 19698 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/21.gif b/im-uniapp/static/emoji/21.gif new file mode 100644 index 0000000000000000000000000000000000000000..d3f1afee90f34e75ecf26b65e729967a72062b4d GIT binary patch literal 19411 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%0DqxYu=K?bS%BVy^yNj8FF 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/23.gif b/im-uniapp/static/emoji/23.gif new file mode 100644 index 0000000000000000000000000000000000000000..5dbd9f314b178f5624f9a3365c89aba4ebdc8414 GIT binary patch literal 10257 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 W^Qs}Lf@{Hx@_zubU^|`w literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/24.gif b/im-uniapp/static/emoji/24.gif new file mode 100644 index 0000000000000000000000000000000000000000..d3ae9465698537a0c44e63310a8e634a2871172c GIT binary patch literal 12918 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/25.gif b/im-uniapp/static/emoji/25.gif new file mode 100644 index 0000000000000000000000000000000000000000..5cf2a9e77fad04e9e81a67f2a3fe732ae05fcb08 GIT binary patch literal 19279 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/26.gif b/im-uniapp/static/emoji/26.gif new file mode 100644 index 0000000000000000000000000000000000000000..793b2ac021c40e1423b9fc531aea763c11ccf744 GIT binary patch literal 15979 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/27.gif b/im-uniapp/static/emoji/27.gif new file mode 100644 index 0000000000000000000000000000000000000000..b78a02bf07238df952e7d7fae0e9e74c2573fd97 GIT binary patch literal 12412 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 GW<4hl#|dNXuTKtMzk zq)R^v(u?dHJf8EuAMXEty6diW?7bAj z=wtv|senmoU{&ihTKBJLn%yk&?{U{9KO98;5faj*b&$~d6)J88F z*s?!~RR5Vqarj>N_7$d|DKx-tA@D{9*!C!W%n7t2cL&fXmGr>4+(8~SFnet)f%@o) zc(OOgK{hk6VSk#U08FR=)7HQjBVbe$_@uR$L3bP{3oKZll}G~Rn!xG}V9vl=i5}Pr z2j0sA)4E3m>}Lx)N7=ln*-l& z0*fAJC91%hjHm$Xj}MZ0el;@z`MN-gF|d3m$chEncmNdX z#yP`3mI?sdkNnJ7MxILkO4d2el6;ej9I3v1oGA$GHXT+_0(nNjl94)+y8bEm$txb9 zP8L`({W)U@yym$gOtYWH)|<-1}ar%>riKf^84A;i|s6{0gK$F68is(rx z+i0`uXdeI2YgwQLJ>RQxmZ1(*QXCdAgk6IJ@4423Vfmg&pqBi!gc2BL1O4xNaR6lc zY9_|2$_8p8C^SA87a4cynw|k1W9w^+`;=4k_p-Hjb@YYXIo@*ikVpJzd4qsEJIEu< z#P!g6UaF2x&YA(ZA{CiVfY_RGdk5E4U0)F16s;9RjTpT4TXpa^ahD%7GgvG=qB!mUvB4}X|AvEqS zAt)>+BQ7B$A_@QVLf~5ScDN~Hgi-&~7Vb4M9{VuPkC!^)z z<7?|-@2G{5N8lo$oShwHB&5YKDrgn7nu-WUSXfO}MOjT+RYDz&QIk*?6IK=br!L0R z{*Jq&hwndi9saAX@PDj(J{8=(a4lmTy`AqmI;eYly2F1DS;qN4_C;CkKgRo0*Wo|* zMOpnn))m5aM(F&;{dWWJPZJ-niNlr>kh=2Md zE;c4QDl#Jcaad?baL}WN4+8H8-1GOld&k$u`?i;-hr64ri?h?Mn~o0lcD6RwRyVF& zT9{umGrekJY-DI~MPE->M_Wr%LtPDnyRS-$3i5KYGSX6#65?W_BEo1Pl%T+6em*2G z4>uPlf&X8HOoUNlIM0_L{Re=@JaV zq4%n`^iyFu!Z3~BsE?LAtrOXZeO78&;Z7$;b%N>BK&GxzxnRvIrW=EP?K_ICL6e`j zN)6ch+Jr3jJs_l9CQGO0(RY=QG1QH$4(rWTHKTtpC%UMaCc55VI`DLZrpo>~z5DTN zx5*4PrgkJqE9^?1Hp#3~>tV!VLjt>`!YM5)m(t}5CIqh$0%Kq0B-zm>yR$IUa0__z zghbwm5aOBIk#uCjt=xeH=>NcnBKCCzq5Bt16l*Ke<4&mmoX?Pu8_?9N>?Gw^3bUz1)?}UtBFpz(LC9k~_`Bx)@E~pe*O<=+ zt!;+}KiY~mb;41`Wp?3j$}_6p!kXiQW2=g;Q)4<5c10pPp~}Mjta?({tJqj$GfEiJ z@hp6Dshz$Ab0LxfBe)~XTSOQH70V(RMR;?27#J3HvAz0=r5f+KeYSl@wurnVMKGVH zqAFsSGeLYW3HaY3#Y#i!Cyo3TqNuO3tB?dy?5Pj%xJ`pK=60SxixNsN9CNUIONp2b9Y|hlLx}HGuw=LuN;eLNuop8^KG_OG za^FHV^J7FSQyzpQW#xJSL)K>BQNtemAIr&$*-hzdZ#C%a&`Ze9$ zi0vqY*nC)#W)Hu`cJ#Hud>B67pLL@cBmeG))k3{6e{G*X5JDovfrTxHKv>ZB(T!9@w0H?J`udpSns_`a zC2LYvQWNDo#Z=98OgesWEs>&-Cb@Q9u?e9%`8Thc-8p7`Z$@R-81eP-QpY}hY`0d4 zmX%E+#?CkQdsvIh&T@~XnFS4+_swvMQe)NEu0U!Q9c$45#{Vc&Yv$gC@- ztKGXnDd*;zv@lyFGv>b&wFyA7GB&U(8Rde z-%9eNFCml+q&WgTr^0mUiCHjKVN$)GRSzNDAKVE?C$5g_it8ng5urd1q#&k8cfas4 zW#9$ruoxS}tBDV3@(&7XDc=s~yy_1iN|A96w69)X&R8n}_fKayOTB5o zg$Vf|Q)IJVVOqGqVIH+n#wHf|`9;eHee9}Uz_e?3%`ra#ieceNX#^vlDib;SrdPT! z_$S+@L+7D)2N5@g<=Bs!EU}PV+2Zkb(F|4wJ0jE$y0SO3O-9C|%Jd=wBUwnqYX>;| zu!e5jmlvUL8FU-+ARk7j-3m%lmJA6m`CY1sVi|#V$B%>()Wkvsylh@!Z^o-=_X@Sf zZ;#(fjq4P!otlpa(VYW6(5!c)b6PCfeg89E7gNKukC~V1tTt(3*8(8{_HdRzUhr@( z;(46^LbT`^M*qo5RRdw-kFVfwDVA)9>t#MOO-nCSl}8rylPFr&iV7(F7arxD$;{So zkjp%*BYq_NJ^CbD4*C+`Q+CagNo-~HJv;l+-lVLx{BF*pea!Y$1ZB%%FQrf$;apPX z!#&Y!Hth$4_HPu923uYjj|99^JUg411@)fjbg(bgYG60+D0AG%(^O&mSx3aS#X+TI z1EN;xAl0U#1B3rSJNF#gg#SWY`5f9d%2u*8%s5FfugvO-l~eZqi#d^(<_^TG|8`zd zlqqx|K^+;ZU+YTGBP@@9j3=3Za#cHc+gW zN?*>GNP}dneyDnn_*e}wk$Mvr@S zb;odaBNt=EJPr|(<4{^%RwoC$eyXev4KM|yu(3N@4g$8P)NP(yN-~fr;Uds5`OJxL zZV(>|!t=80NxjD7#g(FkQtC{{LHlMpQ7jYqnMqmq3!0c@>RV|ZXInw(f%|Jk*$&a> z_?g!2`{vv+hc!c4AuiR?5?^%f2eXrnTn5FA9bP@oWuxNj&5!ZI>$!SsiDRH7E|C8n zLp5lQA2e8?eRnLDzpg!6_j!cQ)@hVY3)5C$^Wj?k%T#Pn}A5&1{zlvchq5^S99obRvq6Wexgr0-V3VLolX z=M8~Cum={%n2$v{blU{iZ5?~{!3!viDjVk4x7VI$%5?E{fGEF|Fb1IA!Wt+w2E#@6 zXIyV5F!Ihp(S#bbRVeBDSWrJgvV)K%St<{|fww3SDi~{b8iKG$qgG`fO&e7o65I%Y zR?F;chO3VhRu6DQU=7toQkLF&7g-q?d$2gvehGi$Fq`Qr^4@=_j%CD+%W&duAvJ_l zq-q?#KZ$`EL#aI}*j`RGy~X-5`&*lQgrcETN?uFxa- z?`&Dtfxk}KpT4R2ug(HsH9l5<<)IaRw?&||H|0o zmC@9uzb=Z=FT_Ol+Aw+KH*SBacuLDaafz0qgMhCfA(VLR`G%!f;pFgx#|J;YHqioI z9yj!}bgYOKm_(vDvn}q=OeTipweO!CiceMwu|EtHALp>?QyLpJB!&k<5<8HM4qlz1 zYMZ>aVN^GR-XSS?r24oXO+Nu`{b_#xIh#jB!jvKi zBB7_jy)+t(U7=J$`!-7nnmkcUlqz2i?dequZ&Rt8z(*FJQaNaGkzBvK)|qM*FrfNO z36%h%mS5PKpwLdXB_uSRPznM&oS~QT{Fh5svhNJ91*drOCm1~UFlFM+GN&k5#SdIA zSe<3*2}@U zZe&3MLsl$!{3U~(@tkdkHiGp1<sv`FV0)VUODhH6|c^;EdG5=X2Q5J2~hs%10o z`9qoYlG2%zjo(WC$zla?Xp`f#6oi0HJ9J}2dp=Fx9k;)3jI3x;8`hm zU?WB-y|-9oEw?>bVBL27I`yu?>FyiPN53Scgg>9|9n1oIp=&BXPc^WUww1i}Of$#d z58litEM2zI$~|OkRLWW<3K=!;pF$o=klDraLbM`PFh~_B5y2l?$DY$V6o+=+-- zw_;ZY%laZ=Hs_f>K5fXo#Kl-BJ>&Z&f4trSiby!w`l>h20GG3^#SKtL%~eL{q{Pg% zd`_7t#BUJixYp!5)hHZP2y$>+&jrCJ_+@kAq7`gV_!acHGp zmp&`YzH6SYbmrvsE-i`zeAk0w$;g8|6-nmCGQo|CQVt;gPsJYJdW0B%VE!QeCJn00QHMUU4 z$3JFyLde)DG8?w1A=t=4KH{f4l9u_dY5MYy_clJ2>O_2R)wd7Ei$?=#Vj6Kr@=Py3 z%LM1O-T&*y=P4uW()J@Qws`b-hC?8$66kEKPiUk1mJKzeIW;%99-;|n2aRsf!Cm$X-0zUBIVmSP0M-Xbc<;7ieD1e_Yw_l29(-9vG}6P}J%z zP}>IW++@|0V=3nvMq8++68Hx%x826^I9Kn?@#f!9-mE8cudtJn_QbOYT(9P3s zx2{`_q>tp6ScyO*4H8ITLufp~int9z%aV}Y-WlfXH)TRc5pe_)2 z+CZ7u)H<$i!h1px_Y}0)<_@99>-2JVe;D0J{hU{y;K6jHecXZC{Yv@9FYLiZ`-@== z9#V^G8*P5Xfn@s9Fkg%vU@S+aBY*76mp?_mnM^rh#YG<-kaaGz4Z zU#;h(C3&m$yB`YjdpRt3k}+c^Z4b7^hO+7W z#r*Ql4&6(yl&Gwnq>QNS^7=kh`h(V*bF@<8(8~22t&YE+%ayU>@@w$F+`Jk#&rK1| zhRd({#yDjzq3A%14mfV6lO7!%U;jJ5290Xdvg%7}XBYa1;=e?rlp$GKwZ$XoG^wR& zEgU!J#4;n(WcEU?=9!fnT@5lRp?Il3T$ZU+Up8Lu^x_q_C61f3XuA~nyEQw5aooHv z`1})!LpODfIr-h_`v{sy+LOOFK2bylnDk*lhf;g%(TSu5IS3Po z5#R7;$AjfVI$}8!Z5HEDbH(;?qNw=-V(46ax1}u7ur~p%j$>N+vn?#1fjZx}WIdiL zu6WorL-jkhS8Mtu><5$FYMcjQPVKpm8BFb%mL*~Y634{v$@#=` z#YGxInWDS#uz4VZ4>zB?!vl&D<1kKj&VWdS7F#DleXn>~t{n!V+?pT3{{-Pensk-f zM!O{9m60=5RX&jTR-8=QE{;+uhBCVXafh;y^(vis6(Vu>KyYkcQ7hi%Vg>BH zX1SkOaA`%N!(uFxua!A(^-O?mxaj9gK7_T6GqzDGp(d{G6=ti{Jn5z`+iDHdG*!|3 zvqaKsO-&Z1sFsMv7+2n4o)QQ0LS(0)caH?QCNIEKAjh9?I*Eu^8K%>&soJj zdA+&+mGFUVgL}uEU4?(i$400wz*Y z^_wRO6v6(+Zn8ngt5tmp{^Qn$1mSa)?<(%k);%|&{?gnxENlEp(6?*3iP!p=xsBGk zb=&Q7PQ9vr*>^cd2)D_v{V$fDM*raPf;}(T^MXD9?_f`+b64vkv%Sb{FEZQzFEZN; zFY3aJy6~bdyr>H=>cWe<@S-lfs0%OZ!i&1_qAt9s3oq)zi@NZlF1)A4Fr4aumfvQ;JclSU~22-#O>IcddKw`tE;b&03jxv-f`YJG0j_&-?5xP7bDK zK9+zb@RbW#VI=3dBH#cK>^1~>#tWqgupbZh8-cI!U?&Ouo(Vo7fIqn4kKeysmI8+f z;LHtO*d@7#ob>nNiIFqn>i~5&Tjou4FAHF2HQ**FUx_MzoqVzUFdK65-3KmePF-wQOs5yvkD(%{Mn_{UbT%N88mo_i8E zUaZiUrvc8h!RNccPu}1Yil>q6$3*E6uPxxhcJL3IkEf-<+qOT_Y^GWbrk-xj4@6JB z*b0ts|5~&Gd_x5%W5JQ#;4I^3p%wUvIgo6-lIsY5O5g-yz|UtE21v|3+Ta_rrV`z+ zIU3-wd*D`uC)YNC!vUX?rNOr@^{kEHB=dQL1vqyR96Qn;MqbXOfaB4ZQ*?(aH~n#Z z%W{n-IPCkQT9xgpGZim$`vPw1oO$nq?G;RU@LSuLdwO7){8k|e-f;NN z#e(x`VESHg$nI+?5^R*s<(vW^De|W#mVQ@{^pFROw@z`l2h?0@a!J2)TycV*4WTnuvcZ}swDVC6RZ}WWTB>5sFjtK?`0cS$~FW4 z!J!Jmx7zRALAG_bC+gz`1y)^FhrHa}F%+M0pVeQ>&5;ZrzaV-z#+QC5AjAqgeg7dA z6F{@VdK$Um-56xLe}H2&lfEN*>rTJuAU`u2cC$6cGSWQqFyk;i+y@hRI5;HCJkkp5 zLksn#o3CE44I5xFzf8h|tgyCge_+<;B!@ET7$bd>o*$k-z!)3r6AX=vjR`mm5l=$2T-0+zPvz>A#k6nDKYm zkTCsU%hC4>J!}x^!!RJ|Upc{D5!$KpN ze)LsywY8}j=42+_Cp?t7Gc+{#uczql9~vGS<{!$ykarkkv^@htXraf#bbi^pxtTkK zgoXQr_|csxR@l`D`T+qnb7NZq-qsdxWN$~L5D4~UQ!?JfmOvnp9qh~uO&uKm@}-3O zMI5Gwg#YDB`?s(0Kl-kf!ePeh%oI8^;3%EuzzjW%`L$&8fPc<~^pElW^riiCE{6Z; zYp|M(!P<-cZ!h|FTJZf)iZE0?5eAw`y z{{FqX+Pimd*W9YEx_RUJwX2oCSCn75Tvl3A%q_Z9c(EWq??UeRoO9V{vobTzq^G5( zBqwna6Hc?^S#hzaPM(N49vu~V>}W)I81o1tG~{q_P+)-np@VdqpRW(~!2W&Sd-v@2 z^89Vr&K(}x-M4LZb9Hfca@^uzPa)gc+HAJAva~QaGc_?bG9(cRcmsVs+$LQeZ7od= zbv3LiW}}L-lA^)}w7i@wN=8}=DJdb2fWyR~q9VdVf{;~kQ2j0>W({xw+#0m3eL?{! z2$02;9B&pi#Y1Iuy*qAlTN06K&IOKD#ce5QQsC>3s**=%Fyv&ue~3isS#OU*Lo*Mb zPQ(Lqsgk~fJ$c54oQSD&!!#FGSLy=xd7FC;T)ka;S9DxHSHCqI+cZQnmYnrnzu%yB98C*m*QA}{1}oa7q{q}5j`hK+>AeHN2HY1i9$h(UdlvYE z>}>T0{}8l*%Y{Pd8Jqe@{%A+0qNkeWj&z?Ln-mD`qLD?L2Q>WOHPoAa{q$06H#kQj zliEx!-kX_iZQx{o4d&EN+kh4)#1%Lli8m@YZzNtcBumMNW%wjWpTu#8q%|9x1hG*= zTElFuu_Gc-j8fGQ3#!@JO4%MvvXK`d`WCBbmX=6J0+{A@F?>6^ME(&<5CAkMGK-Ro zrI1AwT{MZ}wiUI(K|KxSH=5>lb%>_qZtf%oOg0`Vk>3K7amqZL?UNdX6jvL|VD?i_ z%hCk$os8oGO3sGWzxL}4DViEKxv|cU4n7Ft?(cFNSpkXU^q|rh**Fe&UX<>qk>Bm3wz(-q0%vaRr`T&Sg7P3Z-{~B!`PR|)`o8N!siPj~F*R|UFKbZGY%{~#kc3fGQ$CNkr)F9I$r%4$ zO(o63?M~svfvlT0iN~j|1q^BPsWDscAytHIMb$ zU-xE^+erBn;@{q#1W!zAJ}I?LCsCDpA@Ey)kJ(UE(M*)&B_SV|v8-)dzmK7*Dy>>p zkuy<%#KM@?WUc&=z*JVPsuN7Err|5{l0=@}OcpQX_;j6M)Nv^9tBpPJE}tPd+jeaF z1k8lba3SvO@3r_+2cNhC8~5C}0u59yQ7Q8Bt5Kfy4_!|0=aq2_(Pato zti~2-@URG?u1QX#@7VT!ZFOprBA7|g&EQ2cM zvGJB%xDn^P?AFi(({7*X!nS*9cb^rYJ@E@)>n6w6YZ4s}TgY{6 zlQAcRs}YiMDX5}>oA-T2;Y8Y8ttY25LV`K!z{0fArm zf&g9uI8guw=mXln@a5JTzF=$}p}Vvx{%iOmx(hLCpX$Ierc3iM?LoQ8W=OJvcS70u zHrD~`40KD@d*R>{Oy0z4`UOXhdX!IOwntg|IUA?tdMa#C4PkirEzZ=>3ub+;c-J?Z zbLD?j?%YHn1vZU(#BI;5+4D?Gmwju;vxECm?$~PB1??duF~icmtl7qR|HiwagAWfa z5eKN8Gu|1hhf+0!TyKmvWeB?@@X^;_w_dtO^R?vOVm&F^m?9y*$L&%rNhL>p#(!SM zNXoZs(e?h(@s~IBZb!wnabXGvRUYJ_Ce9$mIxE2$41$PzP8*aLk2FJ$E=0X29km1* zlUF@!k7!614{Y}r3oqDXuT_$hjKEl0q>NxHG`wU?Cm4*J={Avs`cGc?$$8 zRBG?QVN#fxY5Q7wA!OWnTBeXf<$f*)o8A#?gMH?)DPC>*=oCc3iFU*ZhwsLwksP{h zKWlFm3UktZ;_cybRs^9tTaWUzJkqMs&+H^scuRLnmMnXmZKB#_GH?a45V+A92R>6= z?h(J5+uaC(X#w$w_71OXOZ#R#O|`S-bVJN+A4zdfiu!ILL=VcH@9N>Sr+=1&a2l%W z{M}wLE&ORh2y2V<))(A16(jnsN3T+HEjgG9J~fVeNqTDXP2`Ki6A~3rkOt`-M@ z1rdj(q^YLyu1K{Gx4UmL#>LGf?5q`uof+Hi|I9zrsyY8H^kjqj zTuVG4THdhuM`+Ag%SQFzpTS}3GjZ?8g0h;9O=3r=b zVLKz$?jo3HZj*`nL54bwi%^}g%XIZiFby6yZ1BA)X^v0S*!k8|sr_6AuWg3YnJ++2 zFwHVS5d_^+i&65`Hkm>~e*(sB00&(K4BVf95%dc%&d2L1MVdyy672}0@Kr*RiD_oT zkDI!ptBSWI;k>95PakFI>`?becZ!PJTV@^PQ6D)wTheYEgrHkgWYN3M$Kd?0iV?|` z#1p!<Atl zF-*-alF6)PK9XXT7sDihu_k9F(+}mM8jOW<7*Om!;*7>=a^i%$l-YX~*C>Z_cEPX+ z)DU9hBx6IXDBDyTi`PiY(}twWx)ERI!u;>V4gx|-?(xd$C`e;Ln7Li2T;zwSM6vMR zs03}lduFXgMEmE>#)Cy8Zesd4pjY+~uTxO+=xd*rvRsuB$8%xHBT!Qz!+Yf>gKX5x zxLrK%1afFVYO>($Tkr@}wB~spRO9SZoyW37L}U5yulz|nXu|-p%Y@Zf-J*#$E$-NG znuFDZNbU^MgAAEsomKPrfwl?(jo1#HH2~>QnMn4*A>J{VPCDKDhdG)x+9MpGfB)W3 zCCNPL%gSP2mx1`Y3H@rQW^PTsvz!w?Kept7LdJC4_dTzJZfIP^LRBhSrMpnM z+E?){5@cLe`!`=cvx|Ea=crr6ajkClx7(iBBypu_0)lJ7h4XCg-z3YiQkTif5vijK zrsnhAPP*3HUTgM|Z}BUIw`jRl_imG^Y}1a7spsniU)Gy=6@2AlvrbtmMgrrf7T1ah zuq$P}EaQV2B8|TtcyniH@4K5$$jEN-b}VvO8Ky2wQ8A-tr7{*XRgd+kNehk8FAhb39xliE9Z{l1_HKQ<9p{q1M8U&j+o z{4Ajna!;@!ktQXBq8tEZEgYpOi$ixlpZFL+yYWDMC!2c0?%8-q!?b@9*J_^7YOq=5 zL`hY{#8=Ntwm&k*&uo?*7%c>j0dTLF-c#R$Y;6-xKU`9si4hMU`l(&q0CncE&@n{^ z?6R8R(e5l*P0|-@N4qy0lC=}2sA45|H(%>#MGIi|y(QYTz@IV_Cc=ho-U4D9-trPm zC^FN6meR;|BC$>+)``SAk@)`+iC@abzlem+zm*NDc32ojXRO)X$1WbJ=0M5idTRL^ zzPh^mS%OH_)(1rGY{%*$qS^ya1OfF4l0?A!>>oN<4zJ4Sa?WD+!{f_B^dq1roU&Va z{yF#sR_Qb9zvK&>FJvCqZr`4tHedRBwB3Y#zt>a?ux}lz4B6)*tH*W@vi1oGq{&}$ zM$F~C`FO|R)q|)93OZr|iW|usnPb0u0#XV| zm{^9;iG{ZFfQ+DhRS&SQ{!vGKbZ$nvxg`(_Rn(%Da})L}?LYuPZc$>4WC$fT+IItq z?Xa~5y{28vt8(0~QvH-nUpcO67Xrod2CLdd%BptJ9oAA_TfDaE=vJ0xL5sy~b_UST zZE+MUR!lUVq)75lfw&; zedP_7x@+|UBQ4ju`{yc@)q*EG1RF&*UBxCs+#L%K^f#vxYq2G7y`C;35rze^zAlnt zH>2BRiO|&zMl);^VCp&0q$^@Iu(~zixx%4DFZ3`%^XYzHxFqWvOSAIr$&b?yf%&D= z2CNxsm&P;SxfH3n=c?Cg?lOv}f#t=$PyG=K|%P#R{k{oEDkariJOQ-h|W4ms>~?I3SyNPzK#h;;%n7I9J5%$96u`|ZouWnI`uPK z(#_ucI;k{IS-EF+>sh>>F`K9zFKiaKc%6li%{Z`P{{EwKDQo8DyfsUaAe8B#kWYEo zId9wA81u;#v$-C2Z};^RA0+(K6VxM9sVRyA_k6mbk&di{55w}v#*Z!IcN7g!yc^)u zsg~(e;9UN@6~ILBN)1L`P>Bl2Yc~o#m&A!siUhSBZMz9{?Dfhbk;$)j;lEwRW)D(@ zlU|^_P4(E~C{=-~*p!Q2r7oqA!-m*P=o|>1QDgG z0YO7YL}?ZjRGJ8)QkCw$=sliW=AJv>AK&-Q+&kHmne4suuD17@H_!U5$J&;RH}*0G znSz$OL38;K;5BaM8fc*i3cSDqJ^H|VEb!bA=+pzA8vba*0&CU42ZN0!G_ZQ}+n-{< zw*$a>1JH{GRU#<7 zdxuJv4u1N#U`ZMPxfyq6~Mbg06 z11lM(K$Ru%-l2)AvzlTBym#A$k^&y-&1Xpg`L4hl@=~6Xy8&$ZyunHq=CCPjsX!Wd z?F7`R1JB98Z8E@e0!9w7DM~=j9$@OwGIQ%no;t8_Z7E9w82qC;N@TiG8@Oo)%u#`N zUcjfwjeAPKv@b9o28??H3p8LPz~7a$nyU*;1p$xEfqqAz&ItIN4op#jS9{jZ6W`In z->z&0zMR^*`A8>XH!j(QkQ9Jt(1T@q z%KEaDzNX7%c}h;P6f0QQGbz#WesJbd(eGo220oDEttB~0a;r?_k6Xs;xyGNUGC-%&%e!cR zWdT&<*NP>&qis4_NPvw_a?}LwiFPHJY}^tDD#X?nyUezW1Ksecco^va&7lkkJCSy~ zSy?!fa3~CfXS0>fTkej|aH3a~*XFmW`N=RZpFqDTxVPWYfDkjp%!5Y=cz~}NVxNH{ z#xcyw&p*H>Hp0&>)@ip-Y@m;^FT#8$-1MZ$$>6YHzbG&G$>5-nNRyLh2ru7IZ$FdG z^PjiT2>7p%s6aD>#m^t$KijkljqrmTp!9ToFjy?y&=7^yH!w8B>cDXrEDnv?{21zD z^-T;6O>jo=zYfG^YZ1OjOk9cNzuMZoGDG-BMTME5(b3V-sAxS@Xv9%8*4WtiXB{}4 z?q-B;WK2ku*Gb)wNR{6;5d9*3A_Bsq0zyOJKWp^z4m}=ahS==szYZZd?C)wrB2mAF zgYpRtMxXQwLt{~xpF{c`=;-+0hXx1#9UU3v>i2K`{Vywy+#M6R@4uBL|6|#oQz1BPvt^=RM8F9@UvflfF#Ok$O#=S8FIN9p-(O{Y|G6*3e=LjM z>noJ^ESy~Xxcbb{*Ffqm(85-#8;jkDqN>@i)OB1P~uBNJj zP=;^cwpB?{L0)c)tPD(AN>V~xOjJZzNDwN(&&LY^Zzc!j6<$FRP&Y{DXEyrz7Yc#` zpaMZ9n+ir_0#sVdqoab^oFbuOmuFL1*qR}ycWkJmvZ(z6+$w#2xv~9xmQ!Ar$^Hmd z(?xUq%HvAG-mT=62vB&YI!*iv-x+-FV+}W?m})F?Pr7opDsNoeXOPzOU1}ujO!c>& zmmtXwG)f7~c$%L|5=MBsPv5=$$Gv0I@Ys=lpBEPLeV_IS-!DJVAO;&@OC=QbIA-9G zCpbCS;r0MUBmO4=rEQw2dX57@%!SeJi{_`^iChkA(=z7QlxnG?20knDX@0jJ+g-@( zivBtqX)UEX2%|>JIo00_A8m?@qq4Bi4F^Seb5uru{Y&g6umal|B;rtMDfWSoX>8zAB!VETmq_)3z0QFYmzF$U zojGBQX&hb@lCzo{fICQ?{ea?8f;ZAG9HTeONILQ)V%ut%e6aJ|0Fc9lVvnm+x7lAU zr&5YsSB@FHC}Tbr$cUh(A;%V1KMbwhR%7CcD+($muiW~T91G)tPd*Qj`0yx3*nd?tAF(@ zBe*=ahRUz7LU!p;8pZNfa9m(neCWfeOg`2kE@-m3!Ez)SebYBH1Xi%PIHRHzt;45K zj;s%RtX^~)DVD}|;TK&cXST@u(g*3%Q8~RyPirNLh?i2nc2#!^XVs;vK@@}ATJ1E( zr}Vr$EEY7fkX8_du4nFdOZK&IoPg8}TRc@Q$n&z^)q(@`Psku7uq4itK1*St-QB?k zL+M5d$;b}8RXfbwbU4xKtU050Sd$GGLZ)cb@z>L{zMg=DF)BNSjd%~$e#l9f?Ggad zFoZ8xPdVA6Z|TEVCvOzIqacgjt71Ri*EsBM<&)K5{1p+EZ=!o^Eo5Wn)hkN}`P$UJ zx(Rmkwg&{u*N1j2YWka3VxD$Xb!y#xJ|8FALTlQXyHije(-tSri(g(nm9LX(A-r~G zgyP5G)lFmy?PZHmcs?smtXn6-RA)}}bRArz-?xBzJA>F`1(v?AOh4v|CnDE}`Sz7G z!NlNvPh(Px?ffl4tl4DUE~c;-yMFHRc{s4+y>^FZrtqFH9@+C8CMY?z#cNnOO)|Sf z7CXLYDL_Gs)nlDR}+}lhiT4rW?s49bUAB*N7 zqzEx&r5!P8u3@;W`;1W=LYS~Ic|I$L#?mFocOIoMVE1MdQEQWb+F!N2lAp?_?7!bW z-k~zFo#lnS^0@1W$wF=qP2Cy{{wullgLL>oA|MP%^H*{!{+Zl78^y6l_6Ux#Ml82) z7uig1;lpCdar#Q7E!KT-Vv53Y@3?Qug>WrZZAQao-(;M6)V zDSUmN_Ly1HRb-uk08IhSP6pzyKlXMisk$m;JGv`<_MKFc0YAD z_~m`~x2Q1Zl*|k*zvAnp?8lbLk0`G@uRl4VJ8Y8ha`@ZS7{4Nks&142`F_N3^Cb;y zCx`DEnYvxQ04k{xj-v!qh%?z>EKvGp%qhh6} z!SbtmtlE^1J@NM9l3VK*K14?(<__R_igtu%>j9zQ5$ejqi{7GkMDZEMc#yaCQy0NZ z1@!W5-oReleJcQn-ybY{r)D@^20GdfMO?!xOG@?ywY16oG0AB`;V3=!IExa@a0)zD zu1hnM(o>N1M+sILJik9%Qq0l;-zF$qN9Bj&qME2eJ7On`QVd7g)FgXYK%#`8GxM#5 zF~x!pET|Nwdd`Fw(zOMiS7fCaX_vt+{M2MjIXXc|a zf*>@LzM=*>eK}qQ^SH;B!fV@>+bqN9$kTGoX{YTZdf#YFy7uY4$EHx3WZV=Z$)q;f zX_sJjn@x9iX_6EM46&5g9S^7SRI~wF)3+Wy@3B{9cQMpq2WnhC3Fj@cwn*kN3LTU> zcDiI-jairCR3Ck&^`$yyoC%V;%Z@L@mB6LTNCEf-TUh&SpPhUQ!)}t%bJbY4CQ(cI z$(GulFcI;r9;WTlX4yVyZ5PclO|e+E0#)CuPOE6FzvrE%RzG~IO=+FH&>?@&#sY=L zrMk>Eenu(H*Bz@HvDZ=vG_9tKfRwzIXf_YRz;VlLMsnjjagc3vjX%89Y#H$Hl&>7W_#9TPRp`yYJ1MZu*{-l%>k_X&Jv z_~guu$asEaX{yxPd^LKeNvo#kN$00B^Jft~jpo~GK`*Mo?MNKO(m}7Z!~3DuQ;wvW zLi6lHvn6rX?2b>b93Csk3r8-zG&N{ueH`g~i^{M#65n`Z^;18D#E`FT5{ zbzU~V2P8+mqZ;=cYbm>q{n>D7$=!jFv+L2r4fybTy_o)d$wEgbIbx!IN|{lctO-U^ z1f@M$*px6&LMkWjjf&{h7et)&RD)())5YVQ`Bwn zfm*5#FxV|=YOW(gDaQVAM4Ecxq#fD_Tx2ikU^q3d?^lsCcHU6Q8I(p^$J3=P3>h4e zef)$EXl-KJlZztwTy;z)v9Ae*?Y^s)f9AaTDR`A{h$DI3+BFS$olRGS$AwGo2kkrb_}42r19h z>fE*i1ZBjv%OI6wH#wbYqNq}GDe(?FV$`y1yYuc2)o|ikix#Pio>Jt!HwJw3P3II+ zrVx)H*JKfj9y@@a+O=!A%yR5ehdIotg2>pwnFcz7k*MP5A1Nwss_)vNG%>Z`M%LAa zdCo99mDx(CduNni+NUs0)-Iwo**6`fyM)Odh364r^&J>n#TOTC%3EOjf-2EBL3zY2 zTR-k^Z9GWJOH|W0H7)Fvysr~=-I+1p+NRg9#E&)Vv?F&IYxJgLUM$LM_GtT}?d%gaWN_`P`=xyoX#fWoYsUnV4mHY`)T&ut16QDCwno^AILLPfm zVK^9m>gF;lvKZcl1VzD4y1-d$%}@x4VGOFF&8L%+Q{t8<&jsp(bhA%CT!Dx$w=Q0m z3BI#}@#EkqP?`9LpT8=8_-cSG?}&RK9rOJQ1XL-^Fa?z|ECh8L#V@QamOUyILLMHj zT%XT*@GR=XWsP#vZHLR`(nJ`;JOW!v!*~R?&EC^Y5UB|&kD2D(BNn%uD?DtYnLj|$ zH4PVZrBfwz(utZO7Eth;Q7OLX`Sb1{8(w`ocNRo}s2g$`#UgrSaqLz#(*X-N%4i&@ zWO(UN^fnDskoY~gmjIfFARp0_2&2VYE#7}59v%-f90G}wApTZ=jC;%S1={q-y3=i1 znv(Ow5HNeD41EYHO&n)v*f0rhG~1ixV_ZkLka z${()$`ClM^PTmV5%c8er+1KfR3|iTmgDr+525 z${*R9-J9~~{>$27YT|%T7Vu^1y?-Dcq&^B#np6_^;CQLxe%)6~c!cGcoZhj(fD7SBuGNBGPDcNQJqD=Zj z#^EnTc{5~*9Kwr|7q|8lzqDASs=86l1pMfBCyu?Wc6)5%oH%AjEAMs?z*8-?s!l_@hzTU!Dda*)cCgfN)^iBCAzT;il>#zy445@Mb+UD-9 zwseA<``bb-WgwLlpEJ* zb$oiD`sgF4ja^YY^->wYX%~u1yHUjWBzQtdIB{uctU2@|?M$q8X#J2E#CPw)Ii;9N zKIy%x(zdE>UZ1AZA`g#JQMyV4g7M(eMkr!LK3fr-0&Q%QgQKb)H2f&Dt%+JX{+8+5 zH*y@%>foY;R7mOEh@)CO-%rFe%oNq=V*ibp7~>c1E~YZH_4sRfQ@6Q#4O%G7{`$=5>YfFp`BE$cu&z_U9p87XbN;YQ}*-O%*@*oW>|LJ#fI$Akcq4KJV8etO5Zf z@PZ#XEC?5B$T6nx)K8>OqVJ4{_e+PQ@@O06xo7!!Y;kn$!tQ5LlQB7?$AZfTqzI)t zhijUiVA~z2>7_E6E;cHrO3gI2YoXFL(7?Ib*w z|LRBWm%9>fWK<#k;nH-bJ<8ec668^|Q0T=yTlaSessZEJ<@c zWPPkk!pi>J;q1fwYBkUzA?*vNLf1qG4vz5^9hF^}uBs`$nyg2OTu9Z6bjkg&B<4+X9>f|>Pf zT9Q|#Dn)Gt5iB<05CxV)XnwtWuF;F)4P&vBMCh2z=6#-bgme>zzl%v3?w1&qQk><5 z`FN$fpZ1dT=Ok(d@sM@}c|vmJ+6HO_uCPTQ(uW;<6wH(p+)G@}-H=P-e_}(oVa%Ni zP)*!2$L4V(QqN1pAhx4j(#=NaWU;)y??QN5jaKK*ht{-imyws0$ZnF&?--Xo=WFfe z+b3NKZ&B6Ml#~CB;ld7TzB^0Gs z5fDT}?@~lW;3!3@iZp2=`-bzJ^E~f|_q^jB_rtwkvPZ_q-aB*6wN|n+_Z+|VpJo=O z+ByV%kUnU;4z!xZw3ZDqK=N$lumEFdUlPoZzy*+CQw8{g03c#%9w-g?LCJ)RJ zf-j-I#3Q~_d4Zo(KdSiV?=mmN@a~o$0k(bx#&m}}gI?Z|T8c(&4XH15s#1y2@3|b? zr6OPZG=PsTUnq!!3B!Xr$%7sRV8X1Dg3ODMj&>IDwU+><9KU670t=zQsQpkZ%YLyN3W)5$22+RIAubxVk+t-!q zBE(AkU_B9#nRg^tQ&C{qclRCo;{$;wNlHNbiPb`pm1LgzH@3hgW%QZCM7usPcN&;B zpGsv~zXu04(tsHc;H}M*bUk3gab+$9nDt2}NdUvA#u`;hf_b3Bj?A9x#59Ua~^2HaS@TExJYB&3wA7W5Xf<`Tsv7;JrL3;0dt6HweFFK{fui^3 z@8+&+i!=SJFZ$INvpu28pO@OAPS4=I=_Rn@b&bMbgFk6A6KS{`P); zSN{GM@t%HxegU3-BqY{O3n_Qb%h$~>C_w&CduwZ53*Uf1g0Cyl0(TNc_n_kC<)(W= z-PF_st8v1_7>!d`H^CZfX&u)-p@YNfV0BE5jE(`m$yM4u01_xgE_x1L4cXf8Ox3#u3(_X!7dePYMyuPmXS+J; zveJ^`qQZhFkMr|#bEw&m9%lXi;C|-4yLU3u(^6BCZzm-t#K%!$W1^!XBf`nI!a_rS zyLlrx==!z50RO8bKVP3Km%Y6_J=}?It}X=UUoTyBy5M-;;oR9X_I9?X@xRzuTb;7B zFgG(b!C{S!Fop&v_4Rahv`=Uq*VI6(tEs9eD=8|-%gM?dla@kBA|=Gdj*5y13keDw z;pgK;z@ZLrNa--x96mxpPypn|e$>2@+8hbxQ*i34 z%xR5{q z?&U|zrmL{GY~K08pJO0)33H8+8P4(yWkJo`HJimpZg_ zD;YTZC^7neO48j-le_!m>c##->wB*^iZm`Inx;jenA!L!6ZNN-pX?WMtu+aF*GPR7|>;E0H5k+#!Ou`puddW_J~ z>#ZHJ&nReTK52omD47;0wd*(m;~L3xwXL2YjGj_QXa{PO?LQ6Q0vGJUKtfo_G!Ob1T>bj7l?X=PLfM6xrmFI|a>{H56vNJiw)4mnS z#H(HDkzEK8G1ZXH@LnpitMSANDOfUDWrxz!fJL4JBYqJctlZ!t6AK(3vvaly zoch9V0uhBMlq9a2NH_VC!&w@*jXE!L{}EpTbFyj-e^5+Sk3hAu63U;ym!9b1sBj-H z6?}2Cv55QKxMgS4gzfKfAZPQd(emIzTa`vdX)qSW{=yQ(bf1ORW)_idS5l)jEpnI zyxl%nPZ;IOIbsB{grRRMzP%ZL{XO0cC<`iZhUGG`Xqb?FCIeK}?2rleDRrg4Z8QZH z)Rww%PRIX3(#EPE3u{3U^vah%rcUe^6};sJn;tXZq6S~RA2an_I>-aLw~(LBz56lC zy>g3}^MoU@PQk`HRFJ4#=Cs6KL6pq(86J0MoOa9ASeXM-gz#e4 z@}-?vJ*soO^9v^WUjXd4vl_2QrC#k&YmwlF$0t!-kKS7_1-w)^rBg+JNOyG_#L#%# zK*ZBXh2Mi-mC4EIxijFv5sDbW#u_~4m*cC3%y$W}R(|qw z&W$`nHA!$hK7{;b7^;%R(H9|$6-L_?oxRxj?5kt(4d|V2vTVW|DR=hAO*Fxki8|5` zv|LoQ%4@MBri-Dp%<$RH2q~=lzJ+hJnB+xIs7VZ522RD=`RZq8{F?u=zl}*#4`-psTs6(ZwJR+XiYG_!4-C z?~;kP5wLW|dxS@lYfW1*hJ_#Dwu&gq9Pl{UT)Y>b7_7;hDx;Y^Mq&?HIWh|qXN@LV z$>qI}jNu7sYi1P<89{ePZ4nF2xWRI{W(YNUY&zv9yLD4YtB%z>WzO4Vj>jckHL(veA^3fqVKK&YRCRR}rfWe^D zprBU4{z~d{xzY}qgIs2w%1%j7bfi^Pa5vMJ_qs_vWK*=@#T(NSB{8wbIvr0} z#-~|C;*RRE@bAwkvLJ0YFIQET;j_NbEV7FD_+AgDl2CDD;iaYQ`K9YUf}JUsLbo&DlQ+Z!#}fM=PN7l7go*J#2Q0gkde;*>B7uT4oVAgYcu_Sr@}Ur}}<) zvRq*7eM-Te?Ast)k$R+ow?=9B`%>v;>5%bOY(vAmf0RMEHM6=HZ6gSRrW_Hm=LuQu z@W?j$o`yGy<{dB`STK5Q_%Uw#RtTo)`$Qejiyv<la&n$%}gB7T&JQ$b@-2EFSLA*F6hO4g+x@OjD zb2NfOuCRmaNf%b!wkpgOGxL>X3}bC*jyC@iCA>nTpg&B*@c6>(4PenW#;^5F9WkjtbARdKe!n za;JE##6ba1#cvQ;l%j;XnlN?cZySS`UmqJ$jJ%V4%|Ke2=wD7~x_MtDY&EVTfh zyZ#3623L7mmPM3K@2Dc)ODyrXtqN~$$K2G&>WmSuT*zS)wXT_;g5}Dg!6LcTRBY&6 zTn<6_G9sh-+|P?SW+?Vcd;}EdFm)z=wHb^R@(dwF%4k$p$0hwNI%2{P{)ucA_tsW7 zQ7kS+TPT_&rch+YDN~=)Z7%V#!oU>cS;Z{3_t8PgUxQPU1CWD}{MfJ@s7SnAwK)Ab zv-pH(>m(S<9Zx|=BcFRtw>^UDYjtFskry!{C$+j&l-$l%z_#FvEVeO6@;VM{~BU5rs7_7jW)ab_&o8Slbn}sOV+ERO!l= zhJN=n?}k*XtA!F|kYs7{)F{>O+{cIpuV=LneniW!RxhvAK%}i?_&3LUSt@o{dU3qE zZnP9J@|cTRdA()MN+&V^>EFrY@g-WJe9YgJ_e@QnU%Mc)tzWHF?3IB2Q1`5O3HgdI z)#jF8TA*49YSN{(VzOmwug$+butZJ_BHnY`CqOV#MR1?M?4ycJns1y8w*p7(&i&!T zc?$w=AI-gP{q$XwcQIMHTq(`+SmCb7(ED3EKvl2S<*4!63BShB3gt+pYAECewk8UJ z2%LyJ@!?DE+LH(#@!H9pvy~KyTjsnp&>siSUF(?ThPot+>emS@nI5m7Lu!IvcFfi3fu|OBy1n7(Y4wpn7zN%b8fv;C1`R@ zACiAcLI?CPb7)^8F;8a9akcYX3U42qVc6naBZADks4-(L^?c&t4{c-2$g1D9>E*Vc z2LXtmyDFjf^-L;lY@qe?s}ZkSS(NdE6c!sQYA;}l=`-dzAT|>vXGLKDrI5y9Jb>cf zyvTB{&WL|(HAWlP47sq-vi=?ylQ;WOLZ=VYh7zfw=to`J9CzV5##$ANPt5%$U47)& zsqnxj6BnSha_!3AsewgBxtK(ZDduWbf|-SsIT*}HWEhDIBavYwGXHyt44v=%E0K}^ z58s(8J7&szyK2sb&~l7b$}&T!ol+oR(Wd-+fWk;AaVD*1HG`Df9dRV|n4U@DFR_B= zE*+bL1%pLyFP(5YUbg0(ERUTGL^%T7hdjjgGHlh-3XBdR&eiuh#jz#0rwv$eEG4 zNE3Fm6K{xlmRBSBOK!x?4`xpY9GeOl+_aW1yu|kaDn&-Ui(*RRGH4@6l8Ig9 zPR!KcG{(%QlK#Mdmy*Y&Bn2h0b1Bu1q2j?9W-g`QN4k2Fh*=TjusVkTn3*@Es9UOK zAtyFci$eXBX62nn0fDVtW)!pR-M|8(iX($o(To*wK~PEsYQXw;&>1f zIYT~?E49*jspyH#>9#^6YIP6vR~)du$SUMOTZ9KXq!m3ulTI(Q{!$##0gcZPX{+jV zf9kEMX#SwPLwDtNb_7z`Pp?wjcbDPbr9%hXl6=~ku-?wxr$~FG@N}7 zaVCm#t&&n&YtJXnYOz{Nz2$ZhKc}iPd@0&S;jL~Stotuyle%7^__VLOr*@o)wLeg8 ztyh&(SuC74GI6%2ESy)YrdRbGmz+?fCx*!su^`esCd5*0&SHPg`}^Co%-+2UwINNk zNtg7{7SzeS{-V*#=Kv*7YTY5Nk>bK^^GYWA)JxlXP`5vX0A;nKx7F}$Knh#!x7WaPBBx@=I_?hzm40qNUV?}_{UdSwP0)2_vlvl{p0QZ@BMtJ-+j{_b13m5P0yl34SuD`c51gI` z=jTBg8L*)N92fvko&-OB1e21$o*s})29Ze6*%=fRd=5!rv7m{`s)YquT@7YrtSKpd z;@}tofF%!)F#uRuSy_{mT(-CWc<9ilfPhco;Y(EN5{tTK^hq%^dkrnfkA;31{o0% zcz_m4h%i8sedt~k0yJJHV~~iiEQ|mHq{;dN#JWv0dI$xfrKW-QR>xuyc)S``QwxvB zsv>aeSR6(j`r*-7O@bDlfYU~NyO5Bz5T9QOPDJx>wxF2-(vQIiB49A#;o)lG8fx?q zUkp}PS9e_p4u^&~(4mnu1~~#v3zh$_fk+AU4xt7ysB{`)T_f3x9>y?0LM#1q3xPpD z)Y3xLzHUd&n;wXXAO~TvYU=A-`p#%)_up9q1AlObGMp%Xt@l4F4t0qPqF|gTq4cm2 zZwkaNziujsU=~6lGw2~MboznsZ_&|@&Y*|-(Ss0X&UgfBH%jgS(BDar_pA?pxGgmK;rJ99*{`kDMy0X0VY4PL2hxhYy?`CJ-PQQ8m z>gCk$lP{h>n;3sOHaha#@X+9sf&RY7j~@0u=(&HdyQ}l=o!hrMZnodJ-gd3ErMao` zYD0ZpZB2DmWyO`ttn#u;7fUac6c?Q@JXdfwKQA{YJL}Bp%u^ZZX(v-tl9Lh>;^SgZ z#4wLXA3J*F@S&*4i1349jL?waAUZAZK!88h&-Yh~kGB`ubN@b%z3zM5TzBu<>Ei6P z!_mRs&eq1-%F@D|L^LxsF*Y(Z(AOjA>S*J&G&OKob&MKXRYh3|rMO*Tn>>a*$i$QN=`{VnUKwF%GAyi!3etuz8v1=q`gpes4K5)9(&GXw~j*y{g1kd2?@Qa7(9_hK2Gb zt>Z8AQ6q+csC-AMBNk{ILI*yeePJvc6(@!Yw1&2V3VZO-(2I zntF1=T_a{FiiJmIadqd}M6z;t zj-v>dkn%}wsL2$~LBGjVotI^kCuOt2^Jq8n-r6fn4FdZ`wnw@ z9O86V&dGdc{9icdCY{oeD`TzWT$J_==hSta^HRRTd}|%%uH-*qPJP%}0-@Y7mLP*2 zd7`9;&Ha5$78Q>njJ$iOJ5U0D{^^~V_S*Dfzy-2Y5eME!px&1%r|X-d$&>pmeJt^y zSaYhfoO(&;QR&7bTi)0U@kj_bv9?+DJrd&I;>fHS6YZti+uIGa#xJ>*`)hj?=q%ru43B(gxEvk;f1~}NXI1yw^X2Y}x-_)N zje~7ZYb-L4y+1;UW;?jq65+-714n&8l^cLTIHJDds1L<56BZwv7#j~uI++-6DWxhU znvj-}c_uq8*$ye8rzdd%uPl<9ETSw_d;x8Uv@0)&$CT)x#Nvr49YvXXeVB})v5hVw zLmY?4@OLCzRTycf48bhbmC9i}dJ1a8Lp)fk$8jV%yzJy8iWIM^$Mfi2iW&y~+7hiH zy>z!(-L#0*7Cr3Z;_BkX)o}=ur7vokEWt$)sWGWO9elP(L7Ct+svtaS5uo7QB2sU{ z6_c&(eSOPIxlR~Nn|#l<+_alR*eG~s zYYC@bwv9E6y|=i1laMfbnV|H}0GH~xw$qho2wAB9hPbAqT5^Nv33%v2Z|U1cwny!^ zXqB!#_yF=K9^UiR zVNcRSyvh0SccYuUhX!X8>getE`+pImHD&sYcN(Z1yZ@QF@VKE)n~SDXzxZ}okmx{b zZsDD8?QDfdX)Wydct9wTEThBWSTijYQH<;Wh-}>@ISXmskrO;&U#LO86WgyU~_9AcMOX__V=Ejr?OY`_>d8mrS zm(z`?o9(1E*i)?<=47(qHf@u`E8}$)MQ_DcrNb9r6i@>uddN{hgT0+|FUGd2NA$#6X!H$gCN`w!#IBZMDCQ6SxlISl!1htqy!#E;?CwK=@b+S*+_}NvQxR03KW#ZPsu9l z=vY<7S}Eu$^1z!^Z48tyRKr9S2^d6rKTH;*dmW}KrvV>Gm*!CyyR zX`8Tj0IS+U1fQ*NnU7=b)Ma+5(>Xca&)IFVw?V~2E?UvnybigVSu65$l%Oqb?}kO ziq_&P*Ji)qmV!Ngm5;e1`bQ}4!*fsD(cBs|{dTNpmz09LlUvDxThFG&J~U+s`=qB> zk$&BARekRxztku^TQY|2el~f3!`^(MSEGdv&SL1Rjq+LTc8d0@t+nivm))g%?etX` zedmG?74$xjnw#iuH4hta*ZWxXywqL$*wc${9B*3d3O8M^Vgd^bM$F4bLf@Jj{5odB z5N61)nBk$K2#bq{oq(E~_~f)CZZ%~C(=4nKA~6kyP&$*Xtj3+53=>z7G*wq7k(7`) z6R~_|0g{DNs#L|;AjKQbiW)#INNSvn2`V2Z#WLgW=C1s|FDo5a^NnGlI#q+QlFM^4* z_@1$xp1Vqi`P8;OvlKoK6$RNKAuHT|>#P|Q!&ccIb;hRN$MNW;>jF`CG%t{cfH2;%o-+7ka8@&+VI)*0kyFTI-9mD{h)C z>~6Cj8Xb##KY1Z$=xXoQ)r>jA=H&frA=g$KUIlV~?>#p{>hBj)8`bO|QO!1*%Z+2- u#<6eX*thXP=704;W}{I4BMRk4kNWrPQ8$i#|G&pRm;kU5Qh%qA()$+?)l3Kg literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/32.gif b/im-uniapp/static/emoji/32.gif new file mode 100644 index 0000000000000000000000000000000000000000..ca8639919bf58f4a2d5c4648649313afe2cd6b8e GIT binary patch literal 11106 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/34.gif b/im-uniapp/static/emoji/34.gif new file mode 100644 index 0000000000000000000000000000000000000000..2a3c006f043afc25006e829e3b4c32a5149ec19f GIT binary patch literal 9212 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/35.gif b/im-uniapp/static/emoji/35.gif new file mode 100644 index 0000000000000000000000000000000000000000..7fc7f947305b42ef43684db9388d9388c40d4406 GIT binary patch literal 8356 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/38.gif b/im-uniapp/static/emoji/38.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6b4e09c00626445b959e6832cf7721c34d09df6 GIT binary patch literal 11035 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 Y^&_Qzbg3U*>PMIQaq;_qfAO>X5BNYay8r+H literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/40.gif b/im-uniapp/static/emoji/40.gif new file mode 100644 index 0000000000000000000000000000000000000000..c5d7a8b6d41cee0e9e6c838329ac481e0773dcec GIT binary patch literal 18717 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/41.gif b/im-uniapp/static/emoji/41.gif new file mode 100644 index 0000000000000000000000000000000000000000..cf1c0a1ca41f8052dc2d851d9ebeea7109432bf7 GIT binary patch literal 5518 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 TmGw_${Zm>0RM!8*%-Z&U@1>l) literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/42.gif b/im-uniapp/static/emoji/42.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc87f9e8479639fece39593d5eeb3164421d3ea6 GIT binary patch literal 17447 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 O^o0|B`M07kyZ!;V(pXpk literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/43.gif b/im-uniapp/static/emoji/43.gif new file mode 100644 index 0000000000000000000000000000000000000000..46153bc7a7b1c28d1a140726feae03de8e8210ab GIT binary patch literal 9220 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/44.gif b/im-uniapp/static/emoji/44.gif new file mode 100644 index 0000000000000000000000000000000000000000..dd451188084a43aba23bb91365e99f9c7e519530 GIT binary patch literal 7463 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 MO4VKm&jM!t7ah$Z9{>OV literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/45.gif b/im-uniapp/static/emoji/45.gif new file mode 100644 index 0000000000000000000000000000000000000000..a3e86d66de7097bb4d92608eac8aca10a3e63ad9 GIT binary patch literal 14311 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 A5&!@I literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/46.gif b/im-uniapp/static/emoji/46.gif new file mode 100644 index 0000000000000000000000000000000000000000..b3fb6814645b3a9e33812d0440b1b00b316d128e GIT binary patch literal 13457 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

v24flOZ{|_@u-3-mvwMY8tnCgk zMopRGh~&yv^ELftV=PF&8bq2Sf^Z?|s`_Y?;j<3l+n5Ly53Ea@ylHCa-yAIEsExHg zOFnLOM+#T;d)Nl=4H1`s(_%2iAi0 z_oja~5o5_4rCbfz%vT8vlBNSmK1}38_u2UuvrxHkmKd`6vpE+BOXGl`3`*GZQ1DHZ z0a-C9eV@T_Y0X;waw z<=S4LHNQBwo}|qH3Q~QT97?uN2HnMOgvq)aA-{g#ky)%5tT-AWP@H^YaW#bq=Z_SM zqv@KxxyTx#h~nt4ef%=9=3{Y@kyK&R88D3N>C}=Rwb6_!o`xm|!&IIKhvEn6VEy1l zemV<&(!7^@o_blYvE9|Sd;jMBat|0$TlF4}%Wh?o;B8+7q=@f<-iJ)pg02`RBu=xN zNaN!>`{sg*nA#KWmBokc8)zuYO_65{fu=%S-%TQ`y!*1lm&G8x;H_i57V<8kSY5xF zEj4X4U-YKm?m<~ME~}|q-&Qj|{b2!?{Zm`~IQ?>}XpQq9*W%Ho+pV3KuU8vks?{Le z#jpsM=yAGeW9^bSeE-LiaU4UOy~~RpWYzCvEW`UU+$a!C2M~?=b+2@5$^x* z5)FVbL_;6GBCZ5hpqAD8FNuc6Y@8u?^g*?Q&G_tanX#9Y#=k;wv2OxbF4FVIs7@O4 zD!B8%`CDP(`MtxD${e?a?yQU}Y+0vHPTfed%vWm=4cwTpRxe0|bSX)NhV@2D1&Pfr5 zosnmXLDow;;^q=}97H2Xcitx-Bm7IEXUqUH`+ZppvyMDr;GRcj&&XG;v;hPKTYrKQ zAu!^&?9uv_zVi}|{PqH-_UXtvHk@cehOWgwJpAaFM1za;yYI5sij8+P6r6OP)g$PK z2zyIET$GX3eaD*(SEjR@6()#pvptAUM{q^k-}i}c`wDV}zYw}OR$puI5Pc^Q_Y0x@ zN!kQ+R{;pGNBDYR50l3rV(Bd4fVN(6{qwpRU)sEOPVps#1zjwu`cHLgFWoLXNm6TI zAHg9-y(6%2Kl|!?b>owTq8boSNeq>In~o-#!h|4zoztdhIT|q^x8d};neIBD(a%a0 z7pZuOu*!2OKzNl!ygFjimfVVL=)jAx5rZNxi*o*lB#=+!hY)~9J`9I&XZle_iGvId z-mUl>JF9Y*X^C7FC68FiB zAp_3CIIvk1b%K9sv2;9AU3-k70Bovcensx&30M<-r(_#|%KOe#uh~{x2(j5TpGw1k ztHwjcox-dE>%~H0SaRO?-)}5L3Z7m68PYDjrr$X)(Oo;&)GKRwkZWJDZ3 zaA3=&6C8jzBg(pYNnqLc=+N9S@Qk&cAyH536*+`DI`Aaea<8h3=>q%* z(IcR^pyOOxzp2`o?X|O8h9%#Xb>2*e$8XiaJ*Y z(G1yv7zU^2hV<2yN9}!=G-B0#4TT=17fn?_$O!kex9z5!fuAx*b=C#LB(i(=+Bq?!us;E_*iYkOxyKX%iIXIb zXg0A9nqT~blri0d7R>=yScU4dG@xK37>4_kqkXk(XTKcK@TK$jCoYJ?j~4>uKo9(O z<*CO>W`X!3DJ6NtBXKlUWLPx!L#d5@6<)|v3sE4c7#MIEP-jhb)uT&q&q@W_7m!9e zyDq?+WFV9FFg|L}JV*av-xF-Nb~_SZL~mLLi+8*urgwOj=*Xd;f>2#p7(X(tT`lKLqmOprl~gBVu`m4qYZe30TKP?9DgRPg;_EK zo#*Ijr5eW{Myy4T}4NXIE1 z*6DeU=9~cii=*e0;RP}|K6|o5KCMj%lMk9URnhXb!@u!>Bn=u-Hu%@wsx|i9YtT^=pV{vHar?F}EO3tli%PkAYEMzf~%YD3N;Fzsm^ov?4Y^UWz zoA~8}`_4lARH__Nh*bs!QNID$_ek7>?F3~tU)oqkLe_ObFjXV~)mED3ice9~d5qcG zRoUW2gFej9eOck-0d8-UJCg0<*v!5-8J??-wThM?bp=iCk}GJd983(gevAL>lM8|* Y9M#HR%}>9*pDV_HJpf$8;ef^e3w)MQsQ>@~ literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/47.gif b/im-uniapp/static/emoji/47.gif new file mode 100644 index 0000000000000000000000000000000000000000..d24bdbb180549f5746c15b68379491b761e88ecc GIT binary patch literal 13299 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/49.gif b/im-uniapp/static/emoji/49.gif new file mode 100644 index 0000000000000000000000000000000000000000..19a7e72bf25c84fbd39e11de0b14998edcf5ab91 GIT binary patch literal 10110 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/50.gif b/im-uniapp/static/emoji/50.gif new file mode 100644 index 0000000000000000000000000000000000000000..51e40937cc82d51cb6fb1617d8d5bf67df86fb44 GIT binary patch literal 12480 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 CWG0jV literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/51.gif b/im-uniapp/static/emoji/51.gif new file mode 100644 index 0000000000000000000000000000000000000000..9bf82a8a3a6ce5ee451a3c6ab475ae9b1c05fdd4 GIT binary patch literal 12176 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/53.gif b/im-uniapp/static/emoji/53.gif new file mode 100644 index 0000000000000000000000000000000000000000..0f584b5bed68988238fad4019c43a7d36dcd75c4 GIT binary patch literal 13979 zcmeI2XH-+^x5iH@U0OiELI_2`5CWkKm_R6@8JdV921rm)AQTl4ozQ#lMGU=#Dk4oy zXagdOh>D5|ii03(>|o=D8E5|Yu65^rxSu&ID>>(6zx#dPoV}j)y3>~hYudCsHi-A=#VK=qO`QMsfl&|e0gzEaYI96Nl9sOamj@X%*x6t zkAnxRt831mKhI({cXV9T*HAAnFR!Vo@u5&^Yipg!`x@#S>gwvejvOf{C~RwO^YZj` zc69Xh@o8^w&(F_qX=!nFcFsso&&`O&DJB)preYj<}KJ>bNb;h|F}PsT8Qi#T=C%g+34M7V>kZDMQ;hs|F7 z@F6amLBcAnzJI@VXX@*xPc&y+qWX>x&8(rpA!|*=p@D&G*REOYmjAZ8>R^POp1yN) zV#3+n*i=E%(Nu5l=~H{%-D~snlecbtTzdKC&Fitz(a%@SN_%=Ix`~dmQv-!!!x??ozrN-*LNJ&!#iM>dP^3*s00B*DDdGG)C zp|CFGVBz9yP9R%oW3UhqFPeAjfIS&W^a}IheH!l#r+fJvr-dQCX-EA6O;B(9uAz{A zR1=h&u07VCZcaPqXB`CMYjz zkT=bUcfN636NUUUB<#2eim>qoa$`>Opb#2T7lYIA!D?wC_4F`WI=Xs#T4KSS4BYzzz-fSUMUn3`?<*&JTS0<=qVPSM5P0iD%Ph(EwFhLn%u%Hl^pr8|f|BB>eL196m$Aaicb7wuIs+(URHRyDx+Mn_E_C}<@ z&@it+9~y~hg5qVs`1w(dh&Vk%L#)0T&Jt^;rDb7mU_sP1*TU%;=wo&DvEPe|-P;b#3*_=T9F$ynn}i z`{wn^tL3Gai+{XWc>ZjD?&*`qvyUD=n7M!N?)06h+mp9$PTUy3J~sOM$nenM0Ed0; z>XrV!-k$EuU6(p9cC@#(wluSv8XM~CYHO;iDl3>5%FmaTmJ}Bi7UbvU=45AOW~8U3 zrX(jNCd9|Z#zZrIJ9jqfOk_m(=~E}eLPLV-L4g4$j{Ex^JL*fL`gnU$j(8sSICRkc zfZKjo7iT9&@;(Q9J6ju)wUwm>(cFxH-)m}OyvNAUKwnQ+2dAxt)x>C^cdM(Z?ov@! zQbZ{r<#+DbzD-V6W~;OmLQ+CpOmvHgu#lhtKOYNy#axY3Ui6S=l+cdHDr}Ma3nhW#`j_;6dg2Kuv93eN{|75Lf5!=Gu;T zg~-Vrt*c@+0X9x}&SjOMAtZVfNh0rWE@Jk^*0Qw}^oNlUv6+XXI8x26erk>V!2JjJ zQLvTQZ)A)$+;5(L!O?%PV-5ZF?b-_Z6gSIkU#`jVN0HpYtQ$v4&*F$s7bmW4UMP%m zY1*WVh-*wVwMK!w{?^ zST$W@B%V)3%qq@Zjxw5xlG+vB7{5`>0$zdiI zf1uGm8y@ateoq7u3AwSNE#3KObi@}bPU&vsXM3c5XORiX5?i8d#VeMFKG#*mp61s2 zD;4I{?oT@2Dlu%UChXF_8XuRk+Z=_-I_6&S+Cd8Do zy>-91iAzAU!Gbvc1&$;EpPh~%*^y|jvfGoDghZ^_9O$;~Otz>>^rYh!doB`{p>&f- zkn-Q@WFxAoO5s3c#pr;rUK_cdPc4Z#B;;ZT8bNq^*bFwxz=%0c)% zxPO4bQDV`JTf{(Y0cA#HbThx*oNv>m47Ri~<2-k{t*Ptl0RI*`yMV91qZd@xQ@}lO z5p#l#QmhxI*F)~wY-?^2&jXY}6C(lvE$4O`5iZqss>bt4Y;Tl=JyA(jg*}{1J=Q*9 zR1ws8=_lAC)pc}irT2pAD@Iv67|%fON0zklKedrRM zV;vf;-+v=y+~UhuS*8YRwQ{qG~HQe^frMtD{ z3cW+VyHUN z?+2~_SY2#g|M~sPZzSX|;qV2}aDWfc05E?Fhwp}P#KDt*l;i}SaKt31=H~6sDojYV zA-lP1;h733B$DK0lUkIuKgpt8U!h&BO+`Vh3f<$HQk2t>Qg%V*Vpr9WK4dIF7l*az z&%B!Jbm8LI#qs;qut%5o80$?ornuwVV1^HtAIb#GzQ&@B<({Rpa>-gVChrX2J${=O z%}`L>nT-ZUbQ=m-?t*;6Hfh|ps)XS!)l-)Y=TWg%JL?5qoe+ZCgd`l&gp&tCuEP-M zuJ)N=K-jjxD`t9}DB?bX(1Y;rc&Kb<5vbEAtb9;$UcqKCA^Vwl*wW-yD=lb)|E0J) zguJ}Y5AM$a+a~jN=-4Wo|6qmJy4k~55ls^?t)M&V-KqpXPw68UN}RiVAX}Tv*{e10 z%^M+T`a4XG_>9}t1Of)x{uRQI%SY=4T(3moW#Xg@Jo$yC*>m9UddPxG{?x6zr9u`R zB!-hw+iV+L%6&sD{V-M;$7DM=i7~1e`l}Y8Oej;yvsY;^xb}&Oa;tlFc;$=n9A%%3 zG7)yXu=;*C5445%&+j?seF4>XW9dl@#m^e;1CjIy&JZGTLOCV>TGPz_L@+3Rj!R6}482{e}j*%_w?0f|%G0E7%Jo;e+_<+&eE>?ZaL38P3c8Wm}M1wvytnzAaqole=6N~u7B!Uep=$(9BywruCsR6acF-B^L5u;)N5_i3iBid)M$dC;Gqa>iKAhHt;>&z~e2cDHr1~rPV zGNnE@lWqL5wI9sBEuWzf2#{-vj}E=+j*lEtuNsf+U%u#*W0-8LDrVGDuc`WNX#)MU zjLSLkj47GB(1|}bmRvIa;pX_)sD{>xMm+rZ+B>5+L+ZP)L;D{XiGe+eEYio`fK})h5o^D`7OX7#a2OAOuCLdwfBx1%1|FD;7t}uC4ASH?|DHPy@f;ALN;ZuFRfWx9{CUKYQ^zdXKTw^z*v z1)z4_bJ~*pI^p8aGie}ltT1t9PSnq0JD#{MxQmF0UkiT3E1)9z0}S>B@hGt=1jTONqQZrh z_Yx{!&*gv>KT-xEUY{A2N~L0sFoZc=9WNm6kwqvd&j9Khyur)tI^|9q2(T+k0J5i_ zITtPJo+q$WrEDwNz?@TrbLahYEfmu>R<8ZAF>r%|QkFVMfOyS3o z{i;~J)Lo}DwSt&7ot5HO@K;dSHf#1Gzh^dma4+Bz%n_lQgn7H=%R^N_o z%WZ(OIsuNbCLD5__r?dV_n54#y(o-v@CvBEb`BlH{K+kV3H(TlB0x~|-ghGz6301> z+=R8IYi>?xlGrZQY>3FtN$lXRP;G8U-RlGvy=|9_OqSFhojFLI2|W)6-@fn3{uVR_eTC2;pMY-?9ac9Dl=C1?Z3j!GlP_%=Oyc>;a2?uRixOGVw9d zSsoxALG77kE?hk+4mVeY>C+nYZh2mmWvhKI=D95{pz{S`TY3+Fl4zc-NR7UO*=)?w zwT>4njdK8h!z262ES+xCZ-vpfqSl^<7tGqhE^!A)61y%jW<=i=eUucctU({vU%vy{v`41BBR#qLS|94l z)n1v&J;tudBU!nZ8(^GA%@ec8r&!_;ka^*cWY^kW6ZB)<`CFWKkN2X!+`Tut0>iGa zM+!su0;Jk@s?OFhyKdp7X?rM>OnkJUlpq1Peq1^Dx}=IGf~s{o@U~Ha%mFGOsmvHx z_3}_DuwOd9MZGS|Y5NT$2 z55S1Yrc6U+G{vx{Q72{pAQHfq>sLe6z@KJ%kD@Kb4sRPV@83?pZ>k{x4a#I2wntF} zx<>NVN#t;mxdmSb*hwK}^;yg)yjC-rG(Q!8F#e9sPAHI@lz97wBWTY5vN`?^F4_B_ z;Y6PeLNgh=Ks70ta+fytK$5D=f6vCa9OJL(FFhTvq_KCBU1{;V%np{9c}%5O=`{qV zGmS9#lmDgawkSM^9)Dw@@YRFD+tu#V4+409ABg8{S=@4m>fyW7TYX>NmsI-sqtIN? z1G`ujb6nT8B6L2P4=b@T>drk@t=-$wmj&Kc7Je-WZN`wJAV(%!b!q99pGZW~1 zw9o;hxZ)9s$!-o%45GxYgZYdZW+r=ddZM>d;Q$dtL98fR#=wXWS2@C-~q6D_he0HYggkySE_>`UP5o$;;CrE zm{BuuPmcs-Ig{y)ZCNU*OZ~EG*r%}{+qpHZ_j4u}*do?kQ2ulX<1PG)3OPWa!e1r6 zNakgc*xF$k^g-^(8Hv=^e&NesRemcu+Wpu6DB<8qqTVKo6xAvk8nK_&Y%G%LFsKAuabOp-y_?W<_fweY4l#<$Ywsskreor1& z+AzN6{o_^D@kG0jU|npo+02E9-vU}Qj9B!^`T_UYaIv4SI09kESy)zGAnWBbfqolD zLH!Y5*m&xl9cpZa-8T&;&QvnJNm2(Ypm1+d&+b_}PtTK2w|o}~KT+{pcr1P|msvHe zc3}4XTs~iPwG>k+{U$fr#VPKc`}W@ijLIgRW|@;#xB7Lb^+msb;<&Y*otozRTMd4B z1FiRTvj8bMSo<+BKqa*;)4y#FkjI0V$@_= zrfVf&35R-{cjGy1*b^kfFaG&ubo7yHvAceXHz;t$Q~uYk)RVXDx%q=hzvBnU zS)CzWpK94A-DDU~v?w$ao&hLjhjU^kwl_<-u5%Kyqj{J@@o2VA9~9u%J}nID^KO8$(t~`*5-G-+Y)}>V$!nG8W7~SR;lm+i0TD+i59v#0e zIf=ZhVk}@b_+Wjulg+7v5%&E&S;)rn4_BTlt0uom>c&^n;xiNv_d_GRBMjaN`d1j8r%(dI4@XM3e<|cQ;a7Q*i2P%CyH7TMDC#eu0G?oa zr#j4^BK-mi?B^Rm!7FVYL0TDoPL^F5F@s3Zu^c#`Di>l=J%0x(DwHt8v_`mOO93WK zCIco)#j`=!nYo>ioz=(~2~DyVPZz>DyQI`Xku`$y>67LL2h|*5)G$xVi$7sP)n70H z5gA>}Z%3w87iTc?6{gCtOAVQpf5HStJ|EMpmy>_K0DGK;Il+`xhO{l=Te|Ko`$3iG ztnsV^QxDaOPdH>XDV12yM?+845(#b1T_ldy{jOqvmP1%6t(JwaAW69oETbzf8fDRH z05TQ^3-BF}zI^>&DG25f&3Gy0ejOisMWUW$E~X0r1JHB#B3rMlsZ62~8f}bYucYr( zGJU&nLn>tHJ*=^o!HKCeO)N0+L9FdHC}EL()sz|RhyCs0(xBLd?aSW2{xQC6rR|Kj zlG9iBys3U$xuOdG`iyHHn#;6(G1B;+h@P_Od$i1#gE>nZTjLDHa$NQ<$5^jQUukXr zs%LK&f7@F71&0>I9Iy%Oh$LJ*)4Ftd>it43eFPj7#p-JR+PwNSUd5wge-48Vo=;f( z&YqUV@esn#{mgNk&GcM{aQxA$zE>>uuZQgeFM&_b-aO&TLkK!VTaCmYjD2QO7w62+ zO|Gs8t!$N&?>DGke%7-%s=qGWc@rnWzljYtvB4%b*u(~#*kBVIY+{2=Y_N$9HnG7b zHrT`lo7i9z8*E~OO>D4<4K}+CHoFZryA3wG4K}+CHoFZryAA$7-3Bl?u-R>}*=?}d LZLryGu;;%3qJG7~ literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/54.gif b/im-uniapp/static/emoji/54.gif new file mode 100644 index 0000000000000000000000000000000000000000..5ab6d2506367cc6bb672317e6a543eadca7d9ff9 GIT binary patch literal 10885 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 Ydf2GmN$LHxL)s?rpCVKAU_kSK0Q*tCU;qFB literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/56.gif b/im-uniapp/static/emoji/56.gif new file mode 100644 index 0000000000000000000000000000000000000000..25259709be41fa65fc8d6d469ab61abb3bce09aa GIT binary patch literal 8827 zcmaKQ2UHVj+xCPM5(tn`LVy5C=nx>GOVK2RCdGiDfans6g0#>S72Oa*M+5|=sep(e zy{I6d3B4*HD7xqZVnIQP;HtZp4}Ev{`_F&=_n$N8%rnp2?U}i+>%O0HaI&}9#aSnF-r3n{rmyRN z;6Q-Ce@{=()#{qdl~tE6m3MXAEG;SXU@|>DJc0rOwi9Iw3kwAT0hK6wFeqq`r>C#C z_rSnFUS6K}o;`;{LP8E6Y;0;y;iZHhK0Goq5*Z#&(@=xj1oJu@-t|0%tP*_)AfAeO?hYuf$ zi_TNEaE~56T3K27(Ai<5h8149a-po0<>&k4$&;@_;pE-B8;uRmpFg+I)A;(~L&A|G zi;Ihi$B*0U5i(L!XQrp_-JASyyC*&>!cv>yXh8V#suc4kJ($>Rn2d9K?G zPsYc1IN5b|b?>&L-nqlysK55-lP8-kElv~-D^2CUR#yC6?GNqu@vt#&Z*6sVcIxZx zceSHA+n9fvnsPAJ_p!6iOy;nI{i`Y~%P(GJXe)l27-vwlXJ==prlz*0dTw=YUT_#=JUEzUjMqR=S}UWTRu_gK86o!!Tg5&rGlOgGn@oZJf~#c?q)H{07-Os6Q; zzVuVaGBdNEFZ}*_c8030Y+*=p-)1l|F(JHMnU|4jXS1!VyW5#&b0#;}&x!iy;(~*P z(fM;l{vIx^%}vpvhgiPejrG^uOm*_}3-&NwhlYnA&)h$EI`hlgT0%s~+R?1VC-WUQ zZqUq)Ij2r>xx9%1!C+5k`?V|gCq^pH9_WqOI;_V|?b+kt4wex?39>D#}hB zX7P@&Sx!4#jWi;Bm<$><*u&{UURGfmC!Xc)Lo+|*e0by>LEizt@PM=TuaP z9zhuZ0K#{1H#^VWwlt=lkpUSD`p=ZND-+LP#j&=o);ki|tiZ6~IDA0xq0mSh;=8Up zM0{wF4RNojE7_H88yphqcswR}_i;DRz~fHU2SeNW}jXi3_tK(tbR^|L|!W6%&j%H89o>BvUAOGcyBm#l+Io%+kml|Mx-M@)i?x(9(lp|F^HLI~!t1TpZid(D3NdqXtKf4WeQW z8B(@w+xEkTk&*tEgnsO?$T(Jleq^lLKNc9lv4Jt6?6}aVNc<0rtbnNaI2+CGMz%Pv!!pdu&}o`Gcz(av9mWe zrv0PKhzg942#$>VM>ptyb?yGE?vGT6U~hS51jmFP2@bN4iHgAgT(V{8e~*R!U+w*^ z8}#2}vHh>QhFifH{`j!}$A|uBYAbtw4F65rt($)nKR9wL+hexU8ub0!*Drtl`T5hw zKR#@3yce#&d%L#!=Jm>}moJ_#KU-Q{`2FdV`QIKtntS+QcIN){)a1Rp6XRo}Bf~?3 z1O0uy0{)%bJ>6ZmIy-LOxZd8@+S1(A*ic`0ZHtbouT)*GtSG;9af`G|w%Gh!VZqt_ zyxcQ6r?ay%Gt$#iQ+UbTB+jp=P9~l>ek|eWk@&dSm}quXWJGw_;n0vn2ZMtG16cmQ z9N4N5zCPZ2_jq~k_HbwJa&vWY-s$A%U~k8;rPHW8w%b@+S#Gm1H#0RcHlmOX4fIKR zx;olgni}eAL;_w_MH#20sDPEn$jQn`qft_l5=e0|1Y8sbg@8r2$bs+~fkcBnIi1Az098OENDR0hA?JYd3 zyi-NCmQw1>)hKi-iDm7Xkdm^t*6TYsFqCBAb1+dS_p(bF)*zb-EPQs=xZ+|=AWLh&kTTGCATqu={F?kKL zGuO4=Be?R$v~RzT(ygSGbBo{%%C879$yzgxCQ_xoUCiWp23}0dTBy(RzI%2$DSvgX z)+b^(3HK7;Y;;AO^ZT9T4;{`+_vPgIAP7aRYEbD~xXvx8aK$NRfd*d>c3-tzpS!Ud z^nI{spFrgIryxd+&}%$@bJUz#WBjf1B^p}aC*Q8CmHl-6dgbn&icmI8yatkoBk;86 z20-j?iUgMXWe>z;R^1$Wf-@su79l)DOAjeIlcJ|Fq}NkWf}cg@&KO$yf*_Xq zD3!@%q|}Ir-m%L|7Vm2o*vs6cF7os5crNzh^_HWvqUbj!9fE%qVlUVlTKmtN{u;!} z)Lv}&;2n?9{tT6!`cge%wlL4)p?k@-R~_5Jk|(-FQU z6sw#5aEfu!rk9HuuP+}^Z^ekuBk$GovoXQZv|2ijWh922mgOU~aAl#ZS9h6F;`lT$ z0hPBTD`|_%#wYOtxVJS))OEgAnVdUNb+b3@K?;L)9hn zikBy`@0QNTd1Fb+7-@6zZKdG-h>@qqK%4jlt7gyuX*G7zK*}{<+x`&JSWCru$X=6< zorGYho(%?F=(uVA7~(R?80@2pN(N0#?QwVTRmb%`4ww1PX$DEA;X&g{L-jG(Q|cFA zWBtNdoB9(_02gK}0(Sn7WeY;@tHWw(VYGq2w5}(`NE#5Q^&4gqf|wzD zwMaUDbv@I2yb^PBc);MeFv}l#89U@SXjCH14sp4x_;7g8>;MJ-H>G3)Bp3h%$bimI zN>M=Iw8=_8DaE9QOz2BRDQ(Yo%yZ{tVJUm&=g5I+y0+BAOVb23ha4>EQE}gU?wAy6 zC;yo7;I5I=k2Hz>9nzYx^Kfb+_L^;ysI2MJZa9i^=xz=Oa8*+*c%6I=p8Gg}R;k&4 zmAO_9-$~beP=v~Uu;5-{Hcan!E^O1P=4_W*nn_W?sLtGG*~H)=kHf$Ekk(2~nB`yb?Bb%Rwi_E83)p5$*gDe4B-E{Bwq9vQD^APL{>PSXpvoNY4>u7?;mH0+U?+cgPGkIsIj&IH(rBDnC3fTUbNz1hZorSJW z7jcu>Zt`%DCv%+Ti_S>ga3G}23wR)MJafwK^7Z%XKUjj1cs+dztfyE~ohs8CSBz8@ z<1~vTJvzBCqEks-=~p#7ezfFFm~5+)Q4JjalIGot={Ft8&A)n4DSzpe^d5haB*Wy! zmt$}#cB|6mY_sGUc$A9#tO^WB8muley4f!R3t)2v?*CN6#JTf}nLEYH(%Ee$E@vv@{a{H7X80 zMZEA>h-|a6?gBbDQmt$|MB)BGyL5Ib75Q?z_!`WU(|&;aUUDI-=hHO#Z6E4o*x??@ z9VdF@V|zezw`CSGQyZkLz8DVtscRjn-D^flf|BdGX%X_Z)GVlQ=OhASv8)uODkd-E zLirPu%kD-F6C3Y}ylaIgxhn+NO@4oYi|aD?QyRUC+t7PI{58KqwoE>n00B*-8rq<1 zB}#p>rW+?r1|*8|MoSA1sK0=zzsoY4bqri~eb+7HFv)R6J(@V${{8Kf$PrL!_9xIs zdlVNpjB&GjmY(LHI(e`9>szUtN5a7=uPxTIBO-ItjCasDl4PVLxiFk-cHw#?mxhIZj)Q(xiF8>j+if-u~3unrF zR76UD)a$t?$1#Seg6y)MoPE>yWuZ;D{maa2_s@m!Q%mEY6c>86w40OGgV+3g%YPFK zF-><_ePWinV-IOizmNuD7+4nAqT49xnV_#0y zIXLAyj;`tqm3zikI$oR6Mr>M?@lRhTT_!20QHRz77^_w%H&OK=Sx|D@1l z6!@xct+v3MwA+pniEA!jdYc6pfZ@snJHCvn0o|O^{IN!#!Qk>Fr=6y>%0|g|R>=-& zV%x5PUOo8A7=A2pqP}~fFY2=UCIlR!*h%`xegae6y|S5j6fF|}b@)h)f?)G?SF+Q0 zfzCm(gjd_v!lSVMl42&k0^^c5tiJpWpt0Wc#_ea1pcQNOJcAA>I#r&t(Bc3EAXCYv zX~?7LT59dkDY4@fLY&#C>-0{gUwAbcY6j}uN&S05+ML)|i7EjKm3Y+EsER?!kvI<_ zh{{TCzT%_y*NBW_m&i0V8H4o-D&leU%PdiUlZsTdTYa>s%%$h%u=IVOq+wB4I%vU) z)g9;yqF(LVljvRaKSb4g@p;hv2Xd@KXwd5|MsTDC@DxSE3#-q{*G!7)lw$Kzw6`sM z#K{~DYI&uun?vT6xC^)fMETT;c7i+ac#6JU>#(v4shkO*FWq=e*U*^wK9y7gSKwk5 zE#i!vE;jngBPH{E)>HSwx7|;J!unC+$O;lmt7$)@>oLI@<(>^_;>ZfEcgJ4`0L6Gc zf#ePn)fOZR2l@dEHg%{A&F|uaB!cQ1ZvBxHM$^=XHm8GR@u_S4ji=6y0`R31&5+T- zTsA*z3rt!dz4{0d=*xZV-e=M{Ls6_0IjQ$j$kj&(kxSwXEy=*J=pMy=*=t=$o=fs= ztpw0+^Y--S@d*hYup#*>#~O^*2`_COMgXP4PU7UWtG#FsZ2-}@LW!CXNq6Bt#1x^& zg~lGm#vKK7=d+c~Zt0mzwdrWj@&L z>BwT*h>4XM>Tu#PI><2}Z@--h5W$Fpr#u_yv4E6I2@wrf-r3F*wX!@)Up-b)J(E27q)aLx9 z0eJ=SItIJU6too+}yi(8iGE{0bH zwG1j&uM+`rHKg!4O^sh6|yeMhs6+z7dg*a|Yb}b7=J-k;68Sd6`^4+EtnIChZ0cYnDg5K<+gbvQ+)Ei zXpnVP(0kaO_j=+Oq*ZongN_Lv7Q?zyrkyE_>%8nP0n(-Hgdi{cN2agr3Wbn1nmy9g*$JWCo z$b@uj9qAAbQJ2ZORxT1Wy{rKX2koK0XK}j4{H<3+%H!W?D$Sn~o9T^z1IGZrv0T)fu+^)S71`Q*Gu%}HXA9yL!vti2l8!GuAF2Z$ z_8cg1FqEC)M?saA#u5Y=5uADdL7FYWS~yv6f*^dmBBXKifA&adB(7wveXz?EW$KgehXBt%I}1E>s-vB5ZnTa3ETe%!cS0)2pLRxNfoJ#OMZn)$w-^kATZedR2c#HuRh?YOhrtpJtO4 zL-0LKbFa8x*B3S(lNV?Q*!1{c%E(=H-7vm&f<{n#4t(NR;&!GR+X6eX7Pw9jpQ+V) z`*BS-d$XWs@K!gmQ{(;gGH8?$+PJqP{+RbD*i`nZ)0? zL;}}-R8z#i)zs@>T>h-4wkcjFI;9~)W5twkY0Is?n1BzAHAw%$sZi%it6X&5;O441 z@Kha25;^{mkrHhmYsLH1!9()#k(ChE*@G};3orkGB$0tqNl>CR!@>c|L+UcI1IG$9 zvt(SI%KT>TYk5hNsv?@HV3)Z9@7=zXJO)6N#ab66>6&s{rF27Fgy{Z(3R&6h0{uKQ z=3;NgcJ~qf{ngy=t771}?qj2^19Zvg7u}Fqgoa_|ZFMt|>=#p|637n)B4#g%ki%u} zUz?Mw6lcO?B{fs3i*$MecNFbDSMduxh7o&8qFhF*e&}i!R=)vlI!HLpq!RvOFB#}0}P#l z#+O6H32_35PUNR`dz?)HQIsobL1u{0@>qRjrI=TAX)|{V6dk2K%~Q0uXc<&KTO{vd zzYpRx7-vDxAJmM*qAq7&o2-Eoeyt)PrG3bh$1;22`B#pW)-FP%bFmdL6P=`&I6Brp zfNE4NOFSq!@@TMM)h9?eqz-N-_liwu@TSD>C%*TV-QQn~tJnsiLw)?#XLbWo3GYV` zp393!bn3xTACRgCtyXK7=?GMcjB6W*fSJ<(=8EjK+A{9KcfP2E3@H|gv}Pye%3sEE zbQ?s}nR;Lyq~?XX#|k$f=n%+X$q;^}rI<7PBt5BV&VZ^1y=z8wfG<>{-+~Y(_*IaV zMXBzMjL&E<;9ig(_14CF@6q{=nB=rn((B>BwvZK^{-LMbq&Zniiyp=KyUZX<5_!Qr?ma~T@|{ANjNedu_8}gxYO~Ql`T00}M!b*GNx82aE7LJ|7L@(Ko(s^ZH5{rQ|=(HnI3s0>wz79$vedk&0| z35kn4JDj=JqB`E3@7|QFy`;4_PpgS58a1 zcY@#mo$XpaZyZDpOjb4XyLZEv`u%W{TsgbqyG8&HH2P-8G1V_~Y*Cge!TU78YIHJ78e#K2GJmd|Mh%KI}AD&54kdV?r4MK8#&bEM50gt+i z#H!j%2|LfG1I@2}(x#N;&M%NZu_Lwh80 z(qfR%b-0ws=mUm|a(l0%`Y)a}BPySu>=E_b=yGm^)DN)7&A%Kp-QJBpZE)5kumUWJ zoTAIjePX%FYS}N+L2jW7Kfq3OH1er;A69<6c`jXZA9ZS3qfY2iB~$7bNZs|u6rzsh zeaTBDZ~gP)g*&SgS!CMqKReulB2x%gRe~a-nTYZkF5Q;?Qc31lY@QwI>?(ey%=b?t zI${qaHQ>@I%us(7qdrFa0{9V+j-Zp!D_5eUDB=K0ebTi&k2s!_*Sb|6{YdLLxcV3GqIEE<7h2ysgh zNNTx&18jBIjqMm&z&-{FT_1$aOyfbF8wPm&o*}oerf!*{FvE_{_g#Y4;v53w`lq)i z!tWsNzcK8r$vn{nnw-u=$;tkH%NC-8*W)yTz4eCr#n(?n!qE%4_4VdhsvSMLtei4{ zjI)luPDQauP$RORA%91?)ds>q=MMzPF%2o4a2%r!$#DyiF@vHZes6~+_Z)07)>@6( z3vwQM9CshVC})p>v+LR?iK~@+Mg4k&!^|^f7ld=$OPyQ#h$mhS!f83*sN%J^!R#oZ6ztZbKNHu)sRI}g2H-9k@km{xjG zZQbOD7f_f&8Asqi!KDF1D{z~Ak0RcLuG6It-*;P68&qw1ij65^!8A^l<@{+h7BZE3 zcHSz+uxBpt9_-_9VPr?}Z_5E+Y2%&&17fawuue~$+Io^gFgHLpgcJ4m3YC5Fw)-B$ z!k$D>6;r#s#;K&g0Oc{>_U-S#?)e P+1_5vt#twj1X%qKT4y{i^^Unf4Dh6bOH*=fnyU}V8kgVG=XOP9c>eDLYe$Kx_!G410?Npm$x zuzU+F_k))fc{~9A3XQ#bK^JwarTo4-CtdG8;Fo)P{>ERfa_VVvKF>yv z_XP3>$P;&|pN|>S-Bf1M&|E)MysO%q228A<+TN#N`S`jZdqrL;(rOfhJOqz#lBUzq z!?&o3!CIa5YYl&SbD;7yfWV~>2Q~nnDwxjr0y`(Q!a0i&! z_rICdXX(y8^X}%#juyzizDGKcfG-V~?=K)vbI}%klcRZt%S}Y^)MMs1Di~DfS8rAr z1TV8Td^znqlP2%dai4$9`V{f%J%dxsW~TJP-d=j7n+Ofn{{5)x<@6a{#BV<@5QP{Fsg9Va$4Jc`c7gwZ1+ z88+Cr*KT1kku)3ZW-~8>SF96#M zD*{g>lgTSONF*bHgi+ic20N5v#E8@Wu7N_23ui^fvLl%c%!Tg7qydt&K$UwRyK zH!GYj5Z7L5D%Q%0MGs{&S^i9B^!Ioc>pNFE1-M zMjSho5l(lb*kA=3j3XmyR%Rv?XEMRU)Y*|lArhUPEGcG=6lYT+k>Wrh5FMPq*QGGS zcgN5f?C*7Hf7dnp*SafHAtqMPGKJ2HjHlCFSj-sA*CAU){(E0Y|Elj>UE06*#pGY> z;su?-uiV)GxY6H>1hZ%5^vARnT>O~$bcSHIvjo#x2=KowFMa;B_~*wD?-$<9&%J%~ zdiIZ*>8V#Qf1iBue1iAv>67utj~+g_|J$!)qa(vZ_wEkf8TjRPe_wA;cUNae`>mTd zu3x))rR{R-rIw4$O^p}MpF4Y|;qElbw~Bk)Fm)-IuaAIVo`uCn0_}JC3z0mdS{Tj@lWyBVs$979JKFvMo3$aO;+z z0yb~j=}|8$uj-@>ETERk1}Cb+K)sar(?Z={Gyb$H0AXxpaf7wv{WZcUwtS2w)B_7G0v zCVRQIcM5GRjiT~ZEsZBS;K2c%O+~~?!{GLu1J>m%ddI1W#A%v>_Mn`bc5&S5!dTZ|SL8-J&-Go6eG z$x2z8cjabUWT5h5YWHxJoTGJ4rHSY?J4hq!U=!II2hju<9IiWHqM&9<2rhJR%;hmV z&)L6MMZ0+MsmhwFRCtQ9uW+~|=@%t;Syu&EPD<~?5V~d;butYZZP*M??wA|yLsg@n z=jt5D@O2Q6ZoN&z`5dmzPXF-JCr32py2vnLpY^b(iA@VqOd_MbrmKcTsh+BhYE^d6 z&$N8UtA9^!az>lIpYp~8)N^}DDu&uQt58QIBy|dsg}WOH%(n<3s*{0z>gr?lN|N&N z6qE$fv!!)fx+?9+?bXj#Z#7guSb4c2Q<>Cq*aE6I+k9v2Q6=Wu~fRHSw}4 zX7vX25~uX4sn@bDp)f@}m52O1(aQY@2EZ!r5Ni?UQrcyv^Mj7OKccoQpoYnEgM%m@}Y9+{Q+^z!0 zz0Y!@bV`OEXK%0>Bqk`_c=J5pE-pC#cn1INwCg#MrNq58sD;J18i^YnpB%h3w6Ipw zTa1NTx|E@-KRgOk>~9ydn&To0szugN9nwTZR@SwIG^`zFC+5nu*i*EM%#UGwaP zv{5NPl?_!3*0l`QADc!!x!Pt~I_R#gl&&FF z&-yoP8Vwfx9OAyeKA2f`dLBy`7Uc|9oog_Ey{b;ig>!T%45KKPApggcYt8fSt7x9M z)e`X@3PwR9Phsh8aZh{voIG}8%UKT;Pwj!9ehx1>KUlT8;;7d;R&|bcP8(`!GDqgu z6kBxT#>~#l7ftk6gX`+=mFMV$y_Y~8Pmot~&a))>@hDjmy9%RQdiC}~6p?EF!{Zu`3eWp8?hm`VpxsdQ?y79To2t}_mhJGIHN~+rk*89MpWm*o zq_$-y`-lu1I7lte_u5~uxi$V#hvV1A{U~5$4s&aRhq{KSwjw~&_alZ@R zLIT~WOW`HV^^^{6dJ8m5) z-Wu-!OGRY&lHU$nj>FU@5u;N<&8arMkgArnqj}(J?mj{m zCP~NFWH#DS$C1+XET|V(w+lH_YEO7vt-0`h;ev3tN z+ljK5Gxy0;cl~O8^K+U7t<`?E>mK?GirC_B|2K-oVNx(7nEu}==IIOVk8ShrjGa&v z5)+`9GdG|tzjo()tu7+r-sJ!du+kYo*L>mkKW=PIt2-a>MY5gol9w-uCVv z&TDl~Bu(T9SG(*gF8{{cIc4_L={EHUWwpO}+i3fI%U9mwP6j>x_;koDQt3Z&yYv;e zAYgsPr&liv7G#M*gkmjy+d`^0En;2%5*c2Gw5zff1uL+P$lD{d*&(|7WSWTdKVVCH z@GR>u*s^;cGFTD<*h-N&PgLXtu;nQDriDtX>=eKjFMuuQ{6k*2W|#Cg*v9_@w*H&k z!f|8)Z1+C-v?zeBJ^Pn`z_x-!!Mv*U+obHCnQ6&-RyF<=w*9N&3X#=K1!4p6=cy^{ z(}X3okHNI`kM7iztrL@}4#ur)tUrca1lk8JW+iD_jrU*C5KS z2lTJ;q}2B77#NInIcT)a_tu0q?woD9z(7S+G8w{>=cDEfgi!P6hF8gUNG7>9^~Fb> zRZcBwme8xDJnB=^Y7Fgf88av@kkT`>pVG?ZL|wR?ij#N6mLsm5m!vjwrCa~v?D(0+ z%YSoL!pytzy3%CZTyNhG&b9|fH|giIt`pVQVe)!4kLQIDVPf~syz4D(TNb;cJL+j7 zcBDSpq$?D2)kHzJQxzuexq?_nxtQ5+h)vc#EG}c}6{ptVzBgL=a1J-~j!ypXCZk^I zM&@UdO4=`V$W*p|7>OPY?8Pt@b79hAsiWu292CzT{ZnJRbc~Cx$&z8QAN$o{JhpwP zXu9)=4(iH_{nf2kCOr?XAU0!gW(Bcn6LzIjdtOv~1iPr!b`#xagYSEdxaqxklQgT8 z5UeDlc#*$uN-^E6)bW_=+iN}UIL%GYQe@`dccXRjz8}s?1;@VcjM#v5lp~K7I))Xa z5fD%zph7@}fC>Q>0xASl2&j;S3R$R-h5Ex;3|Xj3NRKa#QXxj|5Z3fyl^Z#Vq%)0*qhLdUm literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/7.gif b/im-uniapp/static/emoji/7.gif new file mode 100644 index 0000000000000000000000000000000000000000..3a847b8f98eb395df42186db648e6372995bb1bb GIT binary patch literal 20608 zcmeI3cTiK?x5q;WA)yEXL_mZPIs`}vC3KQd0wPj!6~RIm6i6s4dQnLT9i*x#2%$<- z0wOll0HLD-dKEh$7K&070Y%OW-s}CndGqf3>&+`OIg^>3bF%i@dnapF*7@x3c64&U zoB3ISEI~`xK)_SnToGu#L=bp|onpv*E)xa@O@MwQ;E@sV)&%$i13bV2-b>0PmQx0)7lGmWczO-GGy~CIUBYrV^w-RT4g?O9M}Bfm{mEideoR0Zh2A-qKk+ z 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/emoji/9.gif b/im-uniapp/static/emoji/9.gif new file mode 100644 index 0000000000000000000000000000000000000000..52562dc088a49f3c550339e1bda87d820788d8bb GIT binary patch literal 21128 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/icon/iconfont.css b/im-uniapp/static/icon/iconfont.css new file mode 100644 index 0000000..295c2a6 --- /dev/null +++ b/im-uniapp/static/icon/iconfont.css @@ -0,0 +1,261 @@ +@font-face { + font-family: "iconfont"; /* Project id 4272106 */ + src: url('iconfont.ttf?t=1759053579007') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-chat:before { + content: "\e93e"; +} + +.icon-create-group:before { + content: "\e65b"; +} + +.icon-refresh:before { + content: "\e64c"; +} + +.icon-copy:before { + content: "\e604"; +} + +.icon-code:before { + content: "\e72c"; +} + +.icon-username:before { + content: "\e623"; +} + +.icon-pwd:before { + content: "\e60d"; +} + +.icon-pwd-hide:before { + content: "\e60c"; +} + +.icon-username2:before { + content: "\e647"; +} + +.icon-pwd-show:before { + content: "\e621"; +} + +.icon-teenager:before { + content: "\eba1"; +} + +.icon-warning-circle-empty:before { + content: "\e606"; +} + +.icon-loading2:before { + content: "\e6b6"; +} + +.icon-complaint:before { + content: "\e612"; +} + +.icon-about:before { + content: "\e637"; +} + +.icon-security:before { + content: "\e61d"; +} + +.icon-personal-info:before { + content: "\e62d"; +} + +.icon-success:before { + content: "\e649"; +} + +.icon-wait:before { + content: "\e701"; +} + +.icon-error:before { + content: "\e62b"; +} + +.icon-dnd:before { + content: "\e693"; +} + +.icon-create-group-2:before { + content: "\e616"; +} + +.icon-qrcode:before { + content: "\e642"; +} + +.icon-add-friend:before { + content: "\e64f"; +} + +.icon-scan:before { + content: "\e8b5"; +} + +.icon-remove:before { + content: "\e603"; +} + +.icon-doc:before { + content: "\e61c"; +} + +.icon-image:before { + content: "\e7f7"; +} + +.icon-top-message:before { + content: "\e6ff"; +} + +.icon-setting:before { + content: "\e851"; +} + +.icon-phone:before { + content: "\e692"; +} + +.icon-email:before { + content: "\e611"; +} + +.icon-chat-muted:before { + content: "\e634"; +} + +.icon-chat-unmuted:before { + content: "\ec44"; +} + +.icon-un-register:before { + content: "\e656"; +} + +.icon-modify-pwd:before { + content: "\e63c"; +} + +.icon-film:before { + content: "\e66b"; +} + +.icon-delete:before { + content: "\e605"; +} + +.icon-receipt:before { + content: "\e601"; +} + +.icon-pause:before { + content: "\e669"; +} + +.icon-play:before { + content: "\e620"; +} + +.icon-voice-play:before { + content: "\e675"; +} + +.icon-chat-video:before { + content: "\e73b"; +} + +.icon-chat-voice:before { + content: "\e633"; +} + +.icon-video:before { + content: "\e685"; +} + +.icon-ok:before { + content: "\e65a"; +} + +.icon-at:before { + content: "\e7de"; +} + +.icon-man:before { + content: "\e615"; +} + +.icon-girl:before { + content: "\e602"; +} + +.icon-file:before { + content: "\e671"; +} + +.icon-add:before { + content: "\e66c"; +} + +.icon-warning-circle-fill:before { + content: "\e848"; +} + +.icon-loading:before { + content: "\e93d"; +} + +.icon-camera:before { + content: "\e600"; +} + +.icon-folder:before { + content: "\e6a0"; +} + +.icon-microphone:before { + content: "\e63b"; +} + +.icon-icon_emoji:before { + content: "\e619"; +} + +.icon-call:before { + content: "\e610"; +} + +.icon-keyboard:before { + content: "\e679"; +} + +.icon-voice-circle:before { + content: "\e67f"; +} + +.icon-picture:before { + content: "\e653"; +} + +.icon-search:before { + content: "\e648"; +} + diff --git a/im-uniapp/static/icon/iconfont.ttf b/im-uniapp/static/icon/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..85b17d0255b02f3f66427c7195c6aff01d706b8e GIT binary patch literal 17428 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/image/online_app.png b/im-uniapp/static/image/online_app.png new file mode 100644 index 0000000000000000000000000000000000000000..173d41586671e01f9b25dd9163d03bc899f8e7e2 GIT binary patch literal 2774 zcmV;{3Muu8P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3TsJ3K~#8N?VEd0 zRM#EHg=H6ZmsbRNqvNASY!!`fqDTb=VIP24sS!j3R3461Y->ACr-{ZW5Suojp;iPJ z)X^~tYHK<;4Ft3?Qy-m9n#2cb)qv6;xL9S=G?I|rUI58&&-fdg-f?mZW3YHExuA$_H#r8I5Y zG@3YZB2Abu;h6#-@CBdE&CRzD9XezbljHv2a5%c`c02j|`*Q*U0^EY+&uX<2(jX?s z{h_+Lnu3CY9+}Ahl&298zN@OLsJ^~_te7PChIQ-Kk=bmPOXNw3jg6&|BS)$jHEI;k z)XZxm6eq=Ffc$5A3o$9J$jV<{QSshHqtvA8t8g+uRu#5 zmDtMU%p!xkcI}c$N=nj>02LK#0j9l`LY7a8WYRv$XSF~m?Ng^tkuu!9dzXBCd`R!B zr;GJ}r>l-zG7UdzA*0F2OCKXmU0t0_NlA%Hfbw#W3Q(aI;8!jJoOcRv^5jV|>q5A5 z=Z-AEh5C9^J4`lE-rQvpxmnmMGA@tmTef`a&mHL@#4iCY1jkdq2ZK~U}e}SeU_C1`~zfLXlN)+pFW*W ze}Po;Kyw1P8~85&zZbyMouE%3;7Txv3&V*@?|z3A3b#*tEJ2B`($djtrzA9jI6HDXvtsu zWnSJ z*{Q3wjtkM%x{GGdh-cSX23g$66+ffwv)8x~+1*zudd3{>2mt52q{NIEF?m6WOZW+; zFM$%5P{zZt+UAVor??IviZULy#xx{=d(oT#dc7Y-h6Peoco0QJ1hMw*tO1zzMn*>R z86d#9Y-7)e2nwP|Mnq5``S?1YiBiYbmxR)v<^*{Cr&Eb~R}g(s!1?(7ES3R3eot*} zt+)yh!sW}Cxd_O7_hLJJxw=4LO#xLW&f;-EZf-8ES+jv=Lg7nhL1VBoa1wTW350#<|G-rnS`*VDE9C3LqqPv+pl6f&^qA|B=o%FzRpDajvv zKNmnt0?19^7Ha|wGy#J_=)E7+1avwb=~yPa7iX%PKn-gG5CHL9o4~AD&gP*x0epN6 z6cHA{Ize=Vf#j{z%Qj@zwG)(WOayd(AUrUTpG!79t0CATM+b;s)C$SwaRJ~Q2|_|V z0equoXi5MBD=`UQ7n|OomofM%Y`72iODJq;LVCRuIS#jJOn|Vd(?F&jPh?WNKH@b2CBA#+Z|HB9zsUSKt%HpK-eh1SL3DGUY7rIoi#A>pP~ylq zRS%JIPi8c@|b9%KRj1k0&J^!tN^tx0-RF`@P&&2$7BJny9lsvnT7>0hleu}s(DR7 zb3mUU*~0z9sA|eKPR*1zXmaQzp)*9haWg1%Vk$%$a#5rv})BVnUs_iIUZh(;gy)gvSrJ(D?npoqnLFe^z`(|8EM-yPqk9K!&ZtA z04qY*6(6?HrFgC{M+*xJwIu*bP)cl5QeghSlh!64y4XgZG2u^ zj$u$Jacqz(1HLEvpA(>E5CQu8`^Bsa;lzm(-wpvVW=m}r3@xvtnB!kN1!%e=+Z$5m zss#8n{|Ce#!Rca8Ge@iFO3=*87rqULKi~myuX z?4^sya$zM|E(%z8=91mUt}|Qmno5do?_=DinD%~}`Dz`zZsRuFj$Ap8xo zc?M98Z9igdS?mKcewfFa!3Puv_IFWosnC`I+B}aVVtXY^xX${3O`A5+`Sa&F=gytu z{}+G_JH+r^%!Xo*{cUdR)~$S-8bcDk>Cev2rbXF{oGen4!|XZPSsYi}VDT`zNZ@?V zoH=|b#OSkS%NA_LdghOl`D`I2?|ht*s|+{r(T{4V*sx$7c$Bw6&eM<#06b6y5(9 cVaSkw1KtrL`@V0!bpQYW07*qoM6N<$g1~+okN^Mx literal 0 HcmV?d00001 diff --git a/im-uniapp/static/image/online_web.png b/im-uniapp/static/image/online_web.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4707805f724489641beb72a6d17834b1d6eefb GIT binary patch literal 3467 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/chat.png b/im-uniapp/static/tab/chat.png new file mode 100644 index 0000000000000000000000000000000000000000..61254e30b4e64099a556b11d1795aaec9c32a8b1 GIT binary patch literal 6356 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/friend.png b/im-uniapp/static/tab/friend.png new file mode 100644 index 0000000000000000000000000000000000000000..008e83f643bd122f663aaf402cf71318b845b8aa GIT binary patch literal 5721 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 Y7PGYpRV?Gb(TYj1%37F8B`f^@0SN(zR{#J2 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/friend_active.png b/im-uniapp/static/tab/friend_active.png new file mode 100644 index 0000000000000000000000000000000000000000..a033da3e2666430ed0986d168afa2f4d687e65e5 GIT binary patch literal 5124 zcmdT|_ct6~uwQE>O2q1&)qC$jEH1aqYkSTckIxZIuPO z;Mx)SA+$9BXdK%n06^QJ15~>Sw--z01tf=)n<05a1VF~+G&9*Y z^?t7EqI^)8_uKMNaGIHVjjqtQKbp0a5*LdUBx)~!Vbs#RRf4GOhcoT8v>!^d_GepY zp+&D~@I!ihH1e2Z*?>%V^6W~-S8Ujx-&)~$?RwsN9w8{ag)pLtDg=Xp<1`Ql9#Blz zAqWPMR*va1B#nnU2a^&}$76}Oz%L`&)VqCXB@C{2Wgj=BGrx=^kJ{g}v6& z%l>&C_`eBgUO3ny1mH2*lC(Ge0NtXc-cw9v%|DNem z0pE^F%Eouz!txev2WTE_1LOhyH^80OX1Z@bk?VIHQ7rwqJPjCm61gx(z`8D}^S7b^ zJ#oWm{i*0pPUv2{Ki}C3I(e0_%L4&s$A;N05<+5=-Z zIrEPE>-*C_{cBy*vV6p1%f+4f6mz2UAV5fL56jRtGxy)KG~eT|Z`=ARgs!h;&VIL% zC5b7NK9VO#V@}%`sEgjVwPDS%I(87}H{gc=4{(Ja`?1(`$&PQ%`0$=i(nWWSBYtaI zeJ4i~8iHK;*bgyCa5D1)YsU|rFlNe_S25*WJc|)rVnIeu0XG}-l$jJIfSnAVeo15} zS5FPimoePp*YwneS|JZDQ!vabJY&R@k9kDM^v;KMs8(zvaezx_m8>WDl z9zYROjqo{iRz$IGoz94iDR7W6ZyfqbV)I*WE@$WMtvh4loR`*q1EV~_tsqCG0!72| zlIP>*gE1oRF2C>0EA`%wa0iWLa`tPFY~o~ z<@dVsy$fvQ3CU_Tw$vtEsL+E7%`PW=)x2y6ipgKgY$x&It!>e86A*$6g)jCal5tMR z+jW(5t@D50#PeEJ1^J8=RzGLCJtyNQ~`pPzG|rD};Mc58A<) zFhjeV3Q_GFO=b4{@_L!w5OmpMGZ?P8#5VffCJ4Ebgq!}13b`EDU-n^9_I5cf&ul1I zrA{=rgJk>$Mi_2pcsX;w6V7dj}PFDXCAtYqx~yy z7~mZiBhimbQ_deS1((@Kz8gqO~sZUBjSJoh)G1<#(#7o-)~~un6i$*M|0>+@%Z|_;n$%s=axu zkzshj;IQ#Sq`sKp6O{~=QXMQDMAIa)MP5p%y?%`IwiH&M%)+q&vy+}1CLe!5FCdR{ z$QVN^y1qu9#g4w9?4iPjak+O4k@t$Ic*)u7ZQ}0VKWlBjqV(@;6Lm8tSVc&R+b?Dn zb?DRx*vpYNr=)+K>s~8qg>n#|Y}E95EDxN-+Q@Y>m*PV|x6sE>M4j+XW-u>QntoNI zd|fyO9DPEClB6s+bT4=8Nuf^ z+Bz#UUeV}&9C1>m>?*g%y|45S>a=9#zPfYoKWKY~9|Q3fOM~0|&g^cM6>E;^u~h)x zw}5D0z4K4v%)FO5)+B1JvH`kAmAp5-$x>%@V%WM@HLi@1N+p$+8WRNHxU4dh8oAx= z@M}l5Tyt#aw^<2!P1~=#Hcm4adYC)MIgl{DWg&(%pG|lW&OlwUR1-q7E)916J@ohv z0kQUpesva!SA_q(_xD5I+tg`}E!z+I(3Ll@H?26<4&6N(Warl?LvD)DYlExf+g1Z9 zWCLzqET|Ko8B>uq`))RFQDkpCkYoTTS_s)DH|m!^nz3M5_0V0dR&RT^D!P6HC~jJb z6oXGmZj}C9oyT_vr$wk<;C94}%3QT>PecJ^0%K)lpKT=h+qig*p1)!_@1spWgpe|Jh`N9oF!Dt@x$yfJgJ) z&IxODuZbThyLEm}k;HyMVzsYJB7rO1-W;o@>*y%FmM)CcPv{jQu6f|8#ileYoakd5 zs-X23gs=|apScKc%bJMiA3p0{ct1e~Td?Xllx2Nt^d!h%^L!k(5HCaz|JxBiH%Z6$ zE=j9$zIkWJ@RB1LDok=+kPipnJw{g2scxO^L>s9juJc|OMi4>-T-71ZyVX2APc`hB zS_pN&hH8HZr-BwHa$5OvOaET7l$)C}xKp2`g~1=kh7^6yzxh&zF#Gfl`=UXv)bi={ zpZP6s+gWy9pJ?HY< zcj3<*(>q(pi|!HvbBStO3B2e9MR%8H85MEBgz(zBrP5lH)9U1$Ltqy=3$M8X?VO-+ z{v{|~vuFKjFCY1ZckU#p$2%l0b0nlM+mU4RaKJFw zm%zNdtOMverG|{xGPwE_SfARG5|ieXr+MUH!*vDzQWz}#gE@|+uemwI%yU!1L&CH z#+vVqUTpYdeq5ZEuIEUoJZ;@jteFpL-8=CU;VfXZq?=o-u7cu&DElxRvzU3x`Nen;q?6~Y1SH!}#wP~Pef!!Fn50Tw4%{}cbobB$zbz6Stc4n?UC7n1fOE$yQ* zjlI0W=La50w$8ZwAJkDL>|qJY=*65ThdmM{KED9%?12{KZ-lmzLtrD z&TH?q&A3q@!hm*HVd?{HshTUi5dphqKodX&>WesSp;u;oN3)lqGi9OxAFwvBknd=c zb`2}wF*&P-2z@D(-$B~zeonKf`D*4~^nP5A%kMZHtU+d80|R_P$B1L+Won3#HRjcf z_rvYV3!70(NQ=gW7WXeakM3IPsXr#i;9P`%Fow~{>^nKHooDE6Y(gjC(Newps;B!D z3yywEAli*6K9%QoG1k@~k2Pv^yh9 z!MGce61P6epZ)LxKQ%chZYR)= zv@OiHRkSC$)8gufA1%I6Mo6IY8qu7xK6j*ox&w`J1%lXmLa8T`7(Um=gA*y_)sF`(EC3JLA~)nxpq? zE^0S;b4|@A&fg%&5i)WOhcj)mHs|hL!T(h7*p}jEser2|8|OlLOq+a1Ypuu8mXpHG zK|T_o!&YMflk=wn!G`%@$}WE38*8l!vt7f9yD34#rv!iNJTHM5*WJX^w~(cM&o}h& zr$yo587AHW70dF#QT7J&j$N{b{ud#f2e3gj&M#D1eo)eN@+t}%FCCD3?H*qWsJG~^(cbt$~@3q&Fn|VM=5)o{!Am$HRj6w zH#bsJFuSULM;9^??9W17Kx5--!NuzR5Yj?2%hDMm8n?*ubS`1lx=>t>nat*9HKX*6 zu-8@{+l;E@-Gwja?FC1H4;wb{*d#A3YwyZqx})aS3Ly@{O^|p>`)Tmw$;#ygcb9y; zBQtGGYP3{pe1KQ~4EE3ib*322@$wg)YV9;cz#=6(dobxKK~~sijzQK*0~UXJODgk+ z{<{ZOcL&bIwZiA9;Smj2YhY*Tj4VRpK{XqkB!Kd_$yC;6M_*CVaZxtIxQJ zMN`E^qd^~Hq8AoioE2KrTqqsGJGx8u(Rqo+q1ZWdJqV*;_ILUja)sO|Pan@Nv?OC< zfM{M~>U7jduutC^%DXvc89okrB_Yocd8!96)Duyu*j4~#L~STLaQ5?MJM92BMJ7^n zAvw2=mYDV+XT5{N6%Ht(&As;*+D#|FPnSnMJ3S%oqEpVe~ z(t5eU)N%1naUcZb)fXVOPrRbPF4QLtMgvDL<{8Gk(x>LCkpW^AOU&d5CTR#>?7|JuV2Tt8>4SjR#xX0XP+%omhD{fus_Jq4b!Vz2 zk<9Pa0?VKz87RK?spI(CsfZcdsU^*?gIrC)>zuertBjCO{RD`k0EnMVFz_>nAta-o zu1n)F222XK&$f|z6MQ6?4h>F9d7pJ!6Y1S+?FWP%CSCtpe+oO0ilQzr-&6hYY`cik zy{C&FuERrvTtiZ1vMsSR+N=yZDB}R4!@TAL>dc($gx%prO*C7OCu=}< zfrAfID9A$f=(LtTNFgWeqUrLNKcCe0i)D=QfFb{@HK{ckCgk?zzN{erzbkR@vB>QPqI{$e8>HMB^uj{iWSuZP(oi{_Y_rtD(UTbq; z^7d|JP>cI&90UBn$w2+^oQWga{hnS&6(6)q@Ck>qPvA-UQ5Cz=U$?g;(m}QOE9WW6 zegb_O%nN486S(9(|I%nO_R*y2N2yoPro@=!gIqwM*XfWEsi$5M_UQm$WnzyAZymI0 z>+jMYwE@HE?8fVey|5P4?!?5^U^F>c==F4~t|EyHCg2qq?; zes_7dhy9CuJyh8v@7|tRkkpBxc8W*Q#I_@BO6q%`WA)CWYt-sRnqQ1ddbF8PN%waJ z&dQyv-eTRh`|j%a4^#nwt5s({PSk$Zk&#Rt^o1R?< i4#;$||K9=oR~4;MbORo7h1X*}fX+Qb7+S+V>VE*v<*@Jo literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/group.png b/im-uniapp/static/tab/group.png new file mode 100644 index 0000000000000000000000000000000000000000..72b900703ea286b4dd408351f706c15aa268f359 GIT binary patch literal 6880 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 oOS1kyqsrwmyXgt?)nojBp$tI(yDQPLfz>% literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/mine.png b/im-uniapp/static/tab/mine.png new file mode 100644 index 0000000000000000000000000000000000000000..06dcc7f92d7e1ee46de5417c7e0df810606acc65 GIT binary patch literal 6342 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/static/tab/mine_active.png b/im-uniapp/static/tab/mine_active.png new file mode 100644 index 0000000000000000000000000000000000000000..4c73eacfd219b85cc65bc50606ca2bb3eb7fb633 GIT binary patch literal 5902 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js new file mode 100644 index 0000000..56d8adc --- /dev/null +++ b/im-uniapp/store/chatStore.js @@ -0,0 +1,546 @@ +import { defineStore } from 'pinia'; +import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js'; +import useFriendStore from './friendStore.js'; +import useGroupStore from './groupStore.js'; +import useUserStore from './userStore'; + +let cacheChats = []; +export default defineStore('chatStore', { + state: () => { + return { + chats: [], + privateMsgMaxId: 0, + groupMsgMaxId: 0, + loading: false + } + }, + actions: { + initChats(chatsData) { + cacheChats = []; + this.chats = []; + for (let chat of chatsData.chats) { + chat.stored = false; + // 暂存至缓冲区 + cacheChats.push(JSON.parse(JSON.stringify(chat))); + // 加载期间显示只前15个会话做做样子,一切都为了加快初始化时间 + if (this.chats.length < 15) { + this.chats.push(chat); + } + } + this.privateMsgMaxId = chatsData.privateMsgMaxId || 0; + this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; + }, + openChat(chatInfo) { + let chats = this.curChats; + let chat = null; + for (let idx in chats) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId === chatInfo.targetId) { + chat = chats[idx]; + // 放置头部 + this.moveTop(idx) + break; + } + } + // 创建会话 + if (chat == null) { + chat = { + targetId: chatInfo.targetId, + type: chatInfo.type, + showName: chatInfo.showName, + headImage: chatInfo.headImage, + isDnd: chatInfo.isDnd, + lastContent: "", + lastSendTime: new Date().getTime(), + unreadCount: 0, + hotMinIdx: 0, + readedMessageIdx: 0, + messages: [], + atMe: false, + atAll: false, + stored: false + }; + chats.unshift(chat); + this.saveToStorage(); + } + }, + activeChat(idx) { + let chats = this.curChats; + if (idx >= 0) { + chats[idx].unreadCount = 0; + } + }, + resetUnreadCount(chatInfo) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId == chatInfo.targetId) { + chats[idx].unreadCount = 0; + chats[idx].atMe = false; + chats[idx].atAll = false; + chats[idx].stored = false; + this.saveToStorage(); + } + } + + }, + readedMessage(pos) { + let chat = this.findChatByFriend(pos.friendId); + if (!chat) return; + for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) { + let m = chat.messages[idx]; + if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { + // pos.maxId为空表示整个会话已读 + if (!pos.maxId || m.id <= pos.maxId) { + m.status = MESSAGE_STATUS.READED + chat.readedMessageIdx = idx; + chat.stored = false; + } + } + } + if (!chat.stored) { + this.saveToStorage(); + } + }, + cleanMessage(idx) { + let chat = this.curChats[idx]; + chat.lastContent = ''; + chat.hotMinIdx = 0; + chat.unreadCount = 0; + chat.atMe = false; + chat.atAll = false; + chat.stored = false + chat.messages = []; + this.saveToStorage(true); + }, + removeChat(idx) { + let chats = this.curChats; + chats[idx].delete = true; + chats[idx].stored = false; + this.saveToStorage(); + }, + removePrivateChat(userId) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'PRIVATE' && + chats[idx].targetId == userId) { + this.removeChat(idx); + } + } + }, + removeGroupChat(groupId) { + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'GROUP' && + chats[idx].targetId == groupId) { + this.removeChat(idx); + } + } + }, + moveTop(idx) { + if (this.loading) { + return; + } + let chats = this.curChats; + if (idx > 0) { + let chat = chats[idx]; + chats.splice(idx, 1); + chats.unshift(chat); + chat.lastSendTime = new Date().getTime(); + chat.stored = false; + this.saveToStorage(); + } + }, + insertMessage(msgInfo, chatInfo) { + // 获取对方id或群id + let type = chatInfo.type; + // 记录消息的最大id + if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) { + this.privateMsgMaxId = msgInfo.id; + } + if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) { + this.groupMsgMaxId = msgInfo.id; + } + // 如果是已存在消息,则覆盖旧的消息数据 + let chat = this.findChat(chatInfo); + let message = this.findMessage(chat, msgInfo); + if (message) { + console.log("message:", message) + Object.assign(message, msgInfo); + chat.stored = false; + this.saveToStorage(); + return; + } + // 会话列表内容 + if (msgInfo.type == MESSAGE_TYPE.IMAGE) { + chat.lastContent = "[图片]"; + } else if (msgInfo.type == MESSAGE_TYPE.FILE) { + chat.lastContent = "[文件]"; + } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) { + chat.lastContent = "[语音]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) { + chat.lastContent = "[语音通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) { + chat.lastContent = "[视频通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.TEXT || + msgInfo.type == MESSAGE_TYPE.RECALL || + msgInfo.type == MESSAGE_TYPE.TIP_TEXT) { + chat.lastContent = msgInfo.content; + } + chat.lastSendTime = msgInfo.sendTime; + chat.sendNickName = msgInfo.sendNickName; + // 未读加1 + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && + msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) { + chat.unreadCount++; + } + // 是否有人@我 + if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds && + msgInfo.status != MESSAGE_STATUS.READED) { + const userStore = useUserStore(); + let userId = userStore.userInfo.id; + if (msgInfo.atUserIds.indexOf(userId) >= 0) { + chat.atMe = true; + } + if (msgInfo.atUserIds.indexOf(-1) >= 0) { + chat.atAll = true; + } + } + // 间隔大于10分钟插入时间显示 + if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { + chat.messages.push({ + sendTime: msgInfo.sendTime, + type: MESSAGE_TYPE.TIP_TIME, + }); + chat.lastTimeTip = msgInfo.sendTime; + } + // 插入消息 + chat.messages.push(msgInfo); + chat.stored = false; + this.saveToStorage(); + }, + updateMessage(msgInfo, chatInfo) { + // 获取对方id或群id + let chat = this.findChat(chatInfo); + let message = this.findMessage(chat, msgInfo); + if (message) { + // 属性拷贝 + Object.assign(message, msgInfo); + chat.stored = false; + this.saveToStorage(); + } + }, + deleteMessage(msgInfo, chatInfo) { + let isColdMessage = false; + let chat = this.findChat(chatInfo); + let delIdx = -1; + for (let idx in chat.messages) { + // 已经发送成功的,根据id删除 + if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { + delIdx = idx; + break; + } + // 正在发送中的消息可能没有id,只有临时id + if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) { + delIdx = idx; + break; + } + } + if (delIdx >= 0) { + chat.messages.splice(delIdx, 1); + if (delIdx < chat.hotMinIdx) { + isColdMessage = true; + chat.hotMinIdx--; + } + if (delIdx < chat.readedMessageIdx) { + chat.readedMessageIdx--; + } + chat.stored = false; + this.saveToStorage(isColdMessage); + } + }, + recallMessage(msgInfo, chatInfo) { + let chat = this.findChat(chatInfo); + if (!chat) return; + let isColdMessage = false; + // 要撤回的消息id + let id = msgInfo.content; + let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName; + for (let idx in chat.messages) { + let m = chat.messages[idx]; + if (m.id && m.id == id) { + // 改造成一条提示消息 + m.status = MESSAGE_STATUS.RECALL; + m.content = name + "撤回了一条消息"; + m.type = MESSAGE_TYPE.TIP_TEXT + // 会话列表 + chat.lastContent = m.content; + chat.lastSendTime = msgInfo.sendTime; + chat.sendNickName = ''; + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) { + chat.unreadCount++; + } + isColdMessage = idx < chat.hotMinIdx; + } + // 被引用的消息也要撤回 + if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { + m.quoteMessage.content = "引用内容已撤回"; + m.quoteMessage.status = MESSAGE_STATUS.RECALL; + m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT + } + } + chat.stored = false; + this.saveToStorage(isColdMessage); + }, + updateChatFromFriend(friend) { + let chat = this.findChatByFriend(friend.id) + if (chat && (chat.headImage != friend.headImage || + chat.showName != friend.nickName)) { + // 更新会话中的群名和头像 + chat.headImage = friend.headImage; + chat.showName = friend.nickName; + chat.stored = false; + this.saveToStorage(); + } + }, + updateChatFromUser(user) { + let chat = this.findChatByFriend(user.id); + // 更新会话中的昵称和头像 + if (chat && (chat.headImage != user.headImageThumb || + chat.showName != user.nickName)) { + chat.headImage = user.headImageThumb; + chat.showName = user.nickName; + chat.stored = false; + this.saveToStorage(); + } + }, + updateChatFromGroup(group) { + let chat = this.findChatByGroup(group.id); + if (chat && (chat.headImage != group.headImageThumb || + chat.showName != group.showGroupName)) { + // 更新会话中的群名称和头像 + chat.headImage = group.headImageThumb; + chat.showName = group.showGroupName; + chat.stored = false; + this.saveToStorage(); + } + }, + setLoading(loading) { + this.loading = loading; + }, + setDnd(chatInfo, isDnd) { + let chat = this.findChat(chatInfo); + if (chat) { + chat.isDnd = isDnd; + } + }, + refreshChats() { + let chats = cacheChats || this.chats; + // 更新会话免打扰状态 + const friendStore = useFriendStore(); + const groupStore = useGroupStore(); + chats.forEach(chat => { + if (chat.type == 'PRIVATE') { + let friend = friendStore.findFriend(chat.targetId); + if (friend) { + chat.isDnd = friend.isDnd + } + } else if (chat.type == 'GROUP') { + let group = groupStore.findGroup(chat.targetId); + if (group) { + chat.isDnd = group.isDnd + } + } + }) + // 排序 + chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); + // #ifndef APP-PLUS + /** + * 由于h5和小程序的stroge只有5m,大约只能存储2w条消息,所以可能需要清理部分历史消息 + */ + this.fliterMessage(chats, 5000, 1000); + // #endif + // 记录热数据索引位置 + chats.forEach(chat => { + if(!chat.hotMinIdx || chat.hotMinIdx != chat.messages.length){ + chat.hotMinIdx = chat.messages.length; + chat.stored = false; + } + }); + // 将消息一次性装载回来 + this.chats = chats; + // 清空缓存,不再使用 + cacheChats = null; + // 消息持久化 + this.saveToStorage(true); + }, + fliterMessage(chats, maxTotalSize, maxPerChatSize) { + // 每个会话只保留maxPerChatSize条消息 + let remainTotalSize = 0; + chats.forEach(chat => { + if (chat.messages.length > maxPerChatSize) { + let idx = chat.messages.length - maxPerChatSize; + chat.messages = chat.messages.slice(idx); + } + remainTotalSize += chat.messages.length; + }) + // 保证消息总数不超过maxTotalSize条,否则继续清理 + if (remainTotalSize > maxTotalSize) { + this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); + } + }, + saveToStorage(withColdMessage) { + // 加载中不保存,防止卡顿 + if (this.loading) { + return; + } + const userStore = useUserStore(); + let userId = userStore.userInfo.id; + let key = "chats-app-" + userId; + let chatKeys = []; + // 按会话为单位存储,只存储有改动的会话 + this.chats.forEach((chat) => { + let chatKey = `${key}-${chat.type}-${chat.targetId}` + if (!chat.stored) { + if (chat.delete) { + uni.removeStorageSync(chatKey); + } else { + // 存储冷数据 + if (withColdMessage) { + let coldChat = Object.assign({}, chat); + coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); + uni.setStorageSync(chatKey, coldChat) + } + // 存储热消息 + let hotKey = chatKey + '-hot'; + let hotChat = Object.assign({}, chat); + hotChat.messages = chat.messages.slice(chat.hotMinIdx) + uni.setStorageSync(hotKey, hotChat); + } + chat.stored = true; + } + if (!chat.delete) { + chatKeys.push(chatKey); + } + }) + // 会话核心信息 + let chatsData = { + privateMsgMaxId: this.privateMsgMaxId, + groupMsgMaxId: this.groupMsgMaxId, + chatKeys: chatKeys + } + uni.setStorageSync(key, chatsData) + // 清理已删除的会话 + this.chats = this.chats.filter(chat => !chat.delete) + }, + clear(state) { + cacheChats = []; + this.chats = []; + this.privateMsgMaxId = 0; + this.groupMsgMaxId = 0; + this.loadingPrivateMsg = false; + this.loadingGroupMsg = false; + }, + loadChat() { + return new Promise((resolve, reject) => { + let userStore = useUserStore(); + let userId = userStore.userInfo.id; + let chatsData = uni.getStorageSync("chats-app-" + userId) + if (chatsData) { + if (chatsData.chatKeys) { + chatsData.chats = []; + chatsData.chatKeys.forEach(key => { + let coldChat = uni.getStorageSync(key); + let hotChat = uni.getStorageSync(key + '-hot'); + if (!coldChat && !hotChat) { + return; + } + // 防止消息一直处在发送中状态 + hotChat && hotChat.messages.forEach(msg => { + if (msg.status == MESSAGE_STATUS.SENDING) { + msg.status = MESSAGE_STATUS.FAILED + } + }) + // 冷热消息合并 + let chat = Object.assign({}, coldChat, hotChat); + if (hotChat && coldChat) { + chat.messages = coldChat.messages.concat(hotChat.messages) + } + // 历史版本没有readedMessageIdx字段,做兼容一下 + chat.readedMessageIdx = chat.readedMessageIdx || 0; + chatsData.chats.push(chat); + }) + } + this.initChats(chatsData); + } + resolve() + }) + } + }, + getters: { + curChats: (state) => { + if (cacheChats && state.loading) { + return cacheChats; + } + return state.chats; + }, + findChatIdx: (state) => (chat) => { + let chats = state.curChats; + for (let idx in chats) { + if (chats[idx].type == chat.type && + chats[idx].targetId === chat.targetId) { + chat = state.chats[idx]; + return idx; + } + } + }, + findChat: (state) => (chat) => { + let chats = state.curChats; + let idx = state.findChatIdx(chat); + return chats[idx]; + }, + findChatByFriend: (state) => (fid) => { + return state.curChats.find(chat => chat.type == 'PRIVATE' && + chat.targetId == fid) + }, + findChatByGroup: (state) => (gid) => { + return state.curChats.find(chat => chat.type == 'GROUP' && + chat.targetId == gid) + }, + findMessage: (state) => (chat, msgInfo) => { + if (!chat) { + return null; + } + // 通过id判断 + if (msgInfo.id) { + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (m.id && msgInfo.id == m.id) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.id && m.id < msgInfo.id) { + break; + } + } + } + // 正在发送中的临时消息可能没有id,只有tmpId + if (msgInfo.selfSend && msgInfo.tmpId) { + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (!m.selfSend || !m.tmpId) { + continue; + } + if (msgInfo.tmpId == m.tmpId) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.tmpId && m.tmpId < msgInfo.tmpId) { + break; + } + } + } + return null; + } + } +}); \ No newline at end of file diff --git a/im-uniapp/store/configStore.js b/im-uniapp/store/configStore.js new file mode 100644 index 0000000..0048818 --- /dev/null +++ b/im-uniapp/store/configStore.js @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia'; +import http from '../common/request' + +export default defineStore('configStore', { + state: () => { + return { + appInit: false, + webrtc: {} + } + }, + actions: { + setConfig(config) { + this.webrtc = config.webrtc; + }, + setAppInit(appInit) { + this.appInit = appInit; + }, + clear() { + this.webrtc = {}; + }, + loadConfig() { + return new Promise((resolve, reject) => { + http({ + url: '/system/config', + method: 'GET' + }).then((config) => { + console.log("系统配置", config) + this.setConfig(config); + resolve(); + }).catch((res) => { + reject(res); + }); + }) + } + } +}) \ No newline at end of file diff --git a/im-uniapp/store/friendStore.js b/im-uniapp/store/friendStore.js new file mode 100644 index 0000000..c846e75 --- /dev/null +++ b/im-uniapp/store/friendStore.js @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia'; +import http from '../common/request' +import { TERMINAL_TYPE } from '../common/enums.js' + +export default defineStore('friendStore', { + state: () => { + return { + friendMap: new Map(), + timer: null + } + }, + actions: { + setFriends(friends) { + this.friendMap.clear(); + friends.forEach(f => this.friendMap.set(f.id, f)) + }, + updateFriend(friend) { + let f = this.findFriend(friend.id); + friend.online = f.online; + friend.onlineWeb = f.onlineWeb; + friend.onlineApp = f.onlineApp; + this.friendMap.set(friend.id, friend) + }, + removeFriend(id) { + this.friendMap.get(id).deleted = true; + }, + addFriend(friend) { + this.friendMap.set(friend.id, friend) + }, + setOnlineStatus(onlineTerminals) { + this.friends.forEach((f) => { + let userTerminal = onlineTerminals.find((o) => f.id == o.userId); + if (userTerminal) { + f.online = true; + f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB) >= 0 + f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP) >= 0 + } else { + f.online = false; + f.onlineWeb = false; + f.onlineApp = false; + } + }); + }, + refreshOnlineStatus() { + let userIds = this.friends.filter((f) => !f.deleted).map((f) => f.id); + if (userIds.length == 0) { + return; + } + http({ + url: '/user/terminal/online?userIds=' + userIds.join(','), + method: 'GET' + }).then((onlineTerminals) => { + this.setOnlineStatus(onlineTerminals); + }) + // 30s后重新拉取 + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.refreshOnlineStatus(); + }, 30000) + }, + setDnd(id, isDnd) { + this.friendMap.get(id).isDnd = isDnd; + }, + clear() { + this.friendMap.clear(); + clearTimeout(this.timer); + this.timer = null; + }, + loadFriend() { + return new Promise((resolve, reject) => { + http({ + url: '/friend/list', + method: 'GET' + }).then((friends) => { + this.setFriends(friends); + this.refreshOnlineStatus(); + resolve() + }).catch((res) => { + reject(); + }) + }); + } + }, + getters: { + friends: (state) => { + return Array.from(state.friendMap.values()); + }, + findFriend: (state) => (userId) => { + return state.friendMap.get(userId); + }, + isFriend: (state) => (userId) => { + let f = state.findFriend(userId) + return f && !f.deleted + } + } +}) \ No newline at end of file diff --git a/im-uniapp/store/groupStore.js b/im-uniapp/store/groupStore.js new file mode 100644 index 0000000..3409b10 --- /dev/null +++ b/im-uniapp/store/groupStore.js @@ -0,0 +1,56 @@ +import { defineStore } from 'pinia'; +import http from '@/common/request'; + +export default defineStore('groupStore', { + state: () => { + return { + groupMap: new Map() + } + }, + actions: { + setGroups(groups) { + this.groupMap.clear(); + groups.forEach(g => this.groupMap.set(g.id, g)) + }, + addGroup(group) { + this.groupMap.set(group.id, group); + }, + removeGroup(id) { + this.groupMap.get(id).quit = true; + }, + updateGroup(group) { + this.groupMap.set(group.id, group); + }, + setDnd(id, isDnd) { + this.groupMap.get(id).isDnd = isDnd; + }, + clear() { + this.groupMap.clear(); + }, + loadGroup() { + return new Promise((resolve, reject) => { + http({ + url: '/group/list', + method: 'GET' + }).then((groups) => { + this.setGroups(groups); + resolve(); + }).catch((res) => { + reject(res); + }) + }); + } + }, + getters: { + groups: (state) => { + return Array.from(state.groupMap.values()); + }, + findGroup: (state) => (id) => { + return state.groupMap.get(id); + }, + isGroup: (state) => (id) => { + let group = state.findGroup(id); + return group && !group.quit + } + } +}) \ No newline at end of file diff --git a/im-uniapp/store/userStore.js b/im-uniapp/store/userStore.js new file mode 100644 index 0000000..7543bd0 --- /dev/null +++ b/im-uniapp/store/userStore.js @@ -0,0 +1,31 @@ +import { defineStore } from 'pinia'; +import http from '../common/request' + +export default defineStore('userStore', { + state: () => { + return { + userInfo: {} + } + }, + actions: { + setUserInfo(userInfo) { + this.userInfo = userInfo; + }, + clear() { + this.userInfo = {}; + }, + loadUser() { + return new Promise((resolve, reject) => { + http({ + url: '/user/self', + method: 'GET' + }).then((userInfo) => { + this.setUserInfo(userInfo); + resolve(); + }).catch((res) => { + reject(res); + }); + }) + } + } +}) \ No newline at end of file diff --git a/im-uniapp/uni.promisify.adaptor.js b/im-uniapp/uni.promisify.adaptor.js new file mode 100644 index 0000000..47fbce1 --- /dev/null +++ b/im-uniapp/uni.promisify.adaptor.js @@ -0,0 +1,10 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => res[0] ? reject(res[0]) : resolve(res[1])); + }); + }, +}); \ No newline at end of file diff --git a/im-uniapp/uni.scss b/im-uniapp/uni.scss new file mode 100644 index 0000000..f6d11bf --- /dev/null +++ b/im-uniapp/uni.scss @@ -0,0 +1,107 @@ +@import '@/uni_modules/uview-plus/theme.scss'; +@import '@/uni_modules/uni-scss/variables.scss'; +@import '@/im-var.scss'; + +// 修改uni-ui主题 +$uni-primary: $im-color-primary; +$uni-success: $im-color-success; +$uni-warning: $im-color-warning; +$uni-error: $im-color-danger; +$uni-info: $im-color-info; + +// $uni-main-color: #3a3a3a; // 主要文字 +// $uni-base-color: #6a6a6a; // 常规文字 +// $uni-secondary-color: #909399; // 次要文字 +// $uni-extra-color: #c7c7c7; // 辅助说明 + +// // 边框颜色 +// $uni-border-1: #F0F0F0; +// $uni-border-2: #EDEDED; +// $uni-border-3: #DCDCDC; +// $uni-border-4: #B9B9B9; + +// // 常规色 +// $uni-black: #000000; +// $uni-white: #ffffff; +// $uni-transparent: rgba($color: #000000, $alpha: 0); + +// // 背景色 +// $uni-bg-color: #f7f7f7; + +// /* 水平间距 */ +// $uni-spacing-sm: 8px; +// $uni-spacing-base: 15px; +// $uni-spacing-lg: 30px; + +// // 阴影 +// $uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +// $uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +// $uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// // 蒙版 +// $uni-mask: rgba($color: #000000, $alpha: 0.4); + + + + +/**************************************************/ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable: #c0c0c0; +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/im-uniapp/uni_modules/lsj-upload/changelog.md b/im-uniapp/uni_modules/lsj-upload/changelog.md new file mode 100644 index 0000000..090bca0 --- /dev/null +++ b/im-uniapp/uni_modules/lsj-upload/changelog.md @@ -0,0 +1,120 @@ +## 2.3.2(2024-06-13) +问题修复:2.3.1版本引起的部分设备不支持findLastIndex问题 +## 2.3.1(2024-05-20) +修复:文件不去重时返回文件列表name与组件内置列表key不一致问题。 +## 2.3.0(2024-05-20) +优化:1:增加属性distinct【选择文件是否去重】、2:对show/hide函数增加uni.$emit事件监听,若页面存在多个上传组件时,可通过uni.$emit控制所有上传组件webview透明层是否显示。 +## 2.2.9(2023-06-01) +优化:将是否多选与count字段解绑(原逻辑是count>1为允许多选),改为新增multiple属性控制是否多选。 +## 2.2.8(2023-06-01) +修复上版本提交时accept测试值未删除导致h5端只能选择图片的问题。 +## 2.2.7(2023-05-06) +应群友建议,当instantly为true时,触发change事件后延迟1000毫秒再自动上传,方便动态修改参数,其实个人还是建议想在change事件动态设置参数的伙伴将instantly设置为false,修改参数后手动调用upload() +## 2.2.6(2023-02-09) +修复多个文件同时选择时返回多次change回调的问题 +## 2.2.5(2022-12-27) +1.修复多选文件时未能正常校验数量的问题; +2.app端与H5端支持单选或多选文件,通过count数量控制,超过1开启多选。 +## 2.2.4(2022-12-27) +1.修复多选文件时未能正常校验数量的问题; +2.app端修复多选只取到第一个文件的问题。 +## 2.2.3(2022-12-06) +修复手动调用show()导致count失效的问题 +## 2.2.2(2022-12-01) +Vue3自行修改兼容 +## 2.2.1(2022-10-19) +修复childId警告提示 +## 2.2.0(2022-10-10) +更新app端webview窗口参数clidId,默认值添加时间戳保证唯一性 +## 2.1.9(2022-07-13) +[修复] app端选择文件后初始化设置的文件列表被清空问题 +## 2.1.8(2022-07-13) +[新增] ref方法初始化文件列表,用于已提交后再次编辑时需带入已上传文件:setFiles(files),可传入数组或Map对象,传入格式请与组件选择返回格式保持一致,且name为必须属性。 +## 2.1.7(2022-07-12) +修复ios端偶现创建webview初始化参数未生效的问题 +## 2.1.6(2022-07-11) +[修复]:修复上个版本更新导致nvue窗口组件不能选择文件的问题; +[新增]: +1.应群友建议(填写禁止格式太多)格式限制formats由原来填写禁止选择的格式改为填写允许被选择的格式; +2.应群友建议(增加上传结束回调事件),上传结束回调事件@uploadEnd +3.如能帮到你请留下你的免费好评,组件使用过程中有问题可以加QQ群交流,至于Map对象怎么使用这类前端基础问题请自行百度 +## 2.1.5(2022-07-01) +app端组件销毁时添加自动销毁webview功能,避免v-if销毁组件的情况控件还能被点击的问题 +## 2.1.4(2022-07-01) +修复小程序端回显问题 +## 2.1.3(2022-06-30) +回调事件返回参数新增path字段(文件临时地址),用于回显 +## 2.1.2(2022-06-16) +修复APP端Tabbar窗口无法选择文件的问题 +## 2.1.1(2022-06-16) +优化: +1.组件优化为允许在v-if中使用; +2.允许option直接在data赋值,不再强制在onRead中初始化; +## 2.1.0(2022-06-13) +h5 pc端更改为单次可多选 +## 2.0.9(2022-06-10) +更新演示内容,部分同学不知道怎么获取服务端返回的数据 +## 2.0.8(2022-06-09) +优化动态更新上传参数函数,具体查看下方说明:动态更新参数演示 +## 2.0.7(2022-06-07) +新增wxFileType属性,用于小程序端选择附件时可选文件类型 +## 2.0.6(2022-06-07) +修复小程序端真机选择文件提示失败的问题 +## 2.0.5(2022-06-02) +优化小程序端调用hide()后未阻止触发文件选择问题 +## 2.0.4(2022-06-01) +优化APP端选择器初始定位 +## 2.0.3(2022-05-31) +修复nvue窗口选择文件报错问题 +## 2.0.2(2022-05-20) +修复ios端opiton设置过早未传入webview导致不自动上传问题 +## 2.0.1(2022-05-19) +修复APP端子窗口点击选择文件不响应问题 +## 2.0.0(2022-05-18) +此次组件更新至2.0版本,与1.0版本使用上略有差异,已使用1.0的同学请自行斟酌是否需要升级! +部分差异: +一、 2.0新增异步触发上传功能; +二、2.0新增文件批量上传功能; +三、2.0优化option,剔除属性,只保留上传接口所需字段,且允许异步更改option的值; +四、组件增加size(文件大小限制)、count(文件个数限制)、formats(文件后缀限制)、accept(文件类型限制)、instantly(是否立即自动上传)、debug(日志打印)等属性; +五、回调事件取消input事件、callback事件,新增change事件和progress事件; +六、ref事件新增upload事件、clear事件; +七、优化组件代码,show和hide函数改为显示隐藏,不再重复开关webview; + +## 1.2.3(2022-03-22) +修复Demo里传入待完善功能[手动上传属性manual=true]导致不自动上传的问题,手动提交上传待下个版本更新 +## 1.2.2(2022-02-21) +修复上版本APP优化导致H5和小程序端不自动初始化的问题,此次更新仅修复此问题。异步提交功能下个版本更新~ +## 1.2.1(2022-01-25) +QQ1群已满,已开放2群:469580165 +## 1.2.0(2021-12-09) +优化APP端页面中DOM重排后每次需要重新定位的问题 +## 1.1.1(2021-12-09) +优化,与上版本使用方式有改变,请检查后确认是否需要更新,create更名为show, close更名为hide,取消初始化时手动create, 传参方式改为props=>option +## 1.1.0(2021-12-09) +新增refresh方法,用于DOM发生重排时重新定位控件(APP端) +## 1.0.9(2021-07-15) +修复上传进度未同步渲染,直接返回100%的BUG +## 1.0.8(2021-07-12) +修复H5端传入height和width未生效的bug +## 1.0.7(2021-07-07) +修复h5和小程序端上传完成callback未返回fileName字段问题 +## 1.0.6(2021-07-07) +修复h5端提示信息debug +## 1.0.5(2021-06-29) +感谢小伙伴找出bug,上传成功回调success未置为true,已修复 +## 1.0.4(2021-06-28) +新增兼容APP,H5,小程序手动关闭控件,关闭后不再弹出文件选择框,需要重新create再次开启 +## 1.0.3(2021-06-28) +close增加条件编译,除app端外不需要close +## 1.0.2(2021-06-28) +1.修复页面滚动位置后再create控件导致控件位置不正确的问题; +2.修复nvue无法create控件; +3.示例项目新增nvue使用案例; +## 1.0.1(2021-06-28) +因为有的朋友不清楚app端切换tab时应该怎么处理webview,现重新上传一版示例项目,需要做tab切换的朋友可以导入示例项目查看 +## 1.0.0(2021-06-25) +此插件为l-file插件中上传功能改版,更新内容为: +1. 按钮内嵌入页面,不再强制固定底部,可跟随页面滚动 +2.无需再单独弹框点击上传,减去中间层 +3.通过slot自定义按钮样式 diff --git a/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/LsjFile.js b/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/LsjFile.js new file mode 100644 index 0000000..a86233e --- /dev/null +++ b/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/LsjFile.js @@ -0,0 +1,414 @@ +export class LsjFile { + constructor(data) { + this.dom = null; + // files.type = waiting(等待上传)|| loading(上传中)|| success(成功) || fail(失败) + this.files = new Map(); + this.debug = data.debug || false; + this.id = data.id; + this.width = data.width; + this.height = data.height; + this.option = data.option; + this.instantly = data.instantly; + this.prohibited = data.prohibited; + this.onchange = data.onchange; + this.onprogress = data.onprogress; + this.uploadHandle = this._uploadHandle; + // #ifdef MP-WEIXIN + this.uploadHandle = this._uploadHandleWX; + // #endif + } + + + /** + * 创建File节点 + * @param {string}path webview地址 + */ + create(path) { + if (!this.dom) { + // #ifdef H5 + let dom = document.createElement('input'); + dom.type = 'file' + dom.value = '' + dom.style.height = this.height + dom.style.width = this.width + dom.style.position = 'absolute' + dom.style.top = 0 + dom.style.left = 0 + dom.style.right = 0 + dom.style.bottom = 0 + dom.style.opacity = 0 + dom.style.zIndex = 999 + dom.accept = this.prohibited.accept; + if (this.prohibited.multiple) { + dom.multiple = 'multiple'; + } + dom.onchange = event => { + for (let file of event.target.files) { + if (this.files.size >= this.prohibited.count) { + this.toast(`只允许上传${this.prohibited.count}个文件`); + this.dom.value = ''; + break; + } + this.addFile(file); + } + + this._uploadAfter(); + + this.dom.value = ''; + }; + this.dom = dom; + // #endif + + // #ifdef APP-PLUS + let styles = { + top: '-200px', + left: 0, + width: '1px', + height: '200px', + background: 'transparent' + }; + let extras = { + debug: this.debug, + instantly: this.instantly, + prohibited: this.prohibited, + } + this.dom = plus.webview.create(path, this.id, styles,extras); + this.setData(this.option); + this._overrideUrlLoading(); + // #endif + return this.dom; + } + } + + + /** + * 设置上传参数 + * @param {object|string}name 上传参数,支持a.b 和 a[b] + */ + setData() { + let [name,value = ''] = arguments; + if (typeof name === 'object') { + Object.assign(this.option,name); + } + else { + this._setValue(this.option,name,value); + } + + this.debug&&console.log(JSON.stringify(this.option)); + + // #ifdef APP-PLUS + this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`); + // #endif + } + + /** + * 上传 + * @param {string}name 文件名称 + */ + async upload(name='') { + if (!this.option.url) { + throw Error('未设置上传地址'); + } + + // #ifndef APP-PLUS + if (name && this.files.has(name)) { + await this.uploadHandle(this.files.get(name)); + } + else { + for (let item of this.files.values()) { + if (item.type === 'waiting' || item.type === 'fail') { + await this.uploadHandle(item); + } + } + } + // #endif + + // #ifdef APP-PLUS + this.dom&&this.dom.evalJS(`vm.upload('${name}')`); + // #endif + } + + // 选择文件change + addFile(file,isCallChange) { + + let name = file.name; + this.debug&&console.log('文件名称',name,'大小',file.size); + + if (file) { + // 限制文件格式 + let path = ''; + let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase(); + let formats = this.prohibited.formats.toLowerCase(); + + // #ifndef MP-WEIXIN + path = URL.createObjectURL(file); + // #endif + // #ifdef MP-WEIXIN + path = file.path; + // #endif + if (formats&&!formats.includes(suffix)) { + this.toast(`不支持上传${suffix.toUpperCase()}格式文件`); + return false; + } + // 限制文件大小 + if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) { + this.toast(`附件大小请勿超过${this.prohibited.size}M`) + return false; + } + + try{ + if (!this.prohibited.distinct) { + let homonymIndex = [...this.files.keys()].findIndex(item=>{ + return (item.substring(0,item.lastIndexOf("("))||item.substring(0,item.lastIndexOf("."))) == name.substring(0,name.lastIndexOf(".")) && + item.substring(item.lastIndexOf(".")+1).toLowerCase() === suffix; + }) + if (homonymIndex > -1) { + name = `${name.substring(0,name.lastIndexOf("."))}(${homonymIndex+1}).${suffix}`; + } + } + }catch(e){ + //TODO handle the exception + } + + this.files.set(name,{file,path,name: name,size: file.size,progress: 0,type: 'waiting'}); + return true; + } + } + + /** + * 移除文件 + * @param {string}name 不传name默认移除所有文件,传入name移除指定name的文件 + */ + clear(name='') { + // #ifdef APP-PLUS + this.dom&&this.dom.evalJS(`vm.clear('${name}')`); + // #endif + + if (!name) { + this.files.clear(); + } + else { + this.files.delete(name); + } + return this.onchange(this.files); + } + + /** + * 提示框 + * @param {string}msg 轻提示内容 + */ + toast(msg) { + uni.showToast({ + title: msg, + icon: 'none' + }); + } + + /** + * 微信小程序选择文件 + * @param {number}count 可选择文件数量 + */ + chooseMessageFile(type,count) { + wx.chooseMessageFile({ + count: count, + type: type, + success: ({ tempFiles }) => { + for (let file of tempFiles) { + this.addFile(file); + } + this._uploadAfter(); + }, + fail: () => { + this.toast(`打开失败`); + } + }) + } + + _copyObject(obj) { + if (typeof obj !== "undefined") { + return JSON.parse(JSON.stringify(obj)); + } else { + return obj; + } + } + + /** + * 自动根据字符串路径设置对象中的值 支持.和[] + * @param {Object} dataObj 数据源 + * @param {String} name 支持a.b 和 a[b] + * @param {String} value 值 + * setValue(dataObj, name, value); + */ + _setValue(dataObj, name, value) { + // 通过正则表达式 查找路径数据 + let dataValue; + if (typeof value === "object") { + dataValue = this._copyObject(value); + } else { + dataValue = value; + } + let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g"); + const patten = name.match(regExp); + // 遍历路径 逐级查找 最后一级用于直接赋值 + for (let i = 0; i < patten.length - 1; i++) { + let keyName = patten[i]; + if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {}; + dataObj = dataObj[keyName]; + } + // 最后一级 + dataObj[patten[patten.length - 1]] = dataValue; + this.debug&&console.log('参数更新后',JSON.stringify(this.option)); + } + + _uploadAfter() { + this.onchange(this.files); + setTimeout(()=>{ + this.instantly&&this.upload(); + },1000) + } + + _overrideUrlLoading() { + this.dom.overrideUrlLoading({ mode: 'reject' }, e => { + let {retype,item,files,end} = this._getRequest( + e.url + ); + let _this = this; + switch (retype) { + case 'updateOption': + this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`); + break + case 'change': + try { + _this.files = new Map([..._this.files,...JSON.parse(unescape(files))]); + } catch (e) { + return console.error('出错了,请检查代码') + } + _this.onchange(_this.files); + break + case 'progress': + try { + item = JSON.parse(unescape(item)); + } catch (e) { + return console.error('出错了,请检查代码') + } + _this._changeFilesItem(item,end); + break + default: + break + } + }) + } + + _getRequest(url) { + let theRequest = new Object() + let index = url.indexOf('?') + if (index != -1) { + let str = url.substring(index + 1) + let strs = str.split('&') + for (let i = 0; i < strs.length; i++) { + theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1]) + } + } + return theRequest + } + + _changeFilesItem(item,end=false) { + this.debug&&console.log('onprogress',JSON.stringify(item)); + this.onprogress(item,end); + this.files.set(item.name,item); + } + + _uploadHandle(item) { + item.type = 'loading'; + delete item.responseText; + return new Promise((resolve,reject)=>{ + this.debug&&console.log('option',JSON.stringify(this.option)); + let {url,name,method='POST',header,formData} = this.option; + let form = new FormData(); + for (let keys in formData) { + form.append(keys, formData[keys]) + } + form.append(name, item.file); + let xmlRequest = new XMLHttpRequest(); + xmlRequest.open(method, url, true); + for (let keys in header) { + xmlRequest.setRequestHeader(keys, header[keys]) + } + + xmlRequest.upload.addEventListener( + 'progress', + event => { + if (event.lengthComputable) { + let progress = Math.ceil((event.loaded * 100) / event.total) + if (progress <= 100) { + item.progress = progress; + this._changeFilesItem(item); + } + } + }, + false + ); + + xmlRequest.ontimeout = () => { + console.error('请求超时') + item.type = 'fail'; + this._changeFilesItem(item,true); + return resolve(false); + } + + xmlRequest.onreadystatechange = ev => { + if (xmlRequest.readyState == 4) { + if (xmlRequest.status == 200) { + this.debug&&console.log('上传完成:' + xmlRequest.responseText) + item['responseText'] = xmlRequest.responseText; + item.type = 'success'; + this._changeFilesItem(item,true); + return resolve(true); + } else if (xmlRequest.status == 0) { + console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求') + } + console.error('--ERROR--:status = ' + xmlRequest.status) + item.type = 'fail'; + this._changeFilesItem(item,true); + return resolve(false); + } + } + xmlRequest.send(form) + }); + } + + _uploadHandleWX(item) { + item.type = 'loading'; + delete item.responseText; + return new Promise((resolve,reject)=>{ + this.debug&&console.log('option',JSON.stringify(this.option)); + let form = {filePath: item.file.path,...this.option }; + form['fail'] = ({ errMsg = '' }) => { + console.error('--ERROR--:' + errMsg) + item.type = 'fail'; + this._changeFilesItem(item,true); + return resolve(false); + } + form['success'] = res => { + if (res.statusCode == 200) { + this.debug&&console.log('上传完成,微信端返回不一定是字符串,根据接口返回格式判断是否需要JSON.parse:' + res.data) + item['responseText'] = res.data; + item.type = 'success'; + this._changeFilesItem(item,true); + return resolve(true); + } + item.type = 'fail'; + this._changeFilesItem(item,true); + return resolve(false); + } + + let xmlRequest = uni.uploadFile(form); + xmlRequest.onProgressUpdate(({ progress = 0 }) => { + if (progress <= 100) { + item.progress = progress; + this._changeFilesItem(item); + } + }) + }); + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue b/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue new file mode 100644 index 0000000..46ac296 --- /dev/null +++ b/im-uniapp/uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/im-uniapp/uni_modules/lsj-upload/hybrid/html/js/vue.min.js b/im-uniapp/uni_modules/lsj-upload/hybrid/html/js/vue.min.js new file mode 100644 index 0000000..97ac4cf --- /dev/null +++ b/im-uniapp/uni_modules/lsj-upload/hybrid/html/js/vue.min.js @@ -0,0 +1,8 @@ +/*! + * Vue.js v2.3.0 + * (c) 2014-2017 Evan You + * Released under the MIT License. + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Vue=t()}(this,function(){"use strict";function e(e){return void 0===e||null===e}function t(e){return void 0!==e&&null!==e}function n(e){return!0===e}function r(e){return"string"==typeof e||"number"==typeof e}function i(e){return null!==e&&"object"==typeof e}function o(e){return"[object Object]"===Ai.call(e)}function a(e){return"[object RegExp]"===Ai.call(e)}function s(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function c(e){var t=parseFloat(e);return isNaN(t)?e:t}function u(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}function f(e,t){return Si.call(e,t)}function p(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}function d(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n}function v(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function h(e,t){for(var n in t)e[n]=t[n];return e}function m(e){for(var t={},n=0;n=0&&$o[n].id>e.id;)n--;$o.splice(Math.max(n,Ao)+1,0,e)}else $o.push(e);Co||(Co=!0,ro($e))}}function Ae(e){To.clear(),Oe(e,To)}function Oe(e,t){var n,r,o=Array.isArray(e);if((o||i(e))&&Object.isExtensible(e)){if(e.__ob__){var a=e.__ob__.dep.id;if(t.has(a))return;t.add(a)}if(o)for(n=e.length;n--;)Oe(e[n],t);else for(r=Object.keys(e),n=r.length;n--;)Oe(e[r[n]],t)}}function Se(e,t,n){Eo.get=function(){return this[t][n]},Eo.set=function(e){this[t][n]=e},Object.defineProperty(e,n,Eo)}function Te(e){e._watchers=[];var t=e.$options;t.props&&Ee(e,t.props),t.methods&&Me(e,t.methods),t.data?je(e):E(e._data={},!0),t.computed&&Le(e,t.computed),t.watch&&Pe(e,t.watch)}function Ee(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[],o=!e.$parent;lo.shouldConvert=o;for(var a in t)!function(o){i.push(o);var a=U(o,t,n,e);j(r,o,a),o in e||Se(e,"_props",o)}(a);lo.shouldConvert=!0}function je(e){var t=e.$options.data;t=e._data="function"==typeof t?Ne(t,e):t||{},o(t)||(t={});for(var n=Object.keys(t),r=e.$options.props,i=n.length;i--;)r&&f(r,n[i])||$(n[i])||Se(e,"_data",n[i]);E(t,!0)}function Ne(e,t){try{return e.call(t)}catch(e){return C(e,t,"data()"),{}}}function Le(e,t){var n=e._computedWatchers=Object.create(null);for(var r in t){var i=t[r],o="function"==typeof i?i:i.get;n[r]=new So(e,o,g,jo),r in e||Ie(e,r,i)}}function Ie(e,t,n){"function"==typeof n?(Eo.get=De(t),Eo.set=g):(Eo.get=n.get?!1!==n.cache?De(t):n.get:g,Eo.set=n.set?n.set:g),Object.defineProperty(e,t,Eo)}function De(e){return function(){var t=this._computedWatchers&&this._computedWatchers[e];if(t)return t.dirty&&t.evaluate(),oo.target&&t.depend(),t.value}}function Me(e,t){e.$options.props;for(var n in t)e[n]=null==t[n]?g:d(t[n],e)}function Pe(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i=0||n.indexOf(e[i])<0)&&r.push(e[i]);return r}return e}function pt(e){this._init(e)}function dt(e){e.use=function(e){if(!e.installed){var t=v(arguments,1);return t.unshift(this),"function"==typeof e.install?e.install.apply(e,t):"function"==typeof e&&e.apply(null,t),e.installed=!0,this}}}function vt(e){e.mixin=function(e){this.options=B(this.options,e)}}function ht(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=n.cid,i=e._Ctor||(e._Ctor={});if(i[r])return i[r];var o=e.name||n.options.name,a=function(e){this._init(e)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=t++,a.options=B(n.options,e),a.super=n,a.options.props&&mt(a),a.options.computed&>(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,Di.forEach(function(e){a[e]=n[e]}),o&&(a.options.components[o]=a),a.superOptions=n.options,a.extendOptions=e,a.sealedOptions=h({},a.options),i[r]=a,a}}function mt(e){var t=e.options.props;for(var n in t)Se(e.prototype,"_props",n)}function gt(e){var t=e.options.computed;for(var n in t)Ie(e.prototype,n,t[n])}function yt(e){Di.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&o(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}function _t(e){return e&&(e.Ctor.options.name||e.tag)}function bt(e,t){return"string"==typeof e?e.split(",").indexOf(t)>-1:!!a(e)&&e.test(t)}function $t(e,t,n){for(var r in e){var i=e[r];if(i){var o=_t(i.componentOptions);o&&!n(o)&&(i!==t&&xt(i),e[r]=null)}}}function xt(e){e&&e.componentInstance.$destroy()}function wt(e){for(var n=e.data,r=e,i=e;t(i.componentInstance);)i=i.componentInstance._vnode,i.data&&(n=Ct(i.data,n));for(;t(r=r.parent);)r.data&&(n=Ct(n,r.data));return kt(n)}function Ct(e,n){return{staticClass:At(e.staticClass,n.staticClass),class:t(e.class)?[e.class,n.class]:n.class}}function kt(e){var n=e.class,r=e.staticClass;return t(r)||t(n)?At(r,Ot(n)):""}function At(e,t){return e?t?e+" "+t:e:t||""}function Ot(n){if(e(n))return"";if("string"==typeof n)return n;var r="";if(Array.isArray(n)){for(var o,a=0,s=n.length;a-1?ua[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:ua[e]=/HTMLUnknownElement/.test(t.toString())}function Et(e){if("string"==typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}function jt(e,t){var n=document.createElement(e);return"select"!==e?n:(t.data&&t.data.attrs&&void 0!==t.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n)}function Nt(e,t){return document.createElementNS(ia[e],t)}function Lt(e){return document.createTextNode(e)}function It(e){return document.createComment(e)}function Dt(e,t,n){e.insertBefore(t,n)}function Mt(e,t){e.removeChild(t)}function Pt(e,t){e.appendChild(t)}function Rt(e){return e.parentNode}function Ft(e){return e.nextSibling}function Bt(e){return e.tagName}function Ht(e,t){e.textContent=t}function Ut(e,t,n){e.setAttribute(t,n)}function Vt(e,t){var n=e.data.ref;if(n){var r=e.context,i=e.componentInstance||e.elm,o=r.$refs;t?Array.isArray(o[n])?l(o[n],i):o[n]===i&&(o[n]=void 0):e.data.refInFor?Array.isArray(o[n])&&o[n].indexOf(i)<0?o[n].push(i):o[n]=[i]:o[n]=i}}function zt(e,n){return e.key===n.key&&e.tag===n.tag&&e.isComment===n.isComment&&t(e.data)===t(n.data)&&Jt(e,n)}function Jt(e,n){if("input"!==e.tag)return!0;var r;return(t(r=e.data)&&t(r=r.attrs)&&r.type)===(t(r=n.data)&&t(r=r.attrs)&&r.type)}function Kt(e,n,r){var i,o,a={};for(i=n;i<=r;++i)o=e[i].key,t(o)&&(a[o]=i);return a}function qt(e,t){(e.data.directives||t.data.directives)&&Wt(e,t)}function Wt(e,t){var n,r,i,o=e===pa,a=t===pa,s=Zt(e.data.directives,e.context),c=Zt(t.data.directives,t.context),u=[],l=[];for(n in c)r=s[n],i=c[n],r?(i.oldValue=r.value,Yt(i,"update",t,e),i.def&&i.def.componentUpdated&&l.push(i)):(Yt(i,"bind",t,e),i.def&&i.def.inserted&&u.push(i));if(u.length){var f=function(){for(var n=0;n=0&&" "===(m=e.charAt(h));h--);m&&_a.test(m)||(l=!0)}}else void 0===o?(v=i+1,o=e.slice(0,i).trim()):t();if(void 0===o?o=e.slice(0,i).trim():0!==v&&t(),a)for(i=0;i=Bo}function gn(e){return 34===e||39===e}function yn(e){var t=1;for(zo=Vo;!mn();)if(e=hn(),gn(e))_n(e);else if(91===e&&t++,93===e&&t--,0===t){Jo=Vo;break}}function _n(e){for(var t=e;!mn()&&(e=hn())!==t;);}function bn(e,t,n){Ko=n;var r=t.value,i=t.modifiers,o=e.tag,a=e.attrsMap.type;if("select"===o)wn(e,r,i);else if("input"===o&&"checkbox"===a)$n(e,r,i);else if("input"===o&&"radio"===a)xn(e,r,i);else if("input"===o||"textarea"===o)Cn(e,r,i);else if(!Pi.isReservedTag(o))return pn(e,r,i),!1;return!0}function $n(e,t,n){var r=n&&n.number,i=ln(e,"value")||"null",o=ln(e,"true-value")||"true",a=ln(e,"false-value")||"false";an(e,"checked","Array.isArray("+t+")?_i("+t+","+i+")>-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),un(e,$a,"var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$c){$$i<0&&("+t+"=$$a.concat($$v))}else{$$i>-1&&("+t+"=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}}else{"+dn(t,"$$c")+"}",null,!0)}function xn(e,t,n){var r=n&&n.number,i=ln(e,"value")||"null";i=r?"_n("+i+")":i,an(e,"checked","_q("+t+","+i+")"),un(e,$a,dn(t,i),null,!0)}function wn(e,t,n){var r=n&&n.number,i='Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return '+(r?"_n(val)":"val")+"})",o="var $$selectedVal = "+i+";";o=o+" "+dn(t,"$event.target.multiple ? $$selectedVal : $$selectedVal[0]"),un(e,"change",o,null,!0)}function Cn(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,u=o?"change":"range"===r?ba:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=dn(t,l);c&&(f="if($event.target.composing)return;"+f),an(e,"value","("+t+")"),un(e,u,f,null,!0),(s||a||"number"===r)&&un(e,"blur","$forceUpdate()")}function kn(e){var n;t(e[ba])&&(n=zi?"change":"input",e[n]=[].concat(e[ba],e[n]||[]),delete e[ba]),t(e[$a])&&(n=Zi?"click":"change",e[n]=[].concat(e[$a],e[n]||[]),delete e[$a])}function An(e,t,n,r,i){if(n){var o=t,a=qo;t=function(n){null!==(1===arguments.length?o(n):o.apply(null,arguments))&&On(e,t,r,a)}}qo.addEventListener(e,t,Gi?{capture:r,passive:i}:r)}function On(e,t,n,r){(r||qo).removeEventListener(e,t,n)}function Sn(t,n){if(!e(t.data.on)||!e(n.data.on)){var r=n.data.on||{},i=t.data.on||{};qo=n.elm,kn(r),G(r,i,An,On,n.context)}}function Tn(n,r){if(!e(n.data.domProps)||!e(r.data.domProps)){var i,o,a=r.elm,s=n.data.domProps||{},c=r.data.domProps||{};t(c.__ob__)&&(c=r.data.domProps=h({},c));for(i in s)e(c[i])&&(a[i]="");for(i in c)if(o=c[i],"textContent"!==i&&"innerHTML"!==i||(r.children&&(r.children.length=0),o!==s[i]))if("value"===i){a._value=o;var u=null==o?"":String(o);En(a,r,u)&&(a.value=u)}else a[i]=o}}function En(e,t,n){return!e.composing&&("option"===t.tag||jn(e,n)||Nn(e,n))}function jn(e,t){return document.activeElement!==e&&e.value!==t}function Nn(e,n){var r=e.value,i=e._vModifiers;return t(i)&&i.number||"number"===e.type?c(r)!==c(n):t(i)&&i.trim?r.trim()!==n.trim():r!==n}function Ln(e){var t=In(e.style);return e.staticStyle?h(e.staticStyle,t):t}function In(e){return Array.isArray(e)?m(e):"string"==typeof e?Ca(e):e}function Dn(e,t){var n,r={};if(t)for(var i=e;i.componentInstance;)i=i.componentInstance._vnode,i.data&&(n=Ln(i.data))&&h(r,n);(n=Ln(e.data))&&h(r,n);for(var o=e;o=o.parent;)o.data&&(n=Ln(o.data))&&h(r,n);return r}function Mn(n,r){var i=r.data,o=n.data;if(!(e(i.staticStyle)&&e(i.style)&&e(o.staticStyle)&&e(o.style))){var a,s,c=r.elm,u=o.staticStyle,l=o.normalizedStyle||o.style||{},f=u||l,p=In(r.data.style)||{};r.data.normalizedStyle=t(p.__ob__)?h({},p):p;var d=Dn(r,!0);for(s in f)e(d[s])&&Oa(c,s,"");for(s in d)(a=d[s])!==f[s]&&Oa(c,s,null==a?"":a)}}function Pn(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Rn(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t);else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");e.setAttribute("class",n.trim())}}function Fn(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&h(t,ja(e.name||"v")),h(t,e),t}return"string"==typeof e?ja(e):void 0}}function Bn(e){Fa(function(){Fa(e)})}function Hn(e,t){(e._transitionClasses||(e._transitionClasses=[])).push(t),Pn(e,t)}function Un(e,t){e._transitionClasses&&l(e._transitionClasses,t),Rn(e,t)}function Vn(e,t,n){var r=zn(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===La?Ma:Ra,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=La,l=a,f=o.length):t===Ia?u>0&&(n=Ia,l=u,f=c.length):(l=Math.max(a,u),n=l>0?a>u?La:Ia:null,f=n?n===La?o.length:c.length:0),{type:n,timeout:l,propCount:f,hasTransform:n===La&&Ba.test(r[Da+"Property"])}}function Jn(e,t){for(;e.length1}function Yn(e,t){!0!==t.data.show&&qn(t)}function Qn(e,t,n){var r=t.value,i=e.multiple;if(!i||Array.isArray(r)){for(var o,a,s=0,c=e.options.length;s-1,a.selected!==o&&(a.selected=o);else if(y(er(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function Xn(e,t){for(var n=0,r=t.length;n=0&&a[i].lowerCasedTag!==s;i--);else i=0;if(i>=0){for(var c=a.length-1;c>=i;c--)t.end&&t.end(a[c].tag,n,r);a.length=i,o=i&&a[i-1].tag}else"br"===s?t.start&&t.start(e,[],!0,n,r):"p"===s&&(t.start&&t.start(e,[],!1,n,r),t.end&&t.end(e,n,r))}for(var i,o,a=[],s=t.expectHTML,c=t.isUnaryTag||Ni,u=t.canBeLeftOpenTag||Ni,l=0;e;){if(i=e,o&&Ns(o)){var f=o.toLowerCase(),p=Ls[f]||(Ls[f]=new RegExp("([\\s\\S]*?)(]*>)","i")),d=0,v=e.replace(p,function(e,n,r){return d=r.length,Ns(f)||"noscript"===f||(n=n.replace(//g,"$1").replace(//g,"$1")),t.chars&&t.chars(n),""});l+=e.length-v.length,e=v,r(f,l-d,l)}else{var h=e.indexOf("<");if(0===h){if(fs.test(e)){var m=e.indexOf("--\x3e");if(m>=0){n(m+3);continue}}if(ps.test(e)){var g=e.indexOf("]>");if(g>=0){n(g+2);continue}}var y=e.match(ls);if(y){n(y[0].length);continue}var _=e.match(us);if(_){var b=l;n(_[0].length),r(_[1],b,l);continue}var $=function(){var t=e.match(ss);if(t){var r={tagName:t[1],attrs:[],start:l};n(t[0].length);for(var i,o;!(i=e.match(cs))&&(o=e.match(os));)n(o[0].length),r.attrs.push(o);if(i)return r.unarySlash=i[1],n(i[0].length),r.end=l,r}}();if($){!function(e){var n=e.tagName,i=e.unarySlash;s&&("p"===o&&rs(n)&&r(o),u(n)&&o===n&&r(n));for(var l=c(n)||"html"===n&&"head"===o||!!i,f=e.attrs.length,p=new Array(f),d=0;d=0){for(w=e.slice(h);!(us.test(w)||ss.test(w)||fs.test(w)||ps.test(w)||(C=w.indexOf("<",1))<0);)h+=C,w=e.slice(h);x=e.substring(0,h),n(h)}h<0&&(x=e,e=""),t.chars&&x&&t.chars(x)}if(e===i){t.chars&&t.chars(e);break}}r()}function mr(e,t){var n=t?Rs(t):Ps;if(n.test(e)){for(var r,i,o=[],a=n.lastIndex=0;r=n.exec(e);){i=r.index,i>a&&o.push(JSON.stringify(e.slice(a,i)));var s=tn(r[1].trim());o.push("_s("+s+")"),a=i+r[0].length}return a0,Ki=Vi&&Vi.indexOf("edge/")>0,qi=Vi&&Vi.indexOf("android")>0,Wi=Vi&&/iphone|ipad|ipod|ios/.test(Vi),Zi=Vi&&/chrome\/\d+/.test(Vi)&&!Ki,Gi=!1;if(Ui)try{var Yi={};Object.defineProperty(Yi,"passive",{get:function(){Gi=!0}}),window.addEventListener("test-passive",null,Yi)}catch(e){}var Qi,Xi,eo=function(){return void 0===Qi&&(Qi=!Ui&&"undefined"!=typeof global&&"server"===global.process.env.VUE_ENV),Qi},to=Ui&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,no="undefined"!=typeof Symbol&&k(Symbol)&&"undefined"!=typeof Reflect&&k(Reflect.ownKeys),ro=function(){function e(){r=!1;var e=n.slice(0);n.length=0;for(var t=0;t1?v(n):n;for(var r=v(arguments,1),i=0,o=n.length;i1&&(t[n[0].trim()]=n[1].trim())}}),t}),ka=/^--/,Aa=/\s*!important$/,Oa=function(e,t,n){if(ka.test(t))e.style.setProperty(t,n);else if(Aa.test(n))e.style.setProperty(t,n.replace(Aa,""),"important");else{var r=Ta(t);if(Array.isArray(n))for(var i=0,o=n.length;iv?(f=e(i[g+1])?null:i[g+1].elm,y(n,f,i,d,g,o)):d>g&&b(n,r,p,v)}function w(r,i,o,a){if(r!==i){if(n(i.isStatic)&&n(r.isStatic)&&i.key===r.key&&(n(i.isCloned)||n(i.isOnce)))return i.elm=r.elm,void(i.componentInstance=r.componentInstance);var s,c=i.data;t(c)&&t(s=c.hook)&&t(s=s.prepatch)&&s(r,i);var u=i.elm=r.elm,l=r.children,f=i.children;if(t(c)&&h(i)){for(s=0;s',n.innerHTML.indexOf(t)>0}("\n"," "),ts=u("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),ns=u("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),rs=u("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),is=[/"([^"]*)"+/.source,/'([^']*)'+/.source,/([^\s"'=<>`]+)/.source],os=new RegExp("^\\s*"+/([^\s"'<>\/=]+)/.source+"(?:\\s*("+/(?:=)/.source+")\\s*(?:"+is.join("|")+"))?"),as="[a-zA-Z_][\\w\\-\\.]*",ss=new RegExp("^<((?:"+as+"\\:)?"+as+")"),cs=/^\s*(\/?)>/,us=new RegExp("^<\\/((?:"+as+"\\:)?"+as+")[^>]*>"),ls=/^]+>/i,fs=/^ + + + + + + + + + + + + + + {{ title }} + {{ subTitle }} + + + + {{ extra }} + + + + + + + + + + + + + + + + diff --git a/im-uniapp/uni_modules/uni-card/package.json b/im-uniapp/uni_modules/uni-card/package.json new file mode 100644 index 0000000..f16224d --- /dev/null +++ b/im-uniapp/uni_modules/uni-card/package.json @@ -0,0 +1,90 @@ +{ + "id": "uni-card", + "displayName": "uni-card 卡片", + "version": "1.3.1", + "description": "Card 组件,提供常见的卡片样式。", + "keywords": [ + "uni-ui", + "uniui", + "card", + "", + "卡片" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons", + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-card/readme.md b/im-uniapp/uni_modules/uni-card/readme.md new file mode 100644 index 0000000..7434e71 --- /dev/null +++ b/im-uniapp/uni_modules/uni-card/readme.md @@ -0,0 +1,12 @@ + + +## Card 卡片 +> **组件名:uni-card** +> 代码块: `uCard` + +卡片视图组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-collapse/changelog.md b/im-uniapp/uni_modules/uni-collapse/changelog.md new file mode 100644 index 0000000..292e4c7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-collapse/changelog.md @@ -0,0 +1,36 @@ +## 1.4.3(2022-01-25) +- 修复 初始化的时候 ,open 属性失效的bug +## 1.4.2(2022-01-21) +- 修复 微信小程序resize后组件收起的bug +## 1.4.1(2021-11-22) +- 修复 vue3中个别scss变量无法找到的问题 +## 1.4.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse) +## 1.3.3(2021-08-17) +- 优化 show-arrow 属性默认为true +## 1.3.2(2021-08-17) +- 新增 show-arrow 属性,控制是否显示右侧箭头 +## 1.3.1(2021-07-30) +- 优化 vue3下小程序事件警告的问题 +## 1.3.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.2.2(2021-07-21) +- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug +## 1.2.1(2021-07-21) +- 优化 组件示例 +## 1.2.0(2021-07-21) +- 新增 组件折叠动画 +- 新增 value\v-model 属性 ,动态修改面板折叠状态 +- 新增 title 插槽 ,可定义面板标题 +- 新增 border 属性 ,显示隐藏面板内容分隔线 +- 新增 title-border 属性 ,显示隐藏面板标题分隔线 +- 修复 resize 方法失效的Bug +- 修复 change 事件返回参数不正确的Bug +- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法 +## 1.1.7(2021-05-12) +- 新增 组件示例地址 +## 1.1.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.1.5(2021-02-05) +- 调整为uni_modules目录规范 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue b/im-uniapp/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue new file mode 100644 index 0000000..d62a6a7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue b/im-uniapp/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue new file mode 100644 index 0000000..384c39a --- /dev/null +++ b/im-uniapp/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue @@ -0,0 +1,147 @@ + + + diff --git a/im-uniapp/uni_modules/uni-collapse/package.json b/im-uniapp/uni_modules/uni-collapse/package.json new file mode 100644 index 0000000..65349cf --- /dev/null +++ b/im-uniapp/uni_modules/uni-collapse/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-collapse", + "displayName": "uni-collapse 折叠面板", + "version": "1.4.3", + "description": "Collapse 组件,可以折叠 / 展开的内容区域。", + "keywords": [ + "uni-ui", + "折叠", + "折叠面板", + "手风琴" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-collapse/readme.md b/im-uniapp/uni_modules/uni-collapse/readme.md new file mode 100644 index 0000000..bc758eb --- /dev/null +++ b/im-uniapp/uni_modules/uni-collapse/readme.md @@ -0,0 +1,12 @@ + + +## Collapse 折叠面板 +> **组件名:uni-collapse** +> 代码块: `uCollapse` +> 关联组件:`uni-collapse-item`、`uni-icons`。 + + +折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-combox/changelog.md b/im-uniapp/uni_modules/uni-combox/changelog.md new file mode 100644 index 0000000..23c2748 --- /dev/null +++ b/im-uniapp/uni_modules/uni-combox/changelog.md @@ -0,0 +1,15 @@ +## 1.0.1(2021-11-23) +- 优化 label、label-width 属性 +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox) +## 0.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.0.6(2021-05-12) +- 新增 组件示例地址 +## 0.0.5(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 0.0.4(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 0.0.3(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-combox/components/uni-combox/uni-combox.vue b/im-uniapp/uni_modules/uni-combox/components/uni-combox/uni-combox.vue new file mode 100644 index 0000000..d4cb79d --- /dev/null +++ b/im-uniapp/uni_modules/uni-combox/components/uni-combox/uni-combox.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-combox/package.json b/im-uniapp/uni_modules/uni-combox/package.json new file mode 100644 index 0000000..4a05c3f --- /dev/null +++ b/im-uniapp/uni_modules/uni-combox/package.json @@ -0,0 +1,90 @@ +{ + "id": "uni-combox", + "displayName": "uni-combox 组合框", + "version": "1.0.1", + "description": "可以选择也可以输入的表单项 ", + "keywords": [ + "uni-ui", + "uniui", + "combox", + "组合框", + "select" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-combox/readme.md b/im-uniapp/uni_modules/uni-combox/readme.md new file mode 100644 index 0000000..ffa2cc8 --- /dev/null +++ b/im-uniapp/uni_modules/uni-combox/readme.md @@ -0,0 +1,11 @@ + + +## Combox 组合框 +> **组件名:uni-combox** +> 代码块: `uCombox` + + +组合框组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-countdown/changelog.md b/im-uniapp/uni_modules/uni-countdown/changelog.md new file mode 100644 index 0000000..f25beef --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/changelog.md @@ -0,0 +1,24 @@ +## 1.2.2(2022-01-19) +- 修复 在微信小程序中样式不生效的bug +## 1.2.1(2022-01-18) +- 新增 update 方法 ,在动态更新时间后,刷新组件 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown) +## 1.1.3(2021-10-18) +- 重构 +- 新增 font-size 支持自定义字体大小 +## 1.1.2(2021-08-24) +- 新增 支持国际化 +## 1.1.1(2021-07-30) +- 优化 vue3下小程序事件警告的问题 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.5(2021-06-18) +- 修复 uni-countdown 重复赋值跳两秒的 bug +## 1.0.4(2021-05-12) +- 新增 组件示例地址 +## 1.0.3(2021-05-08) +- 修复 uni-countdown 不能控制倒计时的 bug +## 1.0.2(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json new file mode 100644 index 0000000..06309cb --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json @@ -0,0 +1,6 @@ +{ + "uni-countdown.day": "day", + "uni-countdown.h": "h", + "uni-countdown.m": "m", + "uni-countdown.s": "s" +} diff --git a/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json new file mode 100644 index 0000000..358cdd1 --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "uni-countdown.day": "天", + "uni-countdown.h": "时", + "uni-countdown.m": "分", + "uni-countdown.s": "秒" +} diff --git a/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json new file mode 100644 index 0000000..e5a63de --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "uni-countdown.day": "天", + "uni-countdown.h": "時", + "uni-countdown.m": "分", + "uni-countdown.s": "秒" +} diff --git a/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue new file mode 100644 index 0000000..1f8ef4e --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue @@ -0,0 +1,271 @@ + + + diff --git a/im-uniapp/uni_modules/uni-countdown/package.json b/im-uniapp/uni_modules/uni-countdown/package.json new file mode 100644 index 0000000..70e99ee --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-countdown", + "displayName": "uni-countdown 倒计时", + "version": "1.2.2", + "description": "CountDown 倒计时组件", + "keywords": [ + "uni-ui", + "uniui", + "countdown", + "倒计时" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-countdown/readme.md b/im-uniapp/uni_modules/uni-countdown/readme.md new file mode 100644 index 0000000..4bcb1aa --- /dev/null +++ b/im-uniapp/uni_modules/uni-countdown/readme.md @@ -0,0 +1,10 @@ + + +## CountDown 倒计时 +> **组件名:uni-countdown** +> 代码块: `uCountDown` + +倒计时组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-data-checkbox/changelog.md b/im-uniapp/uni_modules/uni-data-checkbox/changelog.md new file mode 100644 index 0000000..c7a468a --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-checkbox/changelog.md @@ -0,0 +1,45 @@ +## 1.0.3(2022-09-16) +- 可以使用 uni-scss 控制主题色 +## 1.0.2(2022-06-30) +- 优化 在 uni-forms 中的依赖注入方式 +## 1.0.1(2022-02-07) +- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox) +## 0.2.5(2021-08-23) +- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题 +## 0.2.4(2021-08-17) +- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +## 0.2.3(2021-08-11) +- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +## 0.2.2(2021-07-30) +- 优化 在uni-forms组件,与label不对齐的问题 +## 0.2.1(2021-07-27) +- 修复 单选默认值为0不能选中的Bug +## 0.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.1.11(2021-07-06) +- 优化 删除无用日志 +## 0.1.10(2021-07-05) +- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题 +## 0.1.9(2021-07-05) +- 修复 nvue 黑框样式问题 +## 0.1.8(2021-06-28) +- 修复 selectedTextColor 属性不生效的Bug +## 0.1.7(2021-06-02) +- 新增 map 属性,可以方便映射text/value属性 +## 0.1.6(2021-05-26) +- 修复 不关联服务空间的情况下组件报错的Bug +## 0.1.5(2021-05-12) +- 新增 组件示例地址 +## 0.1.4(2021-04-09) +- 修复 nvue 下无法选中的问题 +## 0.1.3(2021-03-22) +- 新增 disabled属性 +## 0.1.2(2021-02-24) +- 优化 默认颜色显示 +## 0.1.1(2021-02-24) +- 新增 支持nvue +## 0.1.0(2021-02-18) +- “暂无数据”显示居中 diff --git a/im-uniapp/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue b/im-uniapp/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue new file mode 100644 index 0000000..3c75d9f --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue @@ -0,0 +1,821 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-data-checkbox/package.json b/im-uniapp/uni_modules/uni-data-checkbox/package.json new file mode 100644 index 0000000..113c350 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-checkbox/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-data-checkbox", + "displayName": "uni-data-checkbox 数据选择器", + "version": "1.0.3", + "description": "通过数据驱动的单选框和复选框", + "keywords": [ + "uni-ui", + "checkbox", + "单选", + "多选", + "单选多选" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.1" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-load-more","uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-data-checkbox/readme.md b/im-uniapp/uni_modules/uni-data-checkbox/readme.md new file mode 100644 index 0000000..6eb253d --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-checkbox/readme.md @@ -0,0 +1,18 @@ + + +## DataCheckbox 数据驱动的单选复选框 +> **组件名:uni-data-checkbox** +> 代码块: `uDataCheckbox` + + +本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括: + +1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能 +2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验 +3. 本组件合并了单选多选 +4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性 + +在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-data-picker/changelog.md b/im-uniapp/uni_modules/uni-data-picker/changelog.md new file mode 100644 index 0000000..1dd79f7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/changelog.md @@ -0,0 +1,75 @@ +## 1.1.2(2023-04-11) +- 修复 更改 modelValue 报错的 bug +- 修复 v-for 未使用 key 值控制台 warning +## 1.1.1(2023-02-21) +- 修复代码合并时引发 value 属性为空时不渲染数据的问题 +## 1.1.0(2023-02-15) +- 修复 localdata 不支持动态更新的bug +## 1.0.9(2023-02-15) +- 修复 localdata 不支持动态更新的bug +## 1.0.8(2022-09-16) +- 可以使用 uni-scss 控制主题色 +## 1.0.7(2022-07-06) +- 优化 pc端图标位置不正确的问题 +## 1.0.6(2022-07-05) +- 优化 显示样式 +## 1.0.5(2022-07-04) +- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +## 1.0.4(2022-04-19) +- 修复 字节小程序 本地数据无法选择下一级的Bug +## 1.0.3(2022-02-25) +- 修复 nvue 不支持的 v-show 的 bug +## 1.0.2(2022-02-25) +- 修复 条件编译 nvue 不支持的 css 样式 +## 1.0.1(2021-11-23) +- 修复 由上个版本引发的map、v-model等属性不生效的bug +## 1.0.0(2021-11-19) +- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-picker](https://uniapp.dcloud.io/component/uniui/uni-data-picker) +## 0.4.9(2021-10-28) +- 修复 VUE2 v-model 概率无效的 bug +## 0.4.8(2021-10-27) +- 修复 v-model 概率无效的 bug +## 0.4.7(2021-10-25) +- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+ +- 修复 树型 uniCloud 数据类型为 int 时报错的 bug +## 0.4.6(2021-10-19) +- 修复 非 VUE3 v-model 为 0 时无法选中的 bug +## 0.4.5(2021-09-26) +- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效 +- 修复 readonly 为 true 时报错的 bug +## 0.4.4(2021-09-26) +- 修复 上一版本造成的 map 属性失效的 bug +- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略 +## 0.4.3(2021-09-24) +- 修复 某些情况下级联未触发的 bug +## 0.4.2(2021-09-23) +- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用 +- 新增 选项内容过长自动添加省略号 +## 0.4.1(2021-09-15) +- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段 +## 0.4.0(2021-07-13) +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.3.5(2021-06-04) +- 修复 无法加载云端数据的问题 +## 0.3.4(2021-05-28) +- 修复 v-model 无效问题 +- 修复 loaddata 为空数据组时加载时间过长问题 +- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点 +## 0.3.3(2021-05-12) +- 新增 组件示例地址 +## 0.3.2(2021-04-22) +- 修复 非树形数据有 where 属性查询报错的问题 +## 0.3.1(2021-04-15) +- 修复 本地数据概率无法回显时问题 +## 0.3.0(2021-04-07) +- 新增 支持云端非树形表结构数据 +- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题 +## 0.2.0(2021-03-15) +- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题 +## 0.1.9(2021-03-09) +- 修复 微信小程序某些情况下无法选择的问题 +## 0.1.8(2021-02-05) +- 优化 部分样式在 nvue 上的兼容表现 +## 0.1.7(2021-02-05) +- 调整为 uni_modules 目录规范 diff --git a/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js new file mode 100644 index 0000000..6ef26a2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue new file mode 100644 index 0000000..179a4e0 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js new file mode 100644 index 0000000..cfae22a --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js @@ -0,0 +1,622 @@ +export default { + props: { + localdata: { + type: [Array, Object], + default () { + return [] + } + }, + spaceInfo: { + type: Object, + default () { + return {} + } + }, + collection: { + type: String, + default: '' + }, + action: { + type: String, + default: '' + }, + field: { + type: String, + default: '' + }, + orderby: { + type: String, + default: '' + }, + where: { + type: [String, Object], + default: '' + }, + pageData: { + type: String, + default: 'add' + }, + pageCurrent: { + type: Number, + default: 1 + }, + pageSize: { + type: Number, + default: 500 + }, + getcount: { + type: [Boolean, String], + default: false + }, + getone: { + type: [Boolean, String], + default: false + }, + gettree: { + type: [Boolean, String], + default: false + }, + manual: { + type: Boolean, + default: false + }, + value: { + type: [Array, String, Number], + default () { + return [] + } + }, + modelValue: { + type: [Array, String, Number], + default () { + return [] + } + }, + preload: { + type: Boolean, + default: false + }, + stepSearh: { + type: Boolean, + default: true + }, + selfField: { + type: String, + default: '' + }, + parentField: { + type: String, + default: '' + }, + multiple: { + type: Boolean, + default: false + }, + map: { + type: Object, + default () { + return { + text: "text", + value: "value" + } + } + } + }, + data() { + return { + loading: false, + errorMessage: '', + loadMore: { + contentdown: '', + contentrefresh: '', + contentnomore: '' + }, + dataList: [], + selected: [], + selectedIndex: 0, + page: { + current: this.pageCurrent, + size: this.pageSize, + count: 0 + } + } + }, + computed: { + isLocalData() { + return !this.collection.length; + }, + isCloudData() { + return this.collection.length > 0; + }, + isCloudDataList() { + return (this.isCloudData && (!this.parentField && !this.selfField)); + }, + isCloudDataTree() { + return (this.isCloudData && this.parentField && this.selfField); + }, + dataValue() { + let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || + this.modelValue !== undefined); + return isModelValue ? this.modelValue : this.value; + }, + hasValue() { + if (typeof this.dataValue === 'number') { + return true + } + return (this.dataValue != null) && (this.dataValue.length > 0) + } + }, + created() { + this.$watch(() => { + var al = []; + ['pageCurrent', + 'pageSize', + 'spaceInfo', + 'value', + 'modelValue', + 'localdata', + 'collection', + 'action', + 'field', + 'orderby', + 'where', + 'getont', + 'getcount', + 'gettree' + ].forEach(key => { + al.push(this[key]) + }); + return al + }, (newValue, oldValue) => { + let needReset = false + for (let i = 2; i < newValue.length; i++) { + if (newValue[i] != oldValue[i]) { + needReset = true + break + } + } + if (newValue[0] != oldValue[0]) { + this.page.current = this.pageCurrent + } + this.page.size = this.pageSize + + this.onPropsChange() + }) + this._treeData = [] + }, + methods: { + onPropsChange() { + this._treeData = []; + }, + + // 填充 pickview 数据 + async loadData() { + if (this.isLocalData) { + this.loadLocalData(); + } else if (this.isCloudDataList) { + this.loadCloudDataList(); + } else if (this.isCloudDataTree) { + this.loadCloudDataTree(); + } + }, + + // 加载本地数据 + async loadLocalData() { + this._treeData = []; + this._extractTree(this.localdata, this._treeData); + + let inputValue = this.dataValue; + if (inputValue === undefined) { + return; + } + + if (Array.isArray(inputValue)) { + inputValue = inputValue[inputValue.length - 1]; + if (typeof inputValue === 'object' && inputValue[this.map.value]) { + inputValue = inputValue[this.map.value]; + } + } + + this.selected = this._findNodePath(inputValue, this.localdata); + }, + + // 加载 Cloud 数据 (单列) + async loadCloudDataList() { + if (this.loading) { + return; + } + this.loading = true; + + try { + let response = await this.getCommand(); + let responseData = response.result.data; + + this._treeData = responseData; + + this._updateBindData(); + this._updateSelected(); + + this.onDataChange(); + } catch (e) { + this.errorMessage = e; + } finally { + this.loading = false; + } + }, + + // 加载 Cloud 数据 (树形) + async loadCloudDataTree() { + if (this.loading) { + return; + } + this.loading = true; + + try { + let commandOptions = { + field: this._cloudDataPostField(), + where: this._cloudDataTreeWhere() + }; + if (this.gettree) { + commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`; + } + + let response = await this.getCommand(commandOptions); + let responseData = response.result.data; + + this._treeData = responseData; + this._updateBindData(); + this._updateSelected(); + + this.onDataChange(); + } catch (e) { + this.errorMessage = e; + } finally { + this.loading = false; + } + }, + + // 加载 Cloud 数据 (节点) + async loadCloudDataNode(callback) { + if (this.loading) { + return; + } + this.loading = true; + + try { + let commandOptions = { + field: this._cloudDataPostField(), + where: this._cloudDataNodeWhere() + }; + + let response = await this.getCommand(commandOptions); + let responseData = response.result.data; + + callback(responseData); + } catch (e) { + this.errorMessage = e; + } finally { + this.loading = false; + } + }, + + // 回显 Cloud 数据 + getCloudDataValue() { + if (this.isCloudDataList) { + return this.getCloudDataListValue(); + } + + if (this.isCloudDataTree) { + return this.getCloudDataTreeValue(); + } + }, + + // 回显 Cloud 数据 (单列) + getCloudDataListValue() { + // 根据 field's as value标识匹配 where 条件 + let where = []; + let whereField = this._getForeignKeyByField(); + if (whereField) { + where.push(`${whereField} == '${this.dataValue}'`) + } + + where = where.join(' || '); + + if (this.where) { + where = `(${this.where}) && (${where})` + } + + return this.getCommand({ + field: this._cloudDataPostField(), + where + }).then((res) => { + this.selected = res.result.data; + return res.result.data; + }); + }, + + // 回显 Cloud 数据 (树形) + getCloudDataTreeValue() { + return this.getCommand({ + field: this._cloudDataPostField(), + getTreePath: { + startWith: `${this.selfField}=='${this.dataValue}'` + } + }).then((res) => { + let treePath = []; + this._extractTreePath(res.result.data, treePath); + this.selected = treePath; + return treePath; + }); + }, + + getCommand(options = {}) { + /* eslint-disable no-undef */ + let db = uniCloud.database(this.spaceInfo) + + const action = options.action || this.action + if (action) { + db = db.action(action) + } + + const collection = options.collection || this.collection + db = db.collection(collection) + + const where = options.where || this.where + if (!(!where || !Object.keys(where).length)) { + db = db.where(where) + } + + const field = options.field || this.field + if (field) { + db = db.field(field) + } + + const orderby = options.orderby || this.orderby + if (orderby) { + db = db.orderBy(orderby) + } + + const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current + const size = options.pageSize !== undefined ? options.pageSize : this.page.size + const getCount = options.getcount !== undefined ? options.getcount : this.getcount + const getTree = options.gettree !== undefined ? options.gettree : this.gettree + + const getOptions = { + getCount, + getTree + } + if (options.getTreePath) { + getOptions.getTreePath = options.getTreePath + } + + db = db.skip(size * (current - 1)).limit(size).get(getOptions) + + return db + }, + + _cloudDataPostField() { + let fields = [this.field]; + if (this.parentField) { + fields.push(`${this.parentField} as parent_value`); + } + return fields.join(','); + }, + + _cloudDataTreeWhere() { + let result = [] + let selected = this.selected + let parentField = this.parentField + if (parentField) { + result.push(`${parentField} == null || ${parentField} == ""`) + } + if (selected.length) { + for (var i = 0; i < selected.length - 1; i++) { + result.push(`${parentField} == '${selected[i].value}'`) + } + } + + let where = [] + if (this.where) { + where.push(`(${this.where})`) + } + + if (result.length) { + where.push(`(${result.join(' || ')})`) + } + + return where.join(' && ') + }, + + _cloudDataNodeWhere() { + let where = [] + let selected = this.selected; + if (selected.length) { + where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`); + } + + where = where.join(' || '); + + if (this.where) { + return `(${this.where}) && (${where})` + } + + return where + }, + + _getWhereByForeignKey() { + let result = [] + let whereField = this._getForeignKeyByField(); + if (whereField) { + result.push(`${whereField} == '${this.dataValue}'`) + } + + if (this.where) { + return `(${this.where}) && (${result.join(' || ')})` + } + + return result.join(' || ') + }, + + _getForeignKeyByField() { + let fields = this.field.split(','); + let whereField = null; + for (let i = 0; i < fields.length; i++) { + const items = fields[i].split('as'); + if (items.length < 2) { + continue; + } + if (items[1].trim() === 'value') { + whereField = items[0].trim(); + break; + } + } + return whereField; + }, + + _updateBindData(node) { + const { + dataList, + hasNodes + } = this._filterData(this._treeData, this.selected) + + let isleaf = this._stepSearh === false && !hasNodes + + if (node) { + node.isleaf = isleaf + } + + this.dataList = dataList + this.selectedIndex = dataList.length - 1 + + if (!isleaf && this.selected.length < dataList.length) { + this.selected.push({ + value: null, + text: "请选择" + }) + } + + return { + isleaf, + hasNodes + } + }, + + _updateSelected() { + let dl = this.dataList + let sl = this.selected + let textField = this.map.text + let valueField = this.map.value + for (let i = 0; i < sl.length; i++) { + let value = sl[i].value + let dl2 = dl[i] + for (let j = 0; j < dl2.length; j++) { + let item2 = dl2[j] + if (item2[valueField] === value) { + sl[i].text = item2[textField] + break + } + } + } + }, + + _filterData(data, paths) { + let dataList = [] + let hasNodes = true + + dataList.push(data.filter((item) => { + return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '') + })) + for (let i = 0; i < paths.length; i++) { + let value = paths[i].value + let nodes = data.filter((item) => { + return item.parent_value === value + }) + + if (nodes.length) { + dataList.push(nodes) + } else { + hasNodes = false + } + } + + return { + dataList, + hasNodes + } + }, + + _extractTree(nodes, result, parent_value) { + let list = result || [] + let valueField = this.map.value + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + + let child = {} + for (let key in node) { + if (key !== 'children') { + child[key] = node[key] + } + } + if (parent_value !== null && parent_value !== undefined && parent_value !== '') { + child.parent_value = parent_value + } + result.push(child) + + let children = node.children + if (children) { + this._extractTree(children, result, node[valueField]) + } + } + }, + + _extractTreePath(nodes, result) { + let list = result || [] + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + + let child = {} + for (let key in node) { + if (key !== 'children') { + child[key] = node[key] + } + } + result.push(child) + + let children = node.children + if (children) { + this._extractTreePath(children, result) + } + } + }, + + _findNodePath(key, nodes, path = []) { + let textField = this.map.text + let valueField = this.map.value + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + let children = node.children + let text = node[textField] + let value = node[valueField] + + path.push({ + value, + text + }) + + if (value === key) { + return path + } + + if (children) { + const p = this._findNodePath(key, children, path) + if (p.length) { + return p + } + } + + path.pop() + } + return [] + } + } +} diff --git a/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue new file mode 100644 index 0000000..6ebced9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-data-picker/package.json b/im-uniapp/uni_modules/uni-data-picker/package.json new file mode 100644 index 0000000..038f9ce --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/package.json @@ -0,0 +1,90 @@ +{ + "id": "uni-data-picker", + "displayName": "uni-data-picker 数据驱动的picker选择器", + "version": "1.1.2", + "description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景", + "keywords": [ + "uni-ui", + "uniui", + "picker", + "级联", + "省市区", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-load-more", + "uni-icons", + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-data-picker/readme.md b/im-uniapp/uni_modules/uni-data-picker/readme.md new file mode 100644 index 0000000..6cda224 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-picker/readme.md @@ -0,0 +1,22 @@ +## DataPicker 级联选择 +> **组件名:uni-data-picker** +> 代码块: `uDataPicker` +> 关联组件:`uni-data-pickerview`、`uni-load-more`。 + + +`` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。 + +支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。 + +候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。 + +`` 组件尤其适用于地址选择、分类选择等选择类。 + +`` 支持本地数据、云端静态数据(json),uniCloud云数据库数据。 + +`` 可以通过JQL直连uniCloud云数据库,配套[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema),可在schema2code中自动生成前端页面,还支持服务器端校验。 + +在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”,这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面,会自动生成地址管理的维护页面,自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-picker) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-data-select/changelog.md b/im-uniapp/uni_modules/uni-data-select/changelog.md new file mode 100644 index 0000000..eb3d1ce --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-select/changelog.md @@ -0,0 +1,35 @@ +## 1.0.6(2023-04-12) +- 修复 微信小程序点击时会改变背景颜色的 bug +## 1.0.5(2023-02-03) +- 修复 禁用时会显示清空按钮 +## 1.0.4(2023-02-02) +- 优化 查询条件短期内多次变更只查询最后一次变更后的结果 +- 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue +## 1.0.3(2023-01-16) +- 修复 不关联服务空间报错的问题 +## 1.0.2(2023-01-14) +- 新增 属性 `format` 可用于格式化显示选项内容 +## 1.0.1(2022-12-06) +- 修复 当where变化时,数据不会自动更新的问题 +## 0.1.9(2022-09-05) +- 修复 微信小程序下拉框出现后选择会点击到蒙板后面的输入框 +## 0.1.8(2022-08-29) +- 修复 点击的位置不准确 +## 0.1.7(2022-08-12) +- 新增 支持 disabled 属性 +## 0.1.6(2022-07-06) +- 修复 pc端宽度异常的bug +## 0.1.5 +- 修复 pc端宽度异常的bug +## 0.1.4(2022-07-05) +- 优化 显示样式 +## 0.1.3(2022-06-02) +- 修复 localdata 赋值不生效的 bug +- 新增 支持 uni.scss 修改颜色 +- 新增 支持选项禁用(数据选项设置 disabled: true 即禁用) +## 0.1.2(2022-05-08) +- 修复 当 value 为 0 时选择不生效的 bug +## 0.1.1(2022-05-07) +- 新增 记住上次的选项(仅 collection 存在时有效) +## 0.1.0(2022-04-22) +- 初始化 diff --git a/im-uniapp/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue b/im-uniapp/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue new file mode 100644 index 0000000..9b32348 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue @@ -0,0 +1,517 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-data-select/package.json b/im-uniapp/uni_modules/uni-data-select/package.json new file mode 100644 index 0000000..0187429 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-select/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-data-select", + "displayName": "uni-data-select 下拉框选择器", + "version": "1.0.6", + "description": "通过数据驱动的下拉框选择器", + "keywords": [ + "uni-ui", + "select", + "uni-data-select", + "下拉框", + "下拉选" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.1" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-load-more"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-data-select/readme.md b/im-uniapp/uni_modules/uni-data-select/readme.md new file mode 100644 index 0000000..eb58de3 --- /dev/null +++ b/im-uniapp/uni_modules/uni-data-select/readme.md @@ -0,0 +1,8 @@ +## DataSelect 下拉框选择器 +> **组件名:uni-data-select** +> 代码块: `uDataSelect` + +当选项过多时,使用下拉菜单展示并选择内容 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-select) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-dateformat/changelog.md b/im-uniapp/uni_modules/uni-dateformat/changelog.md new file mode 100644 index 0000000..d551d7b --- /dev/null +++ b/im-uniapp/uni_modules/uni-dateformat/changelog.md @@ -0,0 +1,10 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-dateformat](https://uniapp.dcloud.io/component/uniui/uni-dateformat) +## 0.0.5(2021-07-08) +- 调整 默认时间不再是当前时间,而是显示'-'字符 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-02-04) +- 调整为uni_modules目录规范 +- 修复 iOS 平台日期格式化出错的问题 diff --git a/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js b/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js new file mode 100644 index 0000000..e00d559 --- /dev/null +++ b/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js @@ -0,0 +1,200 @@ +// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型 +function pad(str, length = 2) { + str += '' + while (str.length < length) { + str = '0' + str + } + return str.slice(-length) +} + +const parser = { + yyyy: (dateObj) => { + return pad(dateObj.year, 4) + }, + yy: (dateObj) => { + return pad(dateObj.year) + }, + MM: (dateObj) => { + return pad(dateObj.month) + }, + M: (dateObj) => { + return dateObj.month + }, + dd: (dateObj) => { + return pad(dateObj.day) + }, + d: (dateObj) => { + return dateObj.day + }, + hh: (dateObj) => { + return pad(dateObj.hour) + }, + h: (dateObj) => { + return dateObj.hour + }, + mm: (dateObj) => { + return pad(dateObj.minute) + }, + m: (dateObj) => { + return dateObj.minute + }, + ss: (dateObj) => { + return pad(dateObj.second) + }, + s: (dateObj) => { + return dateObj.second + }, + SSS: (dateObj) => { + return pad(dateObj.millisecond, 3) + }, + S: (dateObj) => { + return dateObj.millisecond + }, +} + +// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12 +function getDate(time) { + if (time instanceof Date) { + return time + } + switch (typeof time) { + case 'string': + { + // 2020-12-12T12:12:12.000Z、2020-12-12T12:12:12.000 + if (time.indexOf('T') > -1) { + return new Date(time) + } + return new Date(time.replace(/-/g, '/')) + } + default: + return new Date(time) + } +} + +export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') { + if (!date && date !== 0) { + return '' + } + date = getDate(date) + const dateObj = { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + hour: date.getHours(), + minute: date.getMinutes(), + second: date.getSeconds(), + millisecond: date.getMilliseconds() + } + const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/ + let flag = true + let result = format + while (flag) { + flag = false + result = result.replace(tokenRegExp, function(matched) { + flag = true + return parser[matched](dateObj) + }) + } + return result +} + +export function friendlyDate(time, { + locale = 'zh', + threshold = [60000, 3600000], + format = 'yyyy/MM/dd hh:mm:ss' +}) { + if (time === '-') { + return time + } + if (!time && time !== 0) { + return '' + } + const localeText = { + zh: { + year: '年', + month: '月', + day: '天', + hour: '小时', + minute: '分钟', + second: '秒', + ago: '前', + later: '后', + justNow: '刚刚', + soon: '马上', + template: '{num}{unit}{suffix}' + }, + en: { + year: 'year', + month: 'month', + day: 'day', + hour: 'hour', + minute: 'minute', + second: 'second', + ago: 'ago', + later: 'later', + justNow: 'just now', + soon: 'soon', + template: '{num} {unit} {suffix}' + } + } + const text = localeText[locale] || localeText.zh + let date = getDate(time) + let ms = date.getTime() - Date.now() + let absMs = Math.abs(ms) + if (absMs < threshold[0]) { + return ms < 0 ? text.justNow : text.soon + } + if (absMs >= threshold[1]) { + return formatDate(date, format) + } + let num + let unit + let suffix = text.later + if (ms < 0) { + suffix = text.ago + ms = -ms + } + const seconds = Math.floor((ms) / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(months / 12) + switch (true) { + case years > 0: + num = years + unit = text.year + break + case months > 0: + num = months + unit = text.month + break + case days > 0: + num = days + unit = text.day + break + case hours > 0: + num = hours + unit = text.hour + break + case minutes > 0: + num = minutes + unit = text.minute + break + default: + num = seconds + unit = text.second + break + } + + if (locale === 'en') { + if (num === 1) { + num = 'a' + } else { + unit += 's' + } + } + + return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g, + suffix) +} diff --git a/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue b/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue new file mode 100644 index 0000000..c5ed030 --- /dev/null +++ b/im-uniapp/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-dateformat/package.json b/im-uniapp/uni_modules/uni-dateformat/package.json new file mode 100644 index 0000000..786a670 --- /dev/null +++ b/im-uniapp/uni_modules/uni-dateformat/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-dateformat", + "displayName": "uni-dateformat 日期格式化", + "version": "1.0.0", + "description": "日期格式化组件,可以将日期格式化为1分钟前、刚刚等形式", + "keywords": [ + "uni-ui", + "uniui", + "日期格式化", + "时间格式化", + "格式化时间", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-dateformat/readme.md b/im-uniapp/uni_modules/uni-dateformat/readme.md new file mode 100644 index 0000000..37ddb6e --- /dev/null +++ b/im-uniapp/uni_modules/uni-dateformat/readme.md @@ -0,0 +1,11 @@ + + +### DateFormat 日期格式化 +> **组件名:uni-dateformat** +> 代码块: `uDateformat` + + +日期格式化组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-dateformat) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-datetime-picker/changelog.md b/im-uniapp/uni_modules/uni-datetime-picker/changelog.md new file mode 100644 index 0000000..1e82f46 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/changelog.md @@ -0,0 +1,140 @@ +## 2.2.24(2023-06-02) +- 修复 部分情况修改时间,开始、结束时间显示异常的Bug [详情](https://ask.dcloud.net.cn/question/171146) +- 优化 当前月可以选择上月、下月的日期 +## 2.2.23(2023-05-02) +- 修复 部分情况修改时间,开始时间未更新 [详情](https://github.com/dcloudio/uni-ui/issues/737) +- 修复 部分平台及设备第一次点击无法显示弹框 +- 修复 ios 日期格式未补零显示及使用异常 [详情](https://ask.dcloud.net.cn/question/162979) +## 2.2.22(2023-03-30) +- 修复 日历 picker 修改年月后,自动选中当月1日 [详情](https://ask.dcloud.net.cn/question/165937) +- 修复 小程序端 低版本 ios NaN [详情](https://ask.dcloud.net.cn/question/162979) +## 2.2.21(2023-02-20) +- 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362) +## 2.2.20(2023-02-17) +- 优化 值为空依然选中当天问题 +- 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间 +- 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间 +- 优化 字节小程序日期时间范围选择,底部日期换行问题 +## 2.2.19(2023-02-09) +- 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686) +## 2.2.18(2023-02-08) +- 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684) +- 优化 PC端输入日期格式错误时返回当前日期时间 +- 优化 PC端输入日期时间超出 start、end 限制的Bug +- 优化 移动端日期时间范围用法时间展示不完整问题 +## 2.2.17(2023-02-04) +- 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679) +- 修复 vue3 time-picker 无法显示绑定时分秒的Bug +## 2.2.16(2023-02-02) +- 修复 字节小程序报错的Bug +## 2.2.15(2023-02-02) +- 修复 某些情况切换月份错误的Bug +## 2.2.14(2023-01-30) +- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/162033) +## 2.2.13(2023-01-10) +- 修复 多次加载组件造成内存占用的Bug +## 2.2.12(2022-12-01) +- 修复 vue3 下 i18n 国际化初始值不正确的Bug +## 2.2.11(2022-09-19) +- 修复 支付宝小程序样式错乱的Bug [详情](https://github.com/dcloudio/uni-app/issues/3861) +## 2.2.10(2022-09-19) +- 修复 反向选择日期范围,日期显示异常的Bug [详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false) +## 2.2.9(2022-09-16) +- 可以使用 uni-scss 控制主题色 +## 2.2.8(2022-09-08) +- 修复 close事件无效的Bug +## 2.2.7(2022-09-05) +- 修复 移动端 maskClick 无效的Bug [详情](https://ask.dcloud.net.cn/question/140824) +## 2.2.6(2022-06-30) +- 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致 +## 2.2.5(2022-06-24) +- 修复 日历顶部年月及底部确认未国际化的Bug +## 2.2.4(2022-03-31) +- 修复 Vue3 下动态赋值,单选类型未响应的Bug +## 2.2.3(2022-03-28) +- 修复 Vue3 下动态赋值未响应的Bug +## 2.2.2(2021-12-10) +- 修复 clear-icon 属性在小程序平台不生效的Bug +## 2.2.1(2021-12-10) +- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的Bug +## 2.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源 [详情](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移 [https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker) +## 2.1.5(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 2.1.4(2021-09-10) +- 修复 hide-second 在移动端的Bug +- 修复 单选赋默认值时,赋值日期未高亮的Bug +- 修复 赋默认值时,移动端未正确显示时间的Bug +## 2.1.3(2021-09-09) +- 新增 hide-second 属性,支持只使用时分,隐藏秒 +## 2.1.2(2021-09-03) +- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次 +- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法 +- 优化 调整字号大小,美化日历界面 +- 修复 因国际化导致的 placeholder 失效的Bug +## 2.1.1(2021-08-24) +- 新增 支持国际化 +- 优化 范围选择器在 pc 端过宽的问题 +## 2.1.0(2021-08-09) +- 新增 适配 vue3 +## 2.0.19(2021-08-09) +- 新增 支持作为 uni-forms 子组件相关功能 +- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的Bug +## 2.0.18(2021-08-05) +- 修复 type 属性动态赋值无效的Bug +- 修复 ‘确认’按钮被 tabbar 遮盖 bug +- 修复 组件未赋值时范围选左、右日历相同的Bug +## 2.0.17(2021-08-04) +- 修复 范围选未正确显示当前值的Bug +- 修复 h5 平台(移动端)报错 'cale' of undefined 的Bug +## 2.0.16(2021-07-21) +- 新增 return-type 属性支持返回 date 日期对象 +## 2.0.15(2021-07-14) +- 修复 单选日期类型,初始赋值后不在当前日历的Bug +- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效) +- 优化 移动端移除显示框的清空按钮,无实际用途 +## 2.0.14(2021-07-14) +- 修复 组件赋值为空,界面未更新的Bug +- 修复 start 和 end 不能动态赋值的Bug +- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的Bug +## 2.0.13(2021-07-08) +- 修复 范围选择不能动态赋值的Bug +## 2.0.12(2021-07-08) +- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug +## 2.0.11(2021-07-08) +- 优化 弹出层在超出视窗边缘定位不准确的问题 +## 2.0.10(2021-07-08) +- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的Bug +- 优化 弹出层在超出视窗边缘被遮盖的问题 +## 2.0.9(2021-07-07) +- 新增 maskClick 事件 +- 修复 特殊情况日历 rpx 布局错误的Bug,rpx -> px +- 修复 范围选择时清空返回值不合理的bug,['', ''] -> [] +## 2.0.8(2021-07-07) +- 新增 日期时间显示框支持插槽 +## 2.0.7(2021-07-01) +- 优化 添加 uni-icons 依赖 +## 2.0.6(2021-05-22) +- 修复 图标在小程序上不显示的Bug +- 优化 重命名引用组件,避免潜在组件命名冲突 +## 2.0.5(2021-05-20) +- 优化 代码目录扁平化 +## 2.0.4(2021-05-12) +- 新增 组件示例地址 +## 2.0.3(2021-05-10) +- 修复 ios 下不识别 '-' 日期格式的Bug +- 优化 pc 下弹出层添加边框和阴影 +## 2.0.2(2021-05-08) +- 修复 在 admin 中获取弹出层定位错误的bug +## 2.0.1(2021-05-08) +- 修复 type 属性向下兼容,默认值从 date 变更为 datetime +## 2.0.0(2021-04-30) +- 支持日历形式的日期+时间的范围选择 + > 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker) +## 1.0.6(2021-03-18) +- 新增 hide-second 属性,时间支持仅选择时、分 +- 修复 选择跟显示的日期不一样的Bug +- 修复 chang事件触发2次的Bug +- 修复 分、秒 end 范围错误的Bug +- 优化 更好的 nvue 适配 diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue new file mode 100644 index 0000000..dba9887 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue new file mode 100644 index 0000000..29b1b03 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue @@ -0,0 +1,929 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json new file mode 100644 index 0000000..024f22f --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json @@ -0,0 +1,22 @@ +{ + "uni-datetime-picker.selectDate": "select date", + "uni-datetime-picker.selectTime": "select time", + "uni-datetime-picker.selectDateTime": "select date and time", + "uni-datetime-picker.startDate": "start date", + "uni-datetime-picker.endDate": "end date", + "uni-datetime-picker.startTime": "start time", + "uni-datetime-picker.endTime": "end time", + "uni-datetime-picker.ok": "ok", + "uni-datetime-picker.clear": "clear", + "uni-datetime-picker.cancel": "cancel", + "uni-datetime-picker.year": "-", + "uni-datetime-picker.month": "", + "uni-calender.MON": "MON", + "uni-calender.TUE": "TUE", + "uni-calender.WED": "WED", + "uni-calender.THU": "THU", + "uni-calender.FRI": "FRI", + "uni-calender.SAT": "SAT", + "uni-calender.SUN": "SUN", + "uni-calender.confirm": "confirm" +} diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json new file mode 100644 index 0000000..d2df5e7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json @@ -0,0 +1,22 @@ +{ + "uni-datetime-picker.selectDate": "选择日期", + "uni-datetime-picker.selectTime": "选择时间", + "uni-datetime-picker.selectDateTime": "选择日期时间", + "uni-datetime-picker.startDate": "开始日期", + "uni-datetime-picker.endDate": "结束日期", + "uni-datetime-picker.startTime": "开始时间", + "uni-datetime-picker.endTime": "结束时间", + "uni-datetime-picker.ok": "确定", + "uni-datetime-picker.clear": "清除", + "uni-datetime-picker.cancel": "取消", + "uni-datetime-picker.year": "年", + "uni-datetime-picker.month": "月", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六", + "uni-calender.confirm": "确认" +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json new file mode 100644 index 0000000..d23fa3c --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "uni-datetime-picker.selectDate": "選擇日期", + "uni-datetime-picker.selectTime": "選擇時間", + "uni-datetime-picker.selectDateTime": "選擇日期時間", + "uni-datetime-picker.startDate": "開始日期", + "uni-datetime-picker.endDate": "結束日期", + "uni-datetime-picker.startTime": "開始时间", + "uni-datetime-picker.endTime": "結束时间", + "uni-datetime-picker.ok": "確定", + "uni-datetime-picker.clear": "清除", + "uni-datetime-picker.cancel": "取消", + "uni-datetime-picker.year": "年", + "uni-datetime-picker.month": "月", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六", + "uni-calender.confirm": "確認" +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue new file mode 100644 index 0000000..81a042a --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue @@ -0,0 +1,934 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue new file mode 100644 index 0000000..8769e83 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue @@ -0,0 +1,1032 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js new file mode 100644 index 0000000..fc98623 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js @@ -0,0 +1,453 @@ +class Calendar { + constructor({ + selected, + startDate, + endDate, + range, + } = {}) { + // 当前日期 + this.date = this.getDateObj(new Date()) // 当前初入日期 + // 打点信息 + this.selected = selected || []; + // 起始时间 + this.startDate = startDate + // 终止时间 + this.endDate = endDate + // 是否范围选择 + this.range = range + // 多选状态 + this.cleanMultipleStatus() + // 每周日期 + this.weeks = {} + this.lastHover = false + } + /** + * 设置日期 + * @param {Object} date + */ + setDate(date) { + const selectDate = this.getDateObj(date) + this.getWeeks(selectDate.fullDate) + } + + /** + * 清理多选状态 + */ + cleanMultipleStatus() { + this.multipleStatus = { + before: '', + after: '', + data: [] + } + } + + setStartDate(startDate) { + this.startDate = startDate + } + + setEndDate(endDate) { + this.endDate = endDate + } + + getPreMonthObj(date){ + date = fixIosDateFormat(date) + date = new Date(date) + + const oldMonth = date.getMonth() + date.setMonth(oldMonth - 1) + const newMonth = date.getMonth() + if(oldMonth !== 0 && newMonth - oldMonth === 0){ + date.setMonth(newMonth - 1) + } + return this.getDateObj(date) + } + getNextMonthObj(date){ + date = fixIosDateFormat(date) + date = new Date(date) + + const oldMonth = date.getMonth() + date.setMonth(oldMonth + 1) + const newMonth = date.getMonth() + if(newMonth - oldMonth > 1){ + date.setMonth(newMonth - 1) + } + return this.getDateObj(date) + } + + /** + * 获取指定格式Date对象 + */ + getDateObj(date) { + date = fixIosDateFormat(date) + date = new Date(date) + + return { + fullDate: getDate(date), + year: date.getFullYear(), + month: addZero(date.getMonth() + 1), + date: addZero(date.getDate()), + day: date.getDay() + } + } + + /** + * 获取上一个月日期集合 + */ + getPreMonthDays(amount, dateObj) { + const result = [] + for (let i = amount - 1; i >= 0; i--) { + const month = dateObj.month > 1 ? dateObj.month -1 : 12 + const year = month === 12 ? dateObj.year - 1 : dateObj.year + const date = new Date(year,month,-i).getDate() + const fullDate = `${year}-${addZero(month)}-${addZero(date)}` + let multiples = this.multipleStatus.data + let multiplesStatus = -1 + if (this.range && multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, fullDate) + }) + } + const checked = multiplesStatus !== -1 + // 获取打点信息 + const extraInfo = this.selected && this.selected.find((item) => { + if (this.dateEqual(fullDate, item.date)) { + return item + } + }) + result.push({ + fullDate, + year, + month, + date, + multiple: this.range ? checked : false, + beforeMultiple: this.isLogicBefore(fullDate, this.multipleStatus.before, this.multipleStatus.after), + afterMultiple: this.isLogicAfter(fullDate, this.multipleStatus.before, this.multipleStatus.after), + disable: (this.startDate && !dateCompare(this.startDate, fullDate)) || (this.endDate && !dateCompare(fullDate,this.endDate)), + isToday: fullDate === this.date.fullDate, + userChecked: false, + extraInfo + }) + } + return result + } + /** + * 获取本月日期集合 + */ + getCurrentMonthDays(amount, dateObj) { + const result = [] + const fullDate = this.date.fullDate + for (let i = 1; i <= amount; i++) { + const currentDate = `${dateObj.year}-${dateObj.month}-${addZero(i)}` + const isToday = fullDate === currentDate + // 获取打点信息 + const extraInfo = this.selected && this.selected.find((item) => { + if (this.dateEqual(currentDate, item.date)) { + return item + } + }) + + // 日期禁用 + let disableBefore = true + let disableAfter = true + if (this.startDate) { + disableBefore = dateCompare(this.startDate, currentDate) + } + + if (this.endDate) { + disableAfter = dateCompare(currentDate, this.endDate) + } + + let multiples = this.multipleStatus.data + let multiplesStatus = -1 + if (this.range && multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, currentDate) + }) + } + const checked = multiplesStatus !== -1 + + result.push({ + fullDate: currentDate, + year: dateObj.year, + month: dateObj.month, + date: i, + multiple: this.range ? checked : false, + beforeMultiple: this.isLogicBefore(currentDate, this.multipleStatus.before, this.multipleStatus.after), + afterMultiple: this.isLogicAfter(currentDate, this.multipleStatus.before, this.multipleStatus.after), + disable: (this.startDate && !dateCompare(this.startDate, currentDate)) || (this.endDate && !dateCompare(currentDate,this.endDate)), + isToday, + userChecked: false, + extraInfo + }) + } + return result + } + /** + * 获取下一个月日期集合 + */ + _getNextMonthDays(amount, dateObj) { + const result = [] + const month = dateObj.month + 1 + for (let i = 1; i <= amount; i++) { + const month = dateObj.month === 12 ? 1 : dateObj.month*1 + 1 + const year = month === 1 ? dateObj.year + 1 : dateObj.year + const fullDate = `${year}-${addZero(month)}-${addZero(i)}` + let multiples = this.multipleStatus.data + let multiplesStatus = -1 + if (this.range && multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, fullDate) + }) + } + const checked = multiplesStatus !== -1 + // 获取打点信息 + const extraInfo = this.selected && this.selected.find((item) => { + if (this.dateEqual(fullDate, item.date)) { + return item + } + }) + result.push({ + fullDate, + year, + date: i, + month, + multiple: this.range ? checked : false, + beforeMultiple: this.isLogicBefore(fullDate, this.multipleStatus.before, this.multipleStatus.after), + afterMultiple: this.isLogicAfter(fullDate, this.multipleStatus.before, this.multipleStatus.after), + disable: (this.startDate && !dateCompare(this.startDate, fullDate)) || (this.endDate && !dateCompare(fullDate,this.endDate)), + isToday: fullDate === this.date.fullDate, + userChecked: false, + extraInfo + }) + } + return result + } + + /** + * 获取当前日期详情 + * @param {Object} date + */ + getInfo(date) { + if (!date) { + date = new Date() + } + + return this.calendar.find(item => item.fullDate === this.getDateObj(date).fullDate) + } + + /** + * 比较时间是否相等 + */ + dateEqual(before, after) { + before = new Date(fixIosDateFormat(before)) + after = new Date(fixIosDateFormat(after)) + return before.valueOf() === after.valueOf() + } + + /** + * 比较真实起始日期 + */ + + isLogicBefore(currentDate, before, after) { + let logicBefore = before + if (before && after) { + logicBefore = dateCompare(before, after) ? before : after + } + return this.dateEqual(logicBefore, currentDate) + } + + isLogicAfter(currentDate, before, after) { + let logicAfter = after + if (before && after) { + logicAfter = dateCompare(before, after) ? after : before + } + return this.dateEqual(logicAfter, currentDate) + } + + /** + * 获取日期范围内所有日期 + * @param {Object} begin + * @param {Object} end + */ + geDateAll(begin, end) { + var arr = [] + var ab = begin.split('-') + var ae = end.split('-') + var db = new Date() + db.setFullYear(ab[0], ab[1] - 1, ab[2]) + var de = new Date() + de.setFullYear(ae[0], ae[1] - 1, ae[2]) + var unixDb = db.getTime() - 24 * 60 * 60 * 1000 + var unixDe = de.getTime() - 24 * 60 * 60 * 1000 + for (var k = unixDb; k <= unixDe;) { + k = k + 24 * 60 * 60 * 1000 + arr.push(this.getDateObj(new Date(parseInt(k))).fullDate) + } + return arr + } + + /** + * 获取多选状态 + */ + setMultiple(fullDate) { + if (!this.range) return + + let { + before, + after + } = this.multipleStatus + if (before && after) { + if (!this.lastHover) { + this.lastHover = true + return + } + this.multipleStatus.before = fullDate + this.multipleStatus.after = '' + this.multipleStatus.data = [] + this.multipleStatus.fulldate = '' + this.lastHover = false + } else { + if (!before) { + this.multipleStatus.before = fullDate + this.lastHover = false + } else { + this.multipleStatus.after = fullDate + if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus + .after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus + .before); + } + this.lastHover = true + } + } + this.getWeeks(fullDate) + } + + /** + * 鼠标 hover 更新多选状态 + */ + setHoverMultiple(fullDate) { + if (!this.range || this.lastHover) return + + const { before } = this.multipleStatus + + if (!before) { + this.multipleStatus.before = fullDate + } else { + this.multipleStatus.after = fullDate + if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before); + } + } + this.getWeeks(fullDate) + } + + /** + * 更新默认值多选状态 + */ + setDefaultMultiple(before, after) { + this.multipleStatus.before = before + this.multipleStatus.after = after + if (before && after) { + if (dateCompare(before, after)) { + this.multipleStatus.data = this.geDateAll(before, after); + this.getWeeks(after) + } else { + this.multipleStatus.data = this.geDateAll(after, before); + this.getWeeks(before) + } + } + } + + /** + * 获取每周数据 + * @param {Object} dateData + */ + getWeeks(dateData) { + const { + year, + month, + } = this.getDateObj(dateData) + + const preMonthDayAmount = new Date(year, month - 1, 1).getDay() + const preMonthDays = this.getPreMonthDays(preMonthDayAmount, this.getDateObj(dateData)) + + const currentMonthDayAmount = new Date(year, month, 0).getDate() + const currentMonthDays = this.getCurrentMonthDays(currentMonthDayAmount, this.getDateObj(dateData)) + + const nextMonthDayAmount = 42 - preMonthDayAmount - currentMonthDayAmount + const nextMonthDays = this._getNextMonthDays(nextMonthDayAmount, this.getDateObj(dateData)) + + const calendarDays = [...preMonthDays, ...currentMonthDays, ...nextMonthDays] + + const weeks = new Array(6) + for (let i = 0; i < calendarDays.length; i++) { + const index = Math.floor(i / 7) + if(!weeks[index]){ + weeks[index] = new Array(7) + } + weeks[index][i % 7] = calendarDays[i] + } + + this.calendar = calendarDays + this.weeks = weeks + } +} + +function getDateTime(date, hideSecond){ + return `${getDate(date)} ${getTime(date, hideSecond)}` +} + +function getDate(date) { + date = fixIosDateFormat(date) + date = new Date(date) + const year = date.getFullYear() + const month = date.getMonth()+1 + const day = date.getDate() + return `${year}-${addZero(month)}-${addZero(day)}` +} + +function getTime(date, hideSecond){ + date = fixIosDateFormat(date) + date = new Date(date) + const hour = date.getHours() + const minute = date.getMinutes() + const second = date.getSeconds() + return hideSecond ? `${addZero(hour)}:${addZero(minute)}` : `${addZero(hour)}:${addZero(minute)}:${addZero(second)}` +} + +function addZero(num) { + if(num < 10){ + num = `0${num}` + } + return num +} + +function getDefaultSecond(hideSecond) { + return hideSecond ? '00:00' : '00:00:00' +} + +function dateCompare(startDate, endDate) { + startDate = new Date(fixIosDateFormat(startDate)) + endDate = new Date(fixIosDateFormat(endDate)) + return startDate <= endDate +} + +function checkDate(date){ + const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g + return date.match(dateReg) +} + +const dateTimeReg = /^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])( [0-5]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])?$/ +function fixIosDateFormat(value) { + if (typeof value === 'string' && dateTimeReg.test(value)) { + value = value.replace(/-/g, '/') + } + return value +} + +export {Calendar, getDateTime, getDate, getTime, addZero, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-datetime-picker/package.json b/im-uniapp/uni_modules/uni-datetime-picker/package.json new file mode 100644 index 0000000..cabb668 --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-datetime-picker", + "displayName": "uni-datetime-picker 日期选择器", + "version": "2.2.24", + "description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择", + "keywords": [ + "uni-datetime-picker", + "uni-ui", + "uniui", + "日期时间选择器", + "日期时间" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-datetime-picker/readme.md b/im-uniapp/uni_modules/uni-datetime-picker/readme.md new file mode 100644 index 0000000..162fbef --- /dev/null +++ b/im-uniapp/uni_modules/uni-datetime-picker/readme.md @@ -0,0 +1,21 @@ + + +> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护` + +## DatetimePicker 时间选择器 + +> **组件名:uni-datetime-picker** +> 代码块: `uDatetimePicker` + + +该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。 + +若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。 + +**_点击 picker 默认值规则:_** + +- 若设置初始值 value, 会显示在 picker 显示框中 +- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-drawer/changelog.md b/im-uniapp/uni_modules/uni-drawer/changelog.md new file mode 100644 index 0000000..6d2488c --- /dev/null +++ b/im-uniapp/uni_modules/uni-drawer/changelog.md @@ -0,0 +1,13 @@ +## 1.2.1(2021-11-22) +- 修复 vue3中个别scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-drawer](https://uniapp.dcloud.io/component/uniui/uni-drawer) +## 1.1.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/keypress.js b/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/keypress.js new file mode 100644 index 0000000..62dda46 --- /dev/null +++ b/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue b/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue new file mode 100644 index 0000000..2471521 --- /dev/null +++ b/im-uniapp/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-drawer/package.json b/im-uniapp/uni_modules/uni-drawer/package.json new file mode 100644 index 0000000..dd056e4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-drawer/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-drawer", + "displayName": "uni-drawer 抽屉", + "version": "1.2.1", + "description": "抽屉式导航,用于展示侧滑菜单,侧滑导航。", + "keywords": [ + "uni-ui", + "uniui", + "drawer", + "抽屉", + "侧滑导航" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-drawer/readme.md b/im-uniapp/uni_modules/uni-drawer/readme.md new file mode 100644 index 0000000..dcf6e6b --- /dev/null +++ b/im-uniapp/uni_modules/uni-drawer/readme.md @@ -0,0 +1,10 @@ + + +## Drawer 抽屉 +> **组件名:uni-drawer** +> 代码块: `uDrawer` + +抽屉侧滑菜单。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-drawer) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-easyinput/changelog.md b/im-uniapp/uni_modules/uni-easyinput/changelog.md new file mode 100644 index 0000000..765401a --- /dev/null +++ b/im-uniapp/uni_modules/uni-easyinput/changelog.md @@ -0,0 +1,97 @@ +## 1.1.9(2023-04-11) +- 修复 vue3 下 keyboardheightchange 事件报错的bug +## 1.1.8(2023-03-29) +- 优化 trim 属性默认值 +## 1.1.7(2023-03-29) +- 新增 cursor-spacing 属性 +## 1.1.6(2023-01-28) +- 新增 keyboardheightchange 事件,可监听键盘高度变化 +## 1.1.5(2022-11-29) +- 优化 主题样式 +## 1.1.4(2022-10-27) +- 修复 props 中背景颜色无默认值的bug +## 1.1.0(2022-06-30) + +- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容 +- 新增 clear 事件,点击右侧叉号图标触发 +- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发 +- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等 + +## 1.0.5(2022-06-07) + +- 优化 clearable 显示策略 + +## 1.0.4(2022-06-07) + +- 优化 clearable 显示策略 + +## 1.0.3(2022-05-20) + +- 修复 关闭图标某些情况下无法取消的 bug + +## 1.0.2(2022-04-12) + +- 修复 默认值不生效的 bug + +## 1.0.1(2022-04-02) + +- 修复 value 不能为 0 的 bug + +## 1.0.0(2021-11-19) + +- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput) + +## 0.1.4(2021-08-20) + +- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug + +## 0.1.3(2021-08-11) + +- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 + +## 0.1.2(2021-07-30) + +- 优化 vue3 下事件警告的问题 + +## 0.1.1 + +- 优化 errorMessage 属性支持 Boolean 类型 + +## 0.1.0(2021-07-13) + +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) + +## 0.0.16(2021-06-29) + +- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug + +## 0.0.15(2021-06-21) + +- 修复 passwordIcon 属性拼写错误的 bug + +## 0.0.14(2021-06-18) + +- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标 +- 修复 confirmType 属性不生效的问题 + +## 0.0.13(2021-06-04) + +- 修复 disabled 状态可清出内容的 bug + +## 0.0.12(2021-05-12) + +- 新增 组件示例地址 + +## 0.0.11(2021-05-07) + +- 修复 input-border 属性不生效的问题 + +## 0.0.10(2021-04-30) + +- 修复 ios 遮挡文字、显示一半的问题 + +## 0.0.9(2021-02-05) + +- 调整为 uni_modules 目录规范 +- 优化 兼容 nvue 页面 diff --git a/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/common.js b/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/common.js new file mode 100644 index 0000000..df9abe1 --- /dev/null +++ b/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/common.js @@ -0,0 +1,56 @@ +/** + * @desc 函数防抖 + * @param func 目标函数 + * @param wait 延迟执行毫秒数 + * @param immediate true - 立即执行, false - 延迟执行 + */ +export const debounce = function(func, wait = 1000, immediate = true) { + let timer; + console.log(1); + return function() { + console.log(123); + let context = this, + args = arguments; + if (timer) clearTimeout(timer); + if (immediate) { + let callNow = !timer; + timer = setTimeout(() => { + timer = null; + }, wait); + if (callNow) func.apply(context, args); + } else { + timer = setTimeout(() => { + func.apply(context, args); + }, wait) + } + } +} +/** + * @desc 函数节流 + * @param func 函数 + * @param wait 延迟执行毫秒数 + * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发 + */ +export const throttle = (func, wait = 1000, type = 1) => { + let previous = 0; + let timeout; + return function() { + let context = this; + let args = arguments; + if (type === 1) { + let now = Date.now(); + + if (now - previous > wait) { + func.apply(context, args); + previous = now; + } + } else if (type === 2) { + if (!timeout) { + timeout = setTimeout(() => { + timeout = null; + func.apply(context, args) + }, wait) + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue b/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue new file mode 100644 index 0000000..2c7993a --- /dev/null +++ b/im-uniapp/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue @@ -0,0 +1,657 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-easyinput/package.json b/im-uniapp/uni_modules/uni-easyinput/package.json new file mode 100644 index 0000000..bd128e1 --- /dev/null +++ b/im-uniapp/uni_modules/uni-easyinput/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-easyinput", + "displayName": "uni-easyinput 增强输入框", + "version": "1.1.9", + "description": "Easyinput 组件是对原生input组件的增强", + "keywords": [ + "uni-ui", + "uniui", + "input", + "uni-easyinput", + "输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-easyinput/readme.md b/im-uniapp/uni_modules/uni-easyinput/readme.md new file mode 100644 index 0000000..f1faf8f --- /dev/null +++ b/im-uniapp/uni_modules/uni-easyinput/readme.md @@ -0,0 +1,11 @@ + + +### Easyinput 增强输入框 +> **组件名:uni-easyinput** +> 代码块: `uEasyinput` + + +easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-fab/changelog.md b/im-uniapp/uni_modules/uni-fab/changelog.md new file mode 100644 index 0000000..9bd4729 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fab/changelog.md @@ -0,0 +1,23 @@ +## 1.2.5(2023-03-29) +- 新增 pattern.icon 属性,可自定义图标 +## 1.2.4(2022-09-07) +小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false) +## 1.2.3(2022-09-05) +- 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310) +## 1.2.2(2021-12-29) +- 更新 组件依赖 +## 1.2.1(2021-11-19) +- 修复 阴影颜色不正确的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fab](https://uniapp.dcloud.io/component/uniui/uni-fab) +## 1.1.1(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 按钮背景色调整 +- 优化 兼容pc端 diff --git a/im-uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue b/im-uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue new file mode 100644 index 0000000..dfa65c1 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue @@ -0,0 +1,491 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-fab/package.json b/im-uniapp/uni_modules/uni-fab/package.json new file mode 100644 index 0000000..18c0810 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fab/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-fab", + "displayName": "uni-fab 悬浮按钮", + "version": "1.2.5", + "description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。", + "keywords": [ + "uni-ui", + "uniui", + "按钮", + "悬浮按钮", + "fab" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-fab/readme.md b/im-uniapp/uni_modules/uni-fab/readme.md new file mode 100644 index 0000000..9a444e8 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fab/readme.md @@ -0,0 +1,9 @@ +## Fab 悬浮按钮 +> **组件名:uni-fab** +> 代码块: `uFab` + + +点击可展开一个图形按钮菜单 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fab) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-fav/changelog.md b/im-uniapp/uni_modules/uni-fav/changelog.md new file mode 100644 index 0000000..d8a08d4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/changelog.md @@ -0,0 +1,19 @@ +## 1.2.1(2022-05-30) +- 新增 stat 属性 ,是否开启uni统计功能 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fav](https://uniapp.dcloud.io/component/uniui/uni-fav) +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.6(2021-05-12) +- 新增 组件示例地址 +## 1.0.5(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.4(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.3(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json new file mode 100644 index 0000000..9a0759e --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "collect", + "uni-fav.collected": "collected" +} diff --git a/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/im-uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue b/im-uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue new file mode 100644 index 0000000..d2c58df --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-fav/package.json b/im-uniapp/uni_modules/uni-fav/package.json new file mode 100644 index 0000000..cc14697 --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-fav", + "displayName": "uni-fav 收藏按钮", + "version": "1.2.1", + "description": " Fav 收藏组件,可自定义颜色、大小。", + "keywords": [ + "fav", + "uni-ui", + "uniui", + "收藏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-fav/readme.md b/im-uniapp/uni_modules/uni-fav/readme.md new file mode 100644 index 0000000..4de125d --- /dev/null +++ b/im-uniapp/uni_modules/uni-fav/readme.md @@ -0,0 +1,10 @@ + + +## Fav 收藏按钮 +> **组件名:uni-fav** +> 代码块: `uFav` + +用于收藏功能,可点击切换选中、不选中的状态。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fav) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-file-picker/changelog.md b/im-uniapp/uni_modules/uni-file-picker/changelog.md new file mode 100644 index 0000000..b8230e9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/changelog.md @@ -0,0 +1,67 @@ +## 1.0.4(2023-03-29) +- 修复 手动上传删除一个文件后不能再上传的bug +## 1.0.3(2022-12-19) +- 新增 sourceType 属性, 可以自定义图片和视频选择的来源 +## 1.0.2(2022-07-04) +- 修复 在uni-forms下样式不生效的bug +## 1.0.1(2021-11-23) +- 修复 参数为对象的情况下,url在某些情况显示错误的bug +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker) +## 0.2.16(2021-11-08) +- 修复 传入空对象 ,显示错误的Bug +## 0.2.15(2021-08-30) +- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug +## 0.2.14(2021-08-23) +- 新增 参数中返回 fileID 字段 +## 0.2.13(2021-08-23) +- 修复 腾讯云传入fileID 不能回显的bug +- 修复 选择图片后,不能放大的问题 +## 0.2.12(2021-08-17) +- 修复 由于 0.2.11 版本引起的不能回显图片的Bug +## 0.2.11(2021-08-16) +- 新增 clearFiles(index) 方法,可以手动删除指定文件 +- 修复 v-model 值设为 null 报错的Bug +## 0.2.10(2021-08-13) +- 修复 return-type="object" 时,无法删除文件的Bug +## 0.2.9(2021-08-03) +- 修复 auto-upload 属性失效的Bug +## 0.2.8(2021-07-31) +- 修复 fileExtname属性不指定值报错的Bug +## 0.2.7(2021-07-31) +- 修复 在某种场景下图片不回显的Bug +## 0.2.6(2021-07-30) +- 修复 return-type为object下,返回值不正确的Bug +## 0.2.5(2021-07-30) +- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题 +## 0.2.3(2021-07-28) +- 优化 调整示例代码 +## 0.2.2(2021-07-27) +- 修复 vue3 下赋值错误的Bug +- 优化 h5平台下上传文件导致页面卡死的问题 +## 0.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.1.1(2021-07-02) +- 修复 sourceType 缺少默认值导致 ios 无法选择文件 +## 0.1.0(2021-06-30) +- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改 +## 0.0.11(2021-06-30) +- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题 +## 0.0.10(2021-06-29) +- 优化 文件上传后进度条消失时机 +## 0.0.9(2021-06-29) +- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug +## 0.0.8(2021-06-15) +- 修复 删除文件时无法触发 v-model 的Bug +## 0.0.7(2021-05-12) +- 新增 组件示例地址 +## 0.0.6(2021-04-09) +- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug +## 0.0.5(2021-04-09) +- 优化 更新组件示例 +## 0.0.4(2021-04-09) +- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug diff --git a/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js new file mode 100644 index 0000000..aff0864 --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js @@ -0,0 +1,224 @@ +'use strict'; + +const ERR_MSG_OK = 'chooseAndUploadFile:ok'; +const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; + +function chooseImage(opts) { + const { + count, + sizeType = ['original', 'compressed'], + sourceType, + extension + } = opts + return new Promise((resolve, reject) => { + uni.chooseImage({ + count, + sizeType, + sourceType, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res, 'image')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseVideo(opts) { + const { + camera, + compressed, + maxDuration, + sourceType, + extension + } = opts; + return new Promise((resolve, reject) => { + uni.chooseVideo({ + camera, + compressed, + maxDuration, + sourceType, + extension, + success(res) { + const { + tempFilePath, + duration, + size, + height, + width + } = res; + resolve(normalizeChooseAndUploadFileRes({ + errMsg: 'chooseVideo:ok', + tempFilePaths: [tempFilePath], + tempFiles: [ + { + name: (res.tempFile && res.tempFile.name) || '', + path: tempFilePath, + size, + type: (res.tempFile && res.tempFile.type) || '', + width, + height, + duration, + fileType: 'video', + cloudPath: '', + }, ], + }, 'video')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseAll(opts) { + const { + count, + extension + } = opts; + return new Promise((resolve, reject) => { + let chooseFile = uni.chooseFile; + if (typeof wx !== 'undefined' && + typeof wx.chooseMessageFile === 'function') { + chooseFile = wx.chooseMessageFile; + } + if (typeof chooseFile !== 'function') { + return reject({ + errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。', + }); + } + chooseFile({ + type: 'all', + count, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res)); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function normalizeChooseAndUploadFileRes(res, fileType) { + res.tempFiles.forEach((item, index) => { + if (!item.name) { + item.name = item.path.substring(item.path.lastIndexOf('/') + 1); + } + if (fileType) { + item.fileType = fileType; + } + item.cloudPath = + Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); + }); + if (!res.tempFilePaths) { + res.tempFilePaths = res.tempFiles.map((file) => file.path); + } + return res; +} + +function uploadCloudFiles(files, max = 5, onUploadProgress) { + files = JSON.parse(JSON.stringify(files)) + const len = files.length + let count = 0 + let self = this + return new Promise(resolve => { + while (count < max) { + next() + } + + function next() { + let cur = count++ + if (cur >= len) { + !files.find(item => !item.url && !item.errMsg) && resolve(files) + return + } + const fileItem = files[cur] + const index = self.files.findIndex(v => v.uuid === fileItem.uuid) + fileItem.url = '' + delete fileItem.errMsg + + uniCloud + .uploadFile({ + filePath: fileItem.path, + cloudPath: fileItem.cloudPath, + fileType: fileItem.fileType, + onUploadProgress: res => { + res.index = index + onUploadProgress && onUploadProgress(res) + } + }) + .then(res => { + fileItem.url = res.fileID + fileItem.index = index + if (cur < len) { + next() + } + }) + .catch(res => { + fileItem.errMsg = res.errMsg || res.message + fileItem.index = index + if (cur < len) { + next() + } + }) + } + }) +} + + + + + +function uploadFiles(choosePromise, { + onChooseFile, + onUploadProgress +}) { + return choosePromise + .then((res) => { + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ? + res : chooseRes); + } + } + return res; + }) + .then((res) => { + if (res === false) { + return { + errMsg: ERR_MSG_OK, + tempFilePaths: [], + tempFiles: [], + }; + } + return res + }) +} + +function chooseAndUploadFile(opts = { + type: 'all' +}) { + if (opts.type === 'image') { + return uploadFiles(chooseImage(opts), opts); + } + else if (opts.type === 'video') { + return uploadFiles(chooseVideo(opts), opts); + } + return uploadFiles(chooseAll(opts), opts); +} + +export { + chooseAndUploadFile, + uploadCloudFiles +}; diff --git a/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue new file mode 100644 index 0000000..370441d --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue @@ -0,0 +1,667 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue new file mode 100644 index 0000000..625d92e --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue new file mode 100644 index 0000000..2a29bc2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js new file mode 100644 index 0000000..60aaa3e --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js @@ -0,0 +1,109 @@ +/** + * 获取文件名和后缀 + * @param {String} name + */ +export const get_file_ext = (name) => { + const last_len = name.lastIndexOf('.') + const len = name.length + return { + name: name.substring(0, last_len), + ext: name.substring(last_len + 1, len) + } +} + +/** + * 获取扩展名 + * @param {Array} fileExtname + */ +export const get_extname = (fileExtname) => { + if (!Array.isArray(fileExtname)) { + let extname = fileExtname.replace(/(\[|\])/g, '') + return extname.split(',') + } else { + return fileExtname + } + return [] +} + +/** + * 获取文件和检测是否可选 + */ +export const get_files_and_is_max = (res, _extname) => { + let filePaths = [] + let files = [] + if(!_extname || _extname.length === 0){ + return { + filePaths, + files + } + } + res.tempFiles.forEach(v => { + let fileFullName = get_file_ext(v.name) + const extname = fileFullName.ext.toLowerCase() + if (_extname.indexOf(extname) !== -1) { + files.push(v) + filePaths.push(v.path) + } + }) + if (files.length !== res.tempFiles.length) { + uni.showToast({ + title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`, + icon: 'none', + duration: 5000 + }) + } + + return { + filePaths, + files + } +} + + +/** + * 获取图片信息 + * @param {Object} filepath + */ +export const get_file_info = (filepath) => { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src: filepath, + success(res) { + resolve(res) + }, + fail(err) { + reject(err) + } + }) + }) +} +/** + * 获取封装数据 + */ +export const get_file_data = async (files, type = 'image') => { + // 最终需要上传数据库的数据 + let fileFullName = get_file_ext(files.name) + const extname = fileFullName.ext.toLowerCase() + let filedata = { + name: files.name, + uuid: files.uuid, + extname: extname || '', + cloudPath: files.cloudPath, + fileType: files.fileType, + url: files.path || files.path, + size: files.size, //单位是字节 + image: {}, + path: files.path, + video: {} + } + if (type === 'image') { + const imageinfo = await get_file_info(files.path) + delete filedata.video + filedata.image.width = imageinfo.width + filedata.image.height = imageinfo.height + filedata.image.location = imageinfo.path + } else { + delete filedata.image + } + return filedata +} diff --git a/im-uniapp/uni_modules/uni-file-picker/package.json b/im-uniapp/uni_modules/uni-file-picker/package.json new file mode 100644 index 0000000..35c926c --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-file-picker", + "displayName": "uni-file-picker 文件选择上传", + "version": "1.0.4", + "description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间", + "keywords": [ + "uni-ui", + "uniui", + "图片上传", + "文件上传" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-file-picker/readme.md b/im-uniapp/uni_modules/uni-file-picker/readme.md new file mode 100644 index 0000000..c8399a5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-file-picker/readme.md @@ -0,0 +1,11 @@ + +## FilePicker 文件选择上传 + +> **组件名:uni-file-picker** +> 代码块: `uFilePicker` + + +文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-forms/changelog.md b/im-uniapp/uni_modules/uni-forms/changelog.md new file mode 100644 index 0000000..8218df5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/changelog.md @@ -0,0 +1,92 @@ +## 1.4.9(2023-02-10) +- 修复 required 参数无法动态绑定 +## 1.4.8(2022-08-23) +- 优化 根据 rules 自动添加 required 的问题 +## 1.4.7(2022-08-22) +- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +## 1.4.6(2022-07-13) +- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug +## 1.4.5(2022-07-05) +- 新增 更多表单示例 +- 优化 子表单组件过期提示的问题 +- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +## 1.4.4(2022-07-04) +- 更新 删除组件日志 +## 1.4.3(2022-07-04) +- 修复 由 1.4.0 引发的 label 插槽不生效的bug +## 1.4.2(2022-07-04) +- 修复 子组件找不到 setValue 报错的bug +## 1.4.1(2022-07-04) +- 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +## 1.4.0(2022-06-30) +- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题 +- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- 优化 动态表单校验方式,废弃拼接name的方式 +## 1.3.3(2022-06-22) +- 修复 表单校验顺序无序问题 +## 1.3.2(2021-12-09) +- +## 1.3.1(2021-11-19) +- 修复 label 插槽不生效的bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms) +## 1.2.7(2021-08-13) +- 修复 没有添加校验规则的字段依然报错的Bug +## 1.2.6(2021-08-11) +- 修复 重置表单错误信息无法清除的问题 +## 1.2.5(2021-08-11) +- 优化 组件文档 +## 1.2.4(2021-08-11) +- 修复 表单验证只生效一次的问题 +## 1.2.3(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.2(2021-07-26) +- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- 修复 1.2.1 引起的示例在小程序平台报错的Bug +## 1.2.1(2021-07-22) +- 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- 修复 不指定name属性时,运行报错的Bug +- 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- 优化 组件示例,新增动态校验示例代码 +- 优化 组件文档,使用方式更清晰 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-06-25) +- 修复 pattern 属性在微信小程序平台无效的问题 +## 1.1.1(2021-06-22) +- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +## 1.1.0(2021-06-22) +- 修复 只写setRules方法而导致校验不生效的Bug +- 修复 由上个办法引发的错误提示文字错位的Bug +## 1.0.48(2021-06-21) +- 修复 不设置 label 属性 ,无法设置label插槽的问题 +## 1.0.47(2021-06-21) +- 修复 不设置label属性,label-width属性不生效的bug +- 修复 setRules 方法与rules属性冲突的问题 +## 1.0.46(2021-06-04) +- 修复 动态删减数据导致报错的问题 +## 1.0.45(2021-06-04) +- 新增 modelValue 属性 ,value 即将废弃 +## 1.0.44(2021-06-02) +- 新增 uni-forms-item 可以设置单独的 rules +- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- 优化 submit 事件重命名为 validate +## 1.0.43(2021-05-12) +- 新增 组件示例地址 +## 1.0.42(2021-04-30) +- 修复 自定义检验器失效的问题 +## 1.0.41(2021-03-05) +- 更新 校验器 +- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug +## 1.0.40(2021-03-04) +- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +## 1.0.39(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 校验器传入 int 等类型 ,返回String类型的Bug diff --git a/im-uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/im-uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000..91fe351 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,627 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/im-uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000..ed2f6d9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-forms/components/uni-forms/utils.js b/im-uniapp/uni_modules/uni-forms/components/uni-forms/utils.js new file mode 100644 index 0000000..6da2421 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/components/uni-forms/utils.js @@ -0,0 +1,293 @@ +/** + * 简单处理对象拷贝 + * @param {Obejct} 被拷贝对象 + * @@return {Object} 拷贝对象 + */ +export const deepCopy = (val) => { + return JSON.parse(JSON.stringify(val)) +} +/** + * 过滤数字类型 + * @param {String} format 数字类型 + * @@return {Boolean} 返回是否为数字类型 + */ +export const typeFilter = (format) => { + return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'; +} + +/** + * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined + * @param {String} key 字段名 + * @param {any} value 字段值 + * @param {Object} rules 表单校验规则 + */ +export const getValue = (key, value, rules) => { + const isRuleNumType = rules.find(val => val.format && typeFilter(val.format)); + const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool'); + // 输入类型为 number + if (!!isRuleNumType) { + if (!value && value !== 0) { + value = null + } else { + value = isNumber(Number(value)) ? Number(value) : value + } + } + + // 输入类型为 boolean + if (!!isRuleBoolType) { + value = isBoolean(value) ? value : false + } + + return value; +} + +/** + * 获取表单数据 + * @param {String|Array} name 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + * @param {any} value 需要设置的值 + */ +export const setDataValue = (field, formdata, value) => { + formdata[field] = value + return value || '' +} + +/** + * 获取表单数据 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + */ +export const getDataValue = (field, data) => { + return objGet(data, field) +} + +/** + * 获取表单类型 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + */ +export const getDataValueType = (field, data) => { + const value = getDataValue(field, data) + return { + type: type(value), + value + } +} + +/** + * 获取表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const realName = (name, data = {}) => { + const base_name = _basePath(name) + if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) { + const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_') + return realname + } + return base_name[0] || name +} + +/** + * 判断是否表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const isRealName = (name) => { + const reg = /^_formdata_#*/ + return reg.test(name) +} + +/** + * 获取表单数据的原始格式 + * @@return {Object|Array} object 需要解析的数据 + */ +export const rawData = (object = {}, name) => { + let newData = JSON.parse(JSON.stringify(object)) + let formData = {} + for(let i in newData){ + let path = name2arr(i) + objSet(formData,path,newData[i]) + } + return formData +} + +/** + * 真实name还原为 array + * @param {*} name + */ +export const name2arr = (name) => { + let field = name.replace('_formdata_#', '') + field = field.split('#').map(v => (isNumber(v) ? Number(v) : v)) + return field +} + +/** + * 对象中设置值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} value 需要设置的值 + */ +export const objSet = (object, path, value) => { + if (typeof object !== 'object') return object; + _basePath(path).reduce((o, k, i, _) => { + if (i === _.length - 1) { + // 若遍历结束直接赋值 + o[k] = value + return null + } else if (k in o) { + // 若存在对应路径,则返回找到的对象,进行下一次遍历 + return o[k] + } else { + // 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象 + o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {} + return o[k] + } + }, object) + // 返回object + return object; +} + +// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用 +function _basePath(path) { + // 若是数组,则直接返回 + if (Array.isArray(path)) return path + // 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']' + return path.replace(/\[/g, '.').replace(/\]/g, '').split('.') +} + +/** + * 从对象中获取值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} defaultVal 如果无法从调用链中获取值的默认值 + */ +export const objGet = (object, path, defaultVal = 'undefined') => { + // 先将path处理成统一格式 + let newPath = _basePath(path) + // 递归处理,返回最后结果 + let val = newPath.reduce((o, k) => { + return (o || {})[k] + }, object); + return !val || val !== undefined ? val : defaultVal +} + + +/** + * 是否为 number 类型 + * @param {any} num 需要判断的值 + * @return {Boolean} 是否为 number + */ +export const isNumber = (num) => { + return !isNaN(Number(num)) +} + +/** + * 是否为 boolean 类型 + * @param {any} bool 需要判断的值 + * @return {Boolean} 是否为 boolean + */ +export const isBoolean = (bool) => { + return (typeof bool === 'boolean') +} +/** + * 是否有必填字段 + * @param {Object} rules 规则 + * @return {Boolean} 是否有必填字段 + */ +export const isRequiredField = (rules) => { + let isNoField = false; + for (let i = 0; i < rules.length; i++) { + const ruleData = rules[i]; + if (ruleData.required) { + isNoField = true; + break; + } + } + return isNoField; +} + + +/** + * 获取数据类型 + * @param {Any} obj 需要获取数据类型的值 + */ +export const type = (obj) => { + var class2type = {}; + + // 生成class2type映射 + "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) { + class2type["[object " + item + "]"] = item.toLowerCase(); + }) + if (obj == null) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[Object.prototype.toString.call(obj)] || "object" : + typeof obj; +} + +/** + * 判断两个值是否相等 + * @param {any} a 值 + * @param {any} b 值 + * @return {Boolean} 是否相等 + */ +export const isEqual = (a, b) => { + //如果a和b本来就全等 + if (a === b) { + //判断是否为0和-0 + return a !== 0 || 1 / a === 1 / b; + } + //判断是否为null和undefined + if (a == null || b == null) { + return a === b; + } + //接下来判断a和b的数据类型 + var classNameA = toString.call(a), + classNameB = toString.call(b); + //如果数据类型不相等,则返回false + if (classNameA !== classNameB) { + return false; + } + //如果数据类型相等,再根据不同数据类型分别判断 + switch (classNameA) { + case '[object RegExp]': + case '[object String]': + //进行字符串转换比较 + return '' + a === '' + b; + case '[object Number]': + //进行数字转换比较,判断是否为NaN + if (+a !== +a) { + return +b !== +b; + } + //判断是否为0或-0 + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + return +a === +b; + } + //如果是对象类型 + if (classNameA == '[object Object]') { + //获取a和b的属性长度 + var propsA = Object.getOwnPropertyNames(a), + propsB = Object.getOwnPropertyNames(b); + if (propsA.length != propsB.length) { + return false; + } + for (var i = 0; i < propsA.length; i++) { + var propName = propsA[i]; + //如果对应属性对应值不相等,则返回false + if (a[propName] !== b[propName]) { + return false; + } + } + return true; + } + //如果是数组类型 + if (classNameA == '[object Array]') { + if (a.toString() == b.toString()) { + return true; + } + return false; + } +} diff --git a/im-uniapp/uni_modules/uni-forms/components/uni-forms/validate.js b/im-uniapp/uni_modules/uni-forms/components/uni-forms/validate.js new file mode 100644 index 0000000..1834c6c --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/components/uni-forms/validate.js @@ -0,0 +1,486 @@ +var pattern = { + email: /^\S+?@\S+?\.\S+?$/, + idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, + url: new RegExp( + "^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", + 'i') +}; + +const FORMAT_MAPPING = { + "int": 'integer', + "bool": 'boolean', + "double": 'number', + "long": 'number', + "password": 'string' + // "fileurls": 'array' +} + +function formatMessage(args, resources = '') { + var defaultMessage = ['label'] + defaultMessage.forEach((item) => { + if (args[item] === undefined) { + args[item] = '' + } + }) + + let str = resources + for (let key in args) { + let reg = new RegExp('{' + key + '}') + str = str.replace(reg, args[key]) + } + return str +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true; + } + + if (typeof value === 'string' && !value) { + return true; + } + + if (Array.isArray(value) && !value.length) { + return true; + } + + if (type === 'object' && !Object.keys(value).length) { + return true; + } + + return false; +} + +const types = { + integer(value) { + return types.number(value) && parseInt(value, 10) === value; + }, + string(value) { + return typeof value === 'string'; + }, + number(value) { + if (isNaN(value)) { + return false; + } + return typeof value === 'number'; + }, + "boolean": function(value) { + return typeof value === 'boolean'; + }, + "float": function(value) { + return types.number(value) && !types.integer(value); + }, + array(value) { + return Array.isArray(value); + }, + object(value) { + return typeof value === 'object' && !types.array(value); + }, + date(value) { + return value instanceof Date; + }, + timestamp(value) { + if (!this.integer(value) || Math.abs(value).toString().length > 16) { + return false + } + return true; + }, + file(value) { + return typeof value.url === 'string'; + }, + email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; + }, + url(value) { + return typeof value === 'string' && !!value.match(pattern.url); + }, + pattern(reg, value) { + try { + return new RegExp(reg).test(value); + } catch (e) { + return false; + } + }, + method(value) { + return typeof value === 'function'; + }, + idcard(value) { + return typeof value === 'string' && !!value.match(pattern.idcard); + }, + 'url-https'(value) { + return this.url(value) && value.startsWith('https://'); + }, + 'url-scheme'(value) { + return value.startsWith('://'); + }, + 'url-web'(value) { + return false; + } +} + +class RuleValidator { + + constructor(message) { + this._message = message + } + + async validateRule(fieldKey, fieldValue, value, data, allData) { + var result = null + + let rules = fieldValue.rules + + let hasRequired = rules.findIndex((item) => { + return item.required + }) + if (hasRequired < 0) { + if (value === null || value === undefined) { + return result + } + if (typeof value === 'string' && !value.length) { + return result + } + } + + var message = this._message + + if (rules === undefined) { + return message['default'] + } + + for (var i = 0; i < rules.length; i++) { + let rule = rules[i] + let vt = this._getValidateType(rule) + + Object.assign(rule, { + label: fieldValue.label || `["${fieldKey}"]` + }) + + if (RuleValidatorHelper[vt]) { + result = RuleValidatorHelper[vt](rule, value, message) + if (result != null) { + break + } + } + + if (rule.validateExpr) { + let now = Date.now() + let resultExpr = rule.validateExpr(value, allData, now) + if (resultExpr === false) { + result = this._getMessage(rule, rule.errorMessage || this._message['default']) + break + } + } + + if (rule.validateFunction) { + result = await this.validateFunction(rule, value, data, allData, vt) + if (result !== null) { + break + } + } + } + + if (result !== null) { + result = message.TAG + result + } + + return result + } + + async validateFunction(rule, value, data, allData, vt) { + let result = null + try { + let callbackMessage = null + const res = await rule.validateFunction(rule, value, allData || data, (message) => { + callbackMessage = message + }) + if (callbackMessage || (typeof res === 'string' && res) || res === false) { + result = this._getMessage(rule, callbackMessage || res, vt) + } + } catch (e) { + result = this._getMessage(rule, e.message, vt) + } + return result + } + + _getMessage(rule, message, vt) { + return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) + } + + _getValidateType(rule) { + var result = '' + if (rule.required) { + result = 'required' + } else if (rule.format) { + result = 'format' + } else if (rule.arrayType) { + result = 'arrayTypeFormat' + } else if (rule.range) { + result = 'range' + } else if (rule.maximum !== undefined || rule.minimum !== undefined) { + result = 'rangeNumber' + } else if (rule.maxLength !== undefined || rule.minLength !== undefined) { + result = 'rangeLength' + } else if (rule.pattern) { + result = 'pattern' + } else if (rule.validateFunction) { + result = 'validateFunction' + } + return result + } +} + +const RuleValidatorHelper = { + required(rule, value, message) { + if (rule.required && isEmptyValue(value, rule.format || typeof value)) { + return formatMessage(rule, rule.errorMessage || message.required); + } + + return null + }, + + range(rule, value, message) { + const { + range, + errorMessage + } = rule; + + let list = new Array(range.length); + for (let i = 0; i < range.length; i++) { + const item = range[i]; + if (types.object(item) && item.value !== undefined) { + list[i] = item.value; + } else { + list[i] = item; + } + } + + let result = false + if (Array.isArray(value)) { + result = (new Set(value.concat(list)).size === list.length); + } else { + if (list.indexOf(value) > -1) { + result = true; + } + } + + if (!result) { + return formatMessage(rule, errorMessage || message['enum']); + } + + return null + }, + + rangeNumber(rule, value, message) { + if (!types.number(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let { + minimum, + maximum, + exclusiveMinimum, + exclusiveMaximum + } = rule; + let min = exclusiveMinimum ? value <= minimum : value < minimum; + let max = exclusiveMaximum ? value >= maximum : value > maximum; + + if (minimum !== undefined && min) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ? + 'exclusiveMinimum' : 'minimum' + ]) + } else if (maximum !== undefined && max) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ? + 'exclusiveMaximum' : 'maximum' + ]) + } else if (minimum !== undefined && maximum !== undefined && (min || max)) { + return formatMessage(rule, rule.errorMessage || message['number'].range) + } + + return null + }, + + rangeLength(rule, value, message) { + if (!types.string(value) && !types.array(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let min = rule.minLength; + let max = rule.maxLength; + let val = value.length; + + if (min !== undefined && val < min) { + return formatMessage(rule, rule.errorMessage || message['length'].minLength) + } else if (max !== undefined && val > max) { + return formatMessage(rule, rule.errorMessage || message['length'].maxLength) + } else if (min !== undefined && max !== undefined && (val < min || val > max)) { + return formatMessage(rule, rule.errorMessage || message['length'].range) + } + + return null + }, + + pattern(rule, value, message) { + if (!types['pattern'](rule.pattern, value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + return null + }, + + format(rule, value, message) { + var customTypes = Object.keys(types); + var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType); + + if (customTypes.indexOf(format) > -1) { + if (!types[format](value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + } + + return null + }, + + arrayTypeFormat(rule, value, message) { + if (!Array.isArray(value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + + for (let i = 0; i < value.length; i++) { + const element = value[i]; + let formatResult = this.format(rule, element, message) + if (formatResult !== null) { + return formatResult + } + } + + return null + } +} + +class SchemaValidator extends RuleValidator { + + constructor(schema, options) { + super(SchemaValidator.message); + + this._schema = schema + this._options = options || null + } + + updateSchema(schema) { + this._schema = schema + } + + async validate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, false, allData) + } + return result.length ? result[0] : null + } + + async validateAll(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, true, allData) + } + return result + } + + async validateUpdate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidateUpdate(data, false, allData) + } + return result.length ? result[0] : null + } + + async invokeValidate(data, all, allData) { + let result = [] + let schema = this._schema + for (let key in schema) { + let value = schema[key] + let errorMessage = await this.validateRule(key, value, data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + async invokeValidateUpdate(data, all, allData) { + let result = [] + for (let key in data) { + let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + _checkFieldInSchema(data) { + var keys = Object.keys(data) + var keys2 = Object.keys(this._schema) + if (new Set(keys.concat(keys2)).size === keys2.length) { + return '' + } + + var noExistFields = keys.filter((key) => { + return keys2.indexOf(key) < 0; + }) + var errorMessage = formatMessage({ + field: JSON.stringify(noExistFields) + }, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid']) + return [{ + key: 'invalid', + errorMessage + }] + } +} + +function Message() { + return { + TAG: "", + default: '验证错误', + defaultInvalid: '提交的字段{field}在数据库中并不存在', + validateFunction: '验证无效', + required: '{label}必填', + 'enum': '{label}超出范围', + timestamp: '{label}格式无效', + whitespace: '{label}不能为空', + typeError: '{label}类型无效', + date: { + format: '{label}日期{value}格式无效', + parse: '{label}日期无法解析,{value}无效', + invalid: '{label}日期{value}无效' + }, + length: { + minLength: '{label}长度不能少于{minLength}', + maxLength: '{label}长度不能超过{maxLength}', + range: '{label}必须介于{minLength}和{maxLength}之间' + }, + number: { + minimum: '{label}不能小于{minimum}', + maximum: '{label}不能大于{maximum}', + exclusiveMinimum: '{label}不能小于等于{minimum}', + exclusiveMaximum: '{label}不能大于等于{maximum}', + range: '{label}必须介于{minimum}and{maximum}之间' + }, + pattern: { + mismatch: '{label}格式不匹配' + } + }; +} + + +SchemaValidator.message = new Message(); + +export default SchemaValidator diff --git a/im-uniapp/uni_modules/uni-forms/package.json b/im-uniapp/uni_modules/uni-forms/package.json new file mode 100644 index 0000000..1925611 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-forms", + "displayName": "uni-forms 表单", + "version": "1.4.9", + "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", + "keywords": [ + "uni-ui", + "表单", + "校验", + "表单校验", + "表单验证" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-forms/readme.md b/im-uniapp/uni_modules/uni-forms/readme.md new file mode 100644 index 0000000..63d5a04 --- /dev/null +++ b/im-uniapp/uni_modules/uni-forms/readme.md @@ -0,0 +1,23 @@ + + +## Forms 表单 + +> **组件名:uni-forms** +> 代码块: `uForms`、`uni-forms-item` +> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。 + + +uni-app的内置组件已经有了 `

`组件,用于提交表单内容。 + +然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 ``组件封装了 ``组件,内置了表单验证功能。 + +`` 提供了 `rules`属性来描述校验规则、``子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。 + +每个要校验的表单项,不管input还是checkbox,都必须放在``组件中,且一个``组件只能放置一个表单项。 + +``组件内部预留了显示error message的区域,默认是在表单项的底部。 + +另外,``组件下面的各个表单项,可以通过``包裹为不同的分组。同一``下的不同表单项目将聚拢在一起,同其他group保持垂直间距。``仅影响视觉效果。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-goods-nav/changelog.md b/im-uniapp/uni_modules/uni-goods-nav/changelog.md new file mode 100644 index 0000000..c6264c6 --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/changelog.md @@ -0,0 +1,18 @@ +## 1.2.1(2022-05-30) +- 新增 stat属性,是否开启uni统计功能 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-goods-nav](https://uniapp.dcloud.io/component/uniui/uni-goods-nav) +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json new file mode 100644 index 0000000..dcdba41 --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "shop", + "uni-goods-nav.options.cart": "cart", + "uni-goods-nav.buttonGroup.addToCart": "add to cart", + "uni-goods-nav.buttonGroup.buyNow": "buy now" +} diff --git a/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json new file mode 100644 index 0000000..48ee344 --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店铺", + "uni-goods-nav.options.cart": "购物车", + "uni-goods-nav.buttonGroup.addToCart": "加入购物车", + "uni-goods-nav.buttonGroup.buyNow": "立即购买" +} diff --git a/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json new file mode 100644 index 0000000..d0a0255 --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店鋪", + "uni-goods-nav.options.cart": "購物車", + "uni-goods-nav.buttonGroup.addToCart": "加入購物車", + "uni-goods-nav.buttonGroup.buyNow": "立即購買" +} diff --git a/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue new file mode 100644 index 0000000..8a16b17 --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-goods-nav/package.json b/im-uniapp/uni_modules/uni-goods-nav/package.json new file mode 100644 index 0000000..636e45e --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-goods-nav", + "displayName": "uni-goods-nav 商品导航", + "version": "1.2.1", + "description": "商品导航组件主要用于电商类应用底部导航,可自定义加入购物车,购买等操作", + "keywords": [ + "uni-ui", + "uniui", + "商品导航" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-goods-nav/readme.md b/im-uniapp/uni_modules/uni-goods-nav/readme.md new file mode 100644 index 0000000..07df93f --- /dev/null +++ b/im-uniapp/uni_modules/uni-goods-nav/readme.md @@ -0,0 +1,10 @@ + + +## GoodsNav 商品导航 +> **组件名:uni-goods-nav** +> 代码块: `uGoodsNav` + +商品加入购物车,立即购买等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-goods-nav) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-grid/changelog.md b/im-uniapp/uni_modules/uni-grid/changelog.md new file mode 100644 index 0000000..d301166 --- /dev/null +++ b/im-uniapp/uni_modules/uni-grid/changelog.md @@ -0,0 +1,13 @@ +## 1.4.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-grid](https://uniapp.dcloud.io/component/uniui/uni-grid) +## 1.3.2(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.3.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.3.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.2.4(2021-05-12) +- 新增 组件示例地址 +## 1.2.3(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue b/im-uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue new file mode 100644 index 0000000..19c08d7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue b/im-uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue new file mode 100644 index 0000000..0edc7ff --- /dev/null +++ b/im-uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-grid/package.json b/im-uniapp/uni_modules/uni-grid/package.json new file mode 100644 index 0000000..ccb2c91 --- /dev/null +++ b/im-uniapp/uni_modules/uni-grid/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-grid", + "displayName": "uni-grid 宫格", + "version": "1.4.0", + "description": "Grid 宫格组件,提供移动端常见的宫格布局,如九宫格。", + "keywords": [ + "uni-ui", + "uniui", + "九宫格", + "表格" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-grid/readme.md b/im-uniapp/uni_modules/uni-grid/readme.md new file mode 100644 index 0000000..0aa44cc --- /dev/null +++ b/im-uniapp/uni_modules/uni-grid/readme.md @@ -0,0 +1,11 @@ + + +## Grid 宫格 +> **组件名:uni-grid** +> 代码块: `uGrid` + + +宫格组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-grid) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-group/changelog.md b/im-uniapp/uni_modules/uni-group/changelog.md new file mode 100644 index 0000000..a7024fd --- /dev/null +++ b/im-uniapp/uni_modules/uni-group/changelog.md @@ -0,0 +1,16 @@ +## 1.2.2(2022-05-30) +- 新增 stat属性,是否开启uni统计功能 +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-group](https://uniapp.dcloud.io/component/uniui/uni-group) +## 1.1.7(2021-11-08) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- 优化 组件文档 +## 1.0.3(2021-05-12) +- 新增 组件示例地址 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 兼容 nvue 页面 diff --git a/im-uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue b/im-uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue new file mode 100644 index 0000000..3425ecd --- /dev/null +++ b/im-uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue @@ -0,0 +1,134 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-group/package.json b/im-uniapp/uni_modules/uni-group/package.json new file mode 100644 index 0000000..ea00a08 --- /dev/null +++ b/im-uniapp/uni_modules/uni-group/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-group", + "displayName": "uni-group 分组", + "version": "1.2.2", + "description": "分组组件可用于将组件用于分组,添加间隔,以产生明显的区块", + "keywords": [ + "uni-ui", + "uniui", + "group", + "分组", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-group/readme.md b/im-uniapp/uni_modules/uni-group/readme.md new file mode 100644 index 0000000..bae67f4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-group/readme.md @@ -0,0 +1,9 @@ + +## Group 分组 +> **组件名:uni-group** +> 代码块: `uGroup` + +分组组件可用于将组件分组,添加间隔,以产生明显的区块。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-group) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-icons/changelog.md b/im-uniapp/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..6449885 --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/changelog.md @@ -0,0 +1,22 @@ +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-icons/components/uni-icons/icons.js b/im-uniapp/uni_modules/uni-icons/components/uni-icons/icons.js new file mode 100644 index 0000000..7889936 --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/components/uni-icons/icons.js @@ -0,0 +1,1169 @@ +export default { + "id": "2852637", + "name": "uniui图标库", + "font_family": "uniicons", + "css_prefix_text": "uniui-", + "description": "", + "glyphs": [ + { + "icon_id": "25027049", + "name": "yanse", + "font_class": "color", + "unicode": "e6cf", + "unicode_decimal": 59087 + }, + { + "icon_id": "25027048", + "name": "wallet", + "font_class": "wallet", + "unicode": "e6b1", + "unicode_decimal": 59057 + }, + { + "icon_id": "25015720", + "name": "settings-filled", + "font_class": "settings-filled", + "unicode": "e6ce", + "unicode_decimal": 59086 + }, + { + "icon_id": "25015434", + "name": "shimingrenzheng-filled", + "font_class": "auth-filled", + "unicode": "e6cc", + "unicode_decimal": 59084 + }, + { + "icon_id": "24934246", + "name": "shop-filled", + "font_class": "shop-filled", + "unicode": "e6cd", + "unicode_decimal": 59085 + }, + { + "icon_id": "24934159", + "name": "staff-filled-01", + "font_class": "staff-filled", + "unicode": "e6cb", + "unicode_decimal": 59083 + }, + { + "icon_id": "24932461", + "name": "VIP-filled", + "font_class": "vip-filled", + "unicode": "e6c6", + "unicode_decimal": 59078 + }, + { + "icon_id": "24932462", + "name": "plus_circle_fill", + "font_class": "plus-filled", + "unicode": "e6c7", + "unicode_decimal": 59079 + }, + { + "icon_id": "24932463", + "name": "folder_add-filled", + "font_class": "folder-add-filled", + "unicode": "e6c8", + "unicode_decimal": 59080 + }, + { + "icon_id": "24932464", + "name": "yanse-filled", + "font_class": "color-filled", + "unicode": "e6c9", + "unicode_decimal": 59081 + }, + { + "icon_id": "24932465", + "name": "tune-filled", + "font_class": "tune-filled", + "unicode": "e6ca", + "unicode_decimal": 59082 + }, + { + "icon_id": "24932455", + "name": "a-rilidaka-filled", + "font_class": "calendar-filled", + "unicode": "e6c0", + "unicode_decimal": 59072 + }, + { + "icon_id": "24932456", + "name": "notification-filled", + "font_class": "notification-filled", + "unicode": "e6c1", + "unicode_decimal": 59073 + }, + { + "icon_id": "24932457", + "name": "wallet-filled", + "font_class": "wallet-filled", + "unicode": "e6c2", + "unicode_decimal": 59074 + }, + { + "icon_id": "24932458", + "name": "paihangbang-filled", + "font_class": "medal-filled", + "unicode": "e6c3", + "unicode_decimal": 59075 + }, + { + "icon_id": "24932459", + "name": "gift-filled", + "font_class": "gift-filled", + "unicode": "e6c4", + "unicode_decimal": 59076 + }, + { + "icon_id": "24932460", + "name": "fire-filled", + "font_class": "fire-filled", + "unicode": "e6c5", + "unicode_decimal": 59077 + }, + { + "icon_id": "24928001", + "name": "refreshempty", + "font_class": "refreshempty", + "unicode": "e6bf", + "unicode_decimal": 59071 + }, + { + "icon_id": "24926853", + "name": "location-ellipse", + "font_class": "location-filled", + "unicode": "e6af", + "unicode_decimal": 59055 + }, + { + "icon_id": "24926735", + "name": "person-filled", + "font_class": "person-filled", + "unicode": "e69d", + "unicode_decimal": 59037 + }, + { + "icon_id": "24926703", + "name": "personadd-filled", + "font_class": "personadd-filled", + "unicode": "e698", + "unicode_decimal": 59032 + }, + { + "icon_id": "24923351", + "name": "back", + "font_class": "back", + "unicode": "e6b9", + "unicode_decimal": 59065 + }, + { + "icon_id": "24923352", + "name": "forward", + "font_class": "forward", + "unicode": "e6ba", + "unicode_decimal": 59066 + }, + { + "icon_id": "24923353", + "name": "arrowthinright", + "font_class": "arrow-right", + "unicode": "e6bb", + "unicode_decimal": 59067 + }, + { + "icon_id": "24923353", + "name": "arrowthinright", + "font_class": "arrowthinright", + "unicode": "e6bb", + "unicode_decimal": 59067 + }, + { + "icon_id": "24923354", + "name": "arrowthinleft", + "font_class": "arrow-left", + "unicode": "e6bc", + "unicode_decimal": 59068 + }, + { + "icon_id": "24923354", + "name": "arrowthinleft", + "font_class": "arrowthinleft", + "unicode": "e6bc", + "unicode_decimal": 59068 + }, + { + "icon_id": "24923355", + "name": "arrowthinup", + "font_class": "arrow-up", + "unicode": "e6bd", + "unicode_decimal": 59069 + }, + { + "icon_id": "24923355", + "name": "arrowthinup", + "font_class": "arrowthinup", + "unicode": "e6bd", + "unicode_decimal": 59069 + }, + { + "icon_id": "24923356", + "name": "arrowthindown", + "font_class": "arrow-down", + "unicode": "e6be", + "unicode_decimal": 59070 + },{ + "icon_id": "24923356", + "name": "arrowthindown", + "font_class": "arrowthindown", + "unicode": "e6be", + "unicode_decimal": 59070 + }, + { + "icon_id": "24923349", + "name": "arrowdown", + "font_class": "bottom", + "unicode": "e6b8", + "unicode_decimal": 59064 + },{ + "icon_id": "24923349", + "name": "arrowdown", + "font_class": "arrowdown", + "unicode": "e6b8", + "unicode_decimal": 59064 + }, + { + "icon_id": "24923346", + "name": "arrowright", + "font_class": "right", + "unicode": "e6b5", + "unicode_decimal": 59061 + }, + { + "icon_id": "24923346", + "name": "arrowright", + "font_class": "arrowright", + "unicode": "e6b5", + "unicode_decimal": 59061 + }, + { + "icon_id": "24923347", + "name": "arrowup", + "font_class": "top", + "unicode": "e6b6", + "unicode_decimal": 59062 + }, + { + "icon_id": "24923347", + "name": "arrowup", + "font_class": "arrowup", + "unicode": "e6b6", + "unicode_decimal": 59062 + }, + { + "icon_id": "24923348", + "name": "arrowleft", + "font_class": "left", + "unicode": "e6b7", + "unicode_decimal": 59063 + }, + { + "icon_id": "24923348", + "name": "arrowleft", + "font_class": "arrowleft", + "unicode": "e6b7", + "unicode_decimal": 59063 + }, + { + "icon_id": "24923334", + "name": "eye", + "font_class": "eye", + "unicode": "e651", + "unicode_decimal": 58961 + }, + { + "icon_id": "24923335", + "name": "eye-filled", + "font_class": "eye-filled", + "unicode": "e66a", + "unicode_decimal": 58986 + }, + { + "icon_id": "24923336", + "name": "eye-slash", + "font_class": "eye-slash", + "unicode": "e6b3", + "unicode_decimal": 59059 + }, + { + "icon_id": "24923337", + "name": "eye-slash-filled", + "font_class": "eye-slash-filled", + "unicode": "e6b4", + "unicode_decimal": 59060 + }, + { + "icon_id": "24923305", + "name": "info-filled", + "font_class": "info-filled", + "unicode": "e649", + "unicode_decimal": 58953 + }, + { + "icon_id": "24923299", + "name": "reload-01", + "font_class": "reload", + "unicode": "e6b2", + "unicode_decimal": 59058 + }, + { + "icon_id": "24923195", + "name": "mic_slash_fill", + "font_class": "micoff-filled", + "unicode": "e6b0", + "unicode_decimal": 59056 + }, + { + "icon_id": "24923165", + "name": "map-pin-ellipse", + "font_class": "map-pin-ellipse", + "unicode": "e6ac", + "unicode_decimal": 59052 + }, + { + "icon_id": "24923166", + "name": "map-pin", + "font_class": "map-pin", + "unicode": "e6ad", + "unicode_decimal": 59053 + }, + { + "icon_id": "24923167", + "name": "location", + "font_class": "location", + "unicode": "e6ae", + "unicode_decimal": 59054 + }, + { + "icon_id": "24923064", + "name": "starhalf", + "font_class": "starhalf", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "24923065", + "name": "star", + "font_class": "star", + "unicode": "e688", + "unicode_decimal": 59016 + }, + { + "icon_id": "24923066", + "name": "star-filled", + "font_class": "star-filled", + "unicode": "e68f", + "unicode_decimal": 59023 + }, + { + "icon_id": "24899646", + "name": "a-rilidaka", + "font_class": "calendar", + "unicode": "e6a0", + "unicode_decimal": 59040 + }, + { + "icon_id": "24899647", + "name": "fire", + "font_class": "fire", + "unicode": "e6a1", + "unicode_decimal": 59041 + }, + { + "icon_id": "24899648", + "name": "paihangbang", + "font_class": "medal", + "unicode": "e6a2", + "unicode_decimal": 59042 + }, + { + "icon_id": "24899649", + "name": "font", + "font_class": "font", + "unicode": "e6a3", + "unicode_decimal": 59043 + }, + { + "icon_id": "24899650", + "name": "gift", + "font_class": "gift", + "unicode": "e6a4", + "unicode_decimal": 59044 + }, + { + "icon_id": "24899651", + "name": "link", + "font_class": "link", + "unicode": "e6a5", + "unicode_decimal": 59045 + }, + { + "icon_id": "24899652", + "name": "notification", + "font_class": "notification", + "unicode": "e6a6", + "unicode_decimal": 59046 + }, + { + "icon_id": "24899653", + "name": "staff", + "font_class": "staff", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "24899654", + "name": "VIP", + "font_class": "vip", + "unicode": "e6a8", + "unicode_decimal": 59048 + }, + { + "icon_id": "24899655", + "name": "folder_add", + "font_class": "folder-add", + "unicode": "e6a9", + "unicode_decimal": 59049 + }, + { + "icon_id": "24899656", + "name": "tune", + "font_class": "tune", + "unicode": "e6aa", + "unicode_decimal": 59050 + }, + { + "icon_id": "24899657", + "name": "shimingrenzheng", + "font_class": "auth", + "unicode": "e6ab", + "unicode_decimal": 59051 + }, + { + "icon_id": "24899565", + "name": "person", + "font_class": "person", + "unicode": "e699", + "unicode_decimal": 59033 + }, + { + "icon_id": "24899566", + "name": "email-filled", + "font_class": "email-filled", + "unicode": "e69a", + "unicode_decimal": 59034 + }, + { + "icon_id": "24899567", + "name": "phone-filled", + "font_class": "phone-filled", + "unicode": "e69b", + "unicode_decimal": 59035 + }, + { + "icon_id": "24899568", + "name": "phone", + "font_class": "phone", + "unicode": "e69c", + "unicode_decimal": 59036 + }, + { + "icon_id": "24899570", + "name": "email", + "font_class": "email", + "unicode": "e69e", + "unicode_decimal": 59038 + }, + { + "icon_id": "24899571", + "name": "personadd", + "font_class": "personadd", + "unicode": "e69f", + "unicode_decimal": 59039 + }, + { + "icon_id": "24899558", + "name": "chatboxes-filled", + "font_class": "chatboxes-filled", + "unicode": "e692", + "unicode_decimal": 59026 + }, + { + "icon_id": "24899559", + "name": "contact", + "font_class": "contact", + "unicode": "e693", + "unicode_decimal": 59027 + }, + { + "icon_id": "24899560", + "name": "chatbubble-filled", + "font_class": "chatbubble-filled", + "unicode": "e694", + "unicode_decimal": 59028 + }, + { + "icon_id": "24899561", + "name": "contact-filled", + "font_class": "contact-filled", + "unicode": "e695", + "unicode_decimal": 59029 + }, + { + "icon_id": "24899562", + "name": "chatboxes", + "font_class": "chatboxes", + "unicode": "e696", + "unicode_decimal": 59030 + }, + { + "icon_id": "24899563", + "name": "chatbubble", + "font_class": "chatbubble", + "unicode": "e697", + "unicode_decimal": 59031 + }, + { + "icon_id": "24881290", + "name": "upload-filled", + "font_class": "upload-filled", + "unicode": "e68e", + "unicode_decimal": 59022 + }, + { + "icon_id": "24881292", + "name": "upload", + "font_class": "upload", + "unicode": "e690", + "unicode_decimal": 59024 + }, + { + "icon_id": "24881293", + "name": "weixin", + "font_class": "weixin", + "unicode": "e691", + "unicode_decimal": 59025 + }, + { + "icon_id": "24881274", + "name": "compose", + "font_class": "compose", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "24881275", + "name": "qq", + "font_class": "qq", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "24881276", + "name": "download-filled", + "font_class": "download-filled", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "24881277", + "name": "pengyouquan", + "font_class": "pyq", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "24881279", + "name": "sound", + "font_class": "sound", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "24881280", + "name": "trash-filled", + "font_class": "trash-filled", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "24881281", + "name": "sound-filled", + "font_class": "sound-filled", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "24881282", + "name": "trash", + "font_class": "trash", + "unicode": "e687", + "unicode_decimal": 59015 + }, + { + "icon_id": "24881284", + "name": "videocam-filled", + "font_class": "videocam-filled", + "unicode": "e689", + "unicode_decimal": 59017 + }, + { + "icon_id": "24881285", + "name": "spinner-cycle", + "font_class": "spinner-cycle", + "unicode": "e68a", + "unicode_decimal": 59018 + }, + { + "icon_id": "24881286", + "name": "weibo", + "font_class": "weibo", + "unicode": "e68b", + "unicode_decimal": 59019 + }, + { + "icon_id": "24881288", + "name": "videocam", + "font_class": "videocam", + "unicode": "e68c", + "unicode_decimal": 59020 + }, + { + "icon_id": "24881289", + "name": "download", + "font_class": "download", + "unicode": "e68d", + "unicode_decimal": 59021 + }, + { + "icon_id": "24879601", + "name": "help", + "font_class": "help", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "24879602", + "name": "navigate-filled", + "font_class": "navigate-filled", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "24879603", + "name": "plusempty", + "font_class": "plusempty", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "24879604", + "name": "smallcircle", + "font_class": "smallcircle", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "24879605", + "name": "minus-filled", + "font_class": "minus-filled", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "24879606", + "name": "micoff", + "font_class": "micoff", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "24879588", + "name": "closeempty", + "font_class": "closeempty", + "unicode": "e66c", + "unicode_decimal": 58988 + }, + { + "icon_id": "24879589", + "name": "clear", + "font_class": "clear", + "unicode": "e66d", + "unicode_decimal": 58989 + }, + { + "icon_id": "24879590", + "name": "navigate", + "font_class": "navigate", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "24879591", + "name": "minus", + "font_class": "minus", + "unicode": "e66f", + "unicode_decimal": 58991 + }, + { + "icon_id": "24879592", + "name": "image", + "font_class": "image", + "unicode": "e670", + "unicode_decimal": 58992 + }, + { + "icon_id": "24879593", + "name": "mic", + "font_class": "mic", + "unicode": "e671", + "unicode_decimal": 58993 + }, + { + "icon_id": "24879594", + "name": "paperplane", + "font_class": "paperplane", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "24879595", + "name": "close", + "font_class": "close", + "unicode": "e673", + "unicode_decimal": 58995 + }, + { + "icon_id": "24879596", + "name": "help-filled", + "font_class": "help-filled", + "unicode": "e674", + "unicode_decimal": 58996 + }, + { + "icon_id": "24879597", + "name": "plus-filled", + "font_class": "paperplane-filled", + "unicode": "e675", + "unicode_decimal": 58997 + }, + { + "icon_id": "24879598", + "name": "plus", + "font_class": "plus", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "24879599", + "name": "mic-filled", + "font_class": "mic-filled", + "unicode": "e677", + "unicode_decimal": 58999 + }, + { + "icon_id": "24879600", + "name": "image-filled", + "font_class": "image-filled", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "24855900", + "name": "locked-filled", + "font_class": "locked-filled", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "24855901", + "name": "info", + "font_class": "info", + "unicode": "e669", + "unicode_decimal": 58985 + }, + { + "icon_id": "24855903", + "name": "locked", + "font_class": "locked", + "unicode": "e66b", + "unicode_decimal": 58987 + }, + { + "icon_id": "24855884", + "name": "camera-filled", + "font_class": "camera-filled", + "unicode": "e658", + "unicode_decimal": 58968 + }, + { + "icon_id": "24855885", + "name": "chat-filled", + "font_class": "chat-filled", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "24855886", + "name": "camera", + "font_class": "camera", + "unicode": "e65a", + "unicode_decimal": 58970 + }, + { + "icon_id": "24855887", + "name": "circle", + "font_class": "circle", + "unicode": "e65b", + "unicode_decimal": 58971 + }, + { + "icon_id": "24855888", + "name": "checkmarkempty", + "font_class": "checkmarkempty", + "unicode": "e65c", + "unicode_decimal": 58972 + }, + { + "icon_id": "24855889", + "name": "chat", + "font_class": "chat", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "24855890", + "name": "circle-filled", + "font_class": "circle-filled", + "unicode": "e65e", + "unicode_decimal": 58974 + }, + { + "icon_id": "24855891", + "name": "flag", + "font_class": "flag", + "unicode": "e65f", + "unicode_decimal": 58975 + }, + { + "icon_id": "24855892", + "name": "flag-filled", + "font_class": "flag-filled", + "unicode": "e660", + "unicode_decimal": 58976 + }, + { + "icon_id": "24855893", + "name": "gear-filled", + "font_class": "gear-filled", + "unicode": "e661", + "unicode_decimal": 58977 + }, + { + "icon_id": "24855894", + "name": "home", + "font_class": "home", + "unicode": "e662", + "unicode_decimal": 58978 + }, + { + "icon_id": "24855895", + "name": "home-filled", + "font_class": "home-filled", + "unicode": "e663", + "unicode_decimal": 58979 + }, + { + "icon_id": "24855896", + "name": "gear", + "font_class": "gear", + "unicode": "e664", + "unicode_decimal": 58980 + }, + { + "icon_id": "24855897", + "name": "smallcircle-filled", + "font_class": "smallcircle-filled", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "24855898", + "name": "map-filled", + "font_class": "map-filled", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "24855899", + "name": "map", + "font_class": "map", + "unicode": "e667", + "unicode_decimal": 58983 + }, + { + "icon_id": "24855825", + "name": "refresh-filled", + "font_class": "refresh-filled", + "unicode": "e656", + "unicode_decimal": 58966 + }, + { + "icon_id": "24855826", + "name": "refresh", + "font_class": "refresh", + "unicode": "e657", + "unicode_decimal": 58967 + }, + { + "icon_id": "24855808", + "name": "cloud-upload", + "font_class": "cloud-upload", + "unicode": "e645", + "unicode_decimal": 58949 + }, + { + "icon_id": "24855809", + "name": "cloud-download-filled", + "font_class": "cloud-download-filled", + "unicode": "e646", + "unicode_decimal": 58950 + }, + { + "icon_id": "24855810", + "name": "cloud-download", + "font_class": "cloud-download", + "unicode": "e647", + "unicode_decimal": 58951 + }, + { + "icon_id": "24855811", + "name": "cloud-upload-filled", + "font_class": "cloud-upload-filled", + "unicode": "e648", + "unicode_decimal": 58952 + }, + { + "icon_id": "24855813", + "name": "redo", + "font_class": "redo", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "24855814", + "name": "images-filled", + "font_class": "images-filled", + "unicode": "e64b", + "unicode_decimal": 58955 + }, + { + "icon_id": "24855815", + "name": "undo-filled", + "font_class": "undo-filled", + "unicode": "e64c", + "unicode_decimal": 58956 + }, + { + "icon_id": "24855816", + "name": "more", + "font_class": "more", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "24855817", + "name": "more-filled", + "font_class": "more-filled", + "unicode": "e64e", + "unicode_decimal": 58958 + }, + { + "icon_id": "24855818", + "name": "undo", + "font_class": "undo", + "unicode": "e64f", + "unicode_decimal": 58959 + }, + { + "icon_id": "24855819", + "name": "images", + "font_class": "images", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "24855821", + "name": "paperclip", + "font_class": "paperclip", + "unicode": "e652", + "unicode_decimal": 58962 + }, + { + "icon_id": "24855822", + "name": "settings", + "font_class": "settings", + "unicode": "e653", + "unicode_decimal": 58963 + }, + { + "icon_id": "24855823", + "name": "search", + "font_class": "search", + "unicode": "e654", + "unicode_decimal": 58964 + }, + { + "icon_id": "24855824", + "name": "redo-filled", + "font_class": "redo-filled", + "unicode": "e655", + "unicode_decimal": 58965 + }, + { + "icon_id": "24841702", + "name": "list", + "font_class": "list", + "unicode": "e644", + "unicode_decimal": 58948 + }, + { + "icon_id": "24841489", + "name": "mail-open-filled", + "font_class": "mail-open-filled", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "24841491", + "name": "hand-thumbsdown-filled", + "font_class": "hand-down-filled", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "24841492", + "name": "hand-thumbsdown", + "font_class": "hand-down", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "24841493", + "name": "hand-thumbsup-filled", + "font_class": "hand-up-filled", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "24841494", + "name": "hand-thumbsup", + "font_class": "hand-up", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "24841496", + "name": "heart-filled", + "font_class": "heart-filled", + "unicode": "e641", + "unicode_decimal": 58945 + }, + { + "icon_id": "24841498", + "name": "mail-open", + "font_class": "mail-open", + "unicode": "e643", + "unicode_decimal": 58947 + }, + { + "icon_id": "24841488", + "name": "heart", + "font_class": "heart", + "unicode": "e639", + "unicode_decimal": 58937 + }, + { + "icon_id": "24839963", + "name": "loop", + "font_class": "loop", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "24839866", + "name": "pulldown", + "font_class": "pulldown", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "24813798", + "name": "scan", + "font_class": "scan", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "24813786", + "name": "bars", + "font_class": "bars", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "24813788", + "name": "cart-filled", + "font_class": "cart-filled", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "24813790", + "name": "checkbox", + "font_class": "checkbox", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "24813791", + "name": "checkbox-filled", + "font_class": "checkbox-filled", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "24813794", + "name": "shop", + "font_class": "shop", + "unicode": "e62f", + "unicode_decimal": 58927 + }, + { + "icon_id": "24813795", + "name": "headphones", + "font_class": "headphones", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "24813796", + "name": "cart", + "font_class": "cart", + "unicode": "e631", + "unicode_decimal": 58929 + } + ] +} diff --git a/im-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/im-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..86e7444 --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css b/im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000..2f56eab --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,663 @@ +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrowthinright:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrowthinleft:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrowthinup:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthindown:before { + content: "\e6be"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowdown:before { + content: "\e6b8"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-arrowright:before { + content: "\e6b5"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-arrowup:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowleft:before { + content: "\e6b7"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-cart-filled:before { + content: "\e629"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/im-uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..835f33bc9411461b8b9046b3fec742e921d6ce3a GIT binary patch literal 35760 zcmeFacbp?vl{Q?rN>WLsSXIigCAC7gI!S6tox^m`be^1}CwPJ>yxZ{uSzi{WC_=LI+*Wx!<-H!v})21fuKMZQ#fB2Fs zuEGwU_ZyDmuR3&e_s&PS@Be}0T=(JF$l;w=9Als0-iQ4lE^0cm^YDeWhnK#G<4!$@ z>-QZydhsP6Kl0Pv9Cw|EbP-?YO$@C;`+ zuKnR1zxV)MXnddfv56Dn_-W<%#_jZ^d%1f!hyI(6O8C!lXY}93nl=3f9^gzbYS03Jm0vJ;T)Z1IKntfUtypB#zx`e6wG3 zC5AhB&Me^QdH${3yL1otSUp#NN^cKN_yKnkm%XqxczWUgrT^Oo|6lL`SPy&+DZ_DX z1{?B^82rf|!cpSXHhg1*;kZecHy+MI*MqCu>g9T0eYieaf9edzAC9!tJL|pm0UQ~l zBQN;JaG!_sb8)VT%i=%By_I_#_ZIFh?(N(=xVtf?-^|^~y@`7RcMEqbcN=#*_eSmx z?)BVjxU0EqFm9T;Q`~9pTJAdTdhWH{>+qBtxEr~fxSP3Gb60T(xkKDx?g)33JH{R7 zPH-1465x0fg%5CGeb33>TxSiZCZa24wyO7(&Q z>$wfwMs5?gnVaOMxM^;NYvpFSIc_<(f-7(s&u0CxL#u8VbF$klItIryQ@x|5W z48lm`>dOtnnB(dz48rK+>MISxIOOW948n-y>Z=XH*yQSK48kbo>T3axcUVKK~8Y>od!W-aP?gVL3VKU-3CF5aP>U~L7s5+3k`x~;p%%0f{fwn z`wW7#;p+Phg52Ti2MmG);_4R}1X;w@scs-hC9Y0&13^AJNK#+l4o$3aH zH00`3HxT3^SEssxAR)Or)eQt$$+z7tkWDxWPSHIIBXb!IaW`m$ZxcXZRf;QpmZ#4*d zg{!~KAZQq_ewRVeHC+Ae20`m^^>-Kq{lnGoHVB%CtKVY~bP`v;*C1#ou700E&{JIf zeuJQ~xcWN{g6`t#?=lEljH|!fAm}r${tpI0vvKtY41$j1>hCcK+K#I~Xb|)sSAVZT z(12Y1eFi}na`pEc1g*%`|Ir}mN3Q+>gPp}tAE%avLosrF^GJG z`bQ0dZsqD9GYDFitAE@e=v%J-34@?{x%wv!f)3{DpE3yAn5#cz5cD!v|Fl8S&|Lj9 z20>SI_0JjvtVjlpxwFpV+N5QQ~$g{(D+>a3kE^= zbM-G81S^26f5{-&16=*f2Ej7m>R&MkHUd}wszKz<)E_qpb^}-cnn7g6)t@j3wggxI zxp1QBm2=klxXa|a&TVx+;Qp=WwVt{>D}O~9QXW)O>O0h5db_-@^8Ud$^pPT-1=NS-v_4k2r|_kM*0!vaW9xyNll~`AaKH zuPQxR4wv6t{%QC2?wh**v$DGKU?rS_5?F|crGafkiL7dNk-%Y_E4VmkSn6>Hb;SUukEx3l0# zej=BS$yT%Av8Uc*J`xpdRw>aQ5n{34Scli{%@0hjo|#!%t;CXMOO1~vM@By$$wu(0 z7+*JsbCF0c%-{dHFL+pQI~s3&zjaOf89{d%!`6o3fsCO(`6

PqiNa$~E4XJ#a8s%?nsx`&SiQ7Wz{CE9c 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 literal 0 HcmV?d00001 diff --git a/im-uniapp/uni_modules/uni-icons/package.json b/im-uniapp/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..d1c4e77 --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "1.3.5", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-icons/readme.md b/im-uniapp/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..86234ba --- /dev/null +++ b/im-uniapp/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-indexed-list/changelog.md b/im-uniapp/uni_modules/uni-indexed-list/changelog.md new file mode 100644 index 0000000..08fa71c --- /dev/null +++ b/im-uniapp/uni_modules/uni-indexed-list/changelog.md @@ -0,0 +1,17 @@ +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-indexed-list](https://uniapp.dcloud.io/component/uniui/uni-indexed-list) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 diff --git a/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue b/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue new file mode 100644 index 0000000..19284bd --- /dev/null +++ b/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue b/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue new file mode 100644 index 0000000..ee3a7ec --- /dev/null +++ b/im-uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue @@ -0,0 +1,367 @@ + + + diff --git a/im-uniapp/uni_modules/uni-indexed-list/package.json b/im-uniapp/uni_modules/uni-indexed-list/package.json new file mode 100644 index 0000000..125c0e7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-indexed-list/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-indexed-list", + "displayName": "uni-indexed-list 索引列表", + "version": "1.2.1", + "description": "索引列表组件,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景", + "keywords": [ + "uni-ui", + "索引列表", + "索引", + "列表" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-indexed-list/readme.md b/im-uniapp/uni_modules/uni-indexed-list/readme.md new file mode 100644 index 0000000..44ad84b --- /dev/null +++ b/im-uniapp/uni_modules/uni-indexed-list/readme.md @@ -0,0 +1,11 @@ + + +## IndexedList 索引列表 +> **组件名:uni-indexed-list** +> 代码块: `uIndexedList` + + +用于展示索引列表。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-indexed-list) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-link/changelog.md b/im-uniapp/uni_modules/uni-link/changelog.md new file mode 100644 index 0000000..2cfbf59 --- /dev/null +++ b/im-uniapp/uni_modules/uni-link/changelog.md @@ -0,0 +1,17 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-link](https://uniapp.dcloud.io/component/uniui/uni-link) +## 1.1.7(2021-11-08) +## 0.0.7(2021-09-03) +- 修复 在 nvue 下不显示的 bug +## 0.0.6(2021-07-30) +- 新增 支持自定义插槽 +## 0.0.5(2021-06-21) +- 新增 download 属性,H5平台下载文件名 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-03-09) +- 新增 href 属性支持 tel:|mailto: + +## 0.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue b/im-uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue new file mode 100644 index 0000000..27c5468 --- /dev/null +++ b/im-uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-link/package.json b/im-uniapp/uni_modules/uni-link/package.json new file mode 100644 index 0000000..77b1986 --- /dev/null +++ b/im-uniapp/uni_modules/uni-link/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-link", + "displayName": "uni-link 超链接", + "version": "1.0.0", + "description": "uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打", + "keywords": [ + "uni-ui", + "uniui", + "link", + "超链接", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-link/readme.md b/im-uniapp/uni_modules/uni-link/readme.md new file mode 100644 index 0000000..7f09e94 --- /dev/null +++ b/im-uniapp/uni_modules/uni-link/readme.md @@ -0,0 +1,11 @@ + + +## Link 链接 +> **组件名:uni-link** +> 代码块: `uLink` + + +uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-link) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-list/changelog.md b/im-uniapp/uni_modules/uni-list/changelog.md new file mode 100644 index 0000000..8254a18 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/changelog.md @@ -0,0 +1,46 @@ +## 1.2.14(2023-04-14) +- 优化 uni-list-chat 具名插槽`header` 非app端套一层元素,方便使用时通过外层元素定位实现样式修改 +## 1.2.13(2023-03-03) +- uni-list-chat 新增 支持具名插槽`header` +## 1.2.12(2023-02-01) +- 新增 列表图标新增 customPrefix 属性 ,用法 [详见](https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html#icons-props) +## 1.2.11(2023-01-31) +- 修复 无反馈效果呈现的bug +## 1.2.9(2022-11-22) +- 修复 uni-list-chat 在vue3下跳转报错的bug +## 1.2.8(2022-11-21) +- 修复 uni-list-chat avatar属性 值为本地路径时错误的问题 +## 1.2.7(2022-11-21) +- 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题 +## 1.2.6(2022-11-18) +- 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题 +## 1.2.5(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug +## 1.2.4(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在nvue(vue2)下无效的bug +## 1.2.3(2022-11-14) +- uni-list-chat 新增 avatar 支持 fileId +## 1.2.2(2022-11-11) +- uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html) +- uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +- uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor +## 1.2.1(2022-03-30) +- 删除无用文件 +## 1.2.0(2021-11-23) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-list](https://uniapp.dcloud.io/component/uniui/uni-list) +## 1.1.3(2021-08-30) +- 修复 在vue3中to属性在发行应用的时候报错的bug +## 1.1.2(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.1(2021-07-21) +- 修复 与其他组件嵌套使用时,点击失效的Bug +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.17(2021-05-12) +- 新增 组件示例地址 +## 1.0.16(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.15(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 uni-list-chat 角标显示不正常的问题 diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue b/im-uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue new file mode 100644 index 0000000..b9349c2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss b/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss new file mode 100644 index 0000000..311f8d9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss @@ -0,0 +1,58 @@ +/** + * 这里是 uni-list 组件内置的常用样式变量 + * 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码 + * + */ + +// 背景色 +$background-color : #fff; +// 分割线颜色 +$divide-line-color : #e5e5e5; + +// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像 +// nvue 页面不支持修改头像大小 +$avatar-width : 45px ; + +// 头像边框 +$avatar-border-radius: 5px; +$avatar-border-color: #eee; +$avatar-border-width: 1px; + +// 标题文字样式 +$title-size : 16px; +$title-color : #3b4144; +$title-weight : normal; + +// 描述文字样式 +$note-size : 12px; +$note-color : #999; +$note-weight : normal; + +// 右侧额外内容默认样式 +$right-text-size : 12px; +$right-text-color : #999; +$right-text-weight : normal; + +// 角标样式 +// nvue 页面不支持修改圆点位置以及大小 +// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动 +$badge-left: 0px; +$badge-top: 0px; + +// 显示圆点时,圆点大小 +$dot-width: 10px; +$dot-height: 10px; + +// 显示角标时,角标大小和字体大小 +$badge-size : 18px; +$badge-font : 12px; +// 显示角标时,角标前景色 +$badge-color : #fff; +// 显示角标时,角标背景色 +$badge-background-color : #ff5a5f; +// 显示角标时,角标左右间距 +$badge-space : 6px; + +// 状态样式 +// 选中颜色 +$hover : #f5f5f5; diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue b/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue new file mode 100644 index 0000000..d49fd7c --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue @@ -0,0 +1,593 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue b/im-uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue new file mode 100644 index 0000000..a274ac8 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue @@ -0,0 +1,534 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue new file mode 100644 index 0000000..6ef5972 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue @@ -0,0 +1,123 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue new file mode 100644 index 0000000..3b4c5a2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs new file mode 100644 index 0000000..818a6b7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs @@ -0,0 +1,87 @@ +var pullDown = { + threshold: 95, + maxHeight: 200, + callRefresh: 'onrefresh', + callPullingDown: 'onpullingdown', + refreshSelector: '.uni-refresh' +}; + +function ready(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + state.canPullDown = newValue; + // console.log(newValue); +} + +function touchStart(e, instance) { + var state = instance.getState(); + state.refreshInstance = instance.selectComponent(pullDown.refreshSelector); + state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined); + if (!state.canPullDown) { + return + } + + // console.log("touchStart"); + + state.height = 0; + state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY; + state.refreshInstance.setStyle({ + 'height': 0 + }); + state.refreshInstance.callMethod("onchange", true); +} + +function touchMove(e, ownerInstance) { + var instance = e.instance; + var state = instance.getState(); + if (!state.canPullDown) { + return + } + + var oldHeight = state.height; + var endY = e.touches[0].pageY || e.changedTouches[0].pageY; + var height = endY - state.touchStartY; + if (height > pullDown.maxHeight) { + return; + } + + var refreshInstance = state.refreshInstance; + refreshInstance.setStyle({ + 'height': height + 'px' + }); + + height = height < pullDown.maxHeight ? height : pullDown.maxHeight; + state.height = height; + refreshInstance.callMethod(pullDown.callPullingDown, { + height: height + }); +} + +function touchEnd(e, ownerInstance) { + var state = e.instance.getState(); + if (!state.canPullDown) { + return + } + + state.refreshInstance.callMethod("onchange", false); + + var refreshInstance = state.refreshInstance; + if (state.height > pullDown.threshold) { + refreshInstance.callMethod(pullDown.callRefresh); + return; + } + + refreshInstance.setStyle({ + 'height': 0 + }); +} + +function propObserver(newValue, oldValue, instance) { + pullDown = newValue; +} + +module.exports = { + touchmove: touchMove, + touchstart: touchStart, + touchend: touchEnd, + propObserver: propObserver +} diff --git a/im-uniapp/uni_modules/uni-list/package.json b/im-uniapp/uni_modules/uni-list/package.json new file mode 100644 index 0000000..8350efc --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-list", + "displayName": "uni-list 列表", + "version": "1.2.14", + "description": "List 组件 ,帮助使用者快速构建列表。", + "keywords": [ + "", + "uni-ui", + "uniui", + "列表", + "", + "list" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-list/readme.md b/im-uniapp/uni_modules/uni-list/readme.md new file mode 100644 index 0000000..32c2865 --- /dev/null +++ b/im-uniapp/uni_modules/uni-list/readme.md @@ -0,0 +1,346 @@ +## List 列表 +> **组件名:uni-list** +> 代码块: `uList`、`uListItem` +> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad` + + +List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。 + +在vue页面里,它默认使用页面级滚动。在app-nvue页面里,它默认使用原生list组件滚动。这样的长列表,在滚动出屏幕外后,系统会回收不可见区域的渲染内存资源,不会造成滚动越长手机越卡的问题。 + +uni-list组件是父容器,里面的核心是uni-list-item子组件,它代表列表中的一个可重复行,子组件可以无限循环。 + +uni-list-item有很多风格,uni-list-item组件通过内置的属性,满足一些常用的场景。当内置属性不满足需求时,可以通过扩展插槽来自定义列表内容。 + +内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。 + +涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。 + +下文均有样例给出。 + +uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件:[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29) + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件 +> - `uni-list` 和 `uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item` +> - 只有开启点击反馈后,会有点击选中效果 +> - 使用插槽时,可以完全自定义内容 +> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展 +> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译 +> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义 +> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 基本用法 + +- 设置 `title` 属性,可以显示列表标题 +- 设置 `disabled` 属性,可以禁用当前项 + +```html + + + + + +``` + +### 多行内容显示 + +- 设置 `note` 属性 ,可以在第二行显示描述文本信息 + +```html + + + + + +``` + +### 右侧显示角标、switch + +- 设置 `show-badge` 属性 ,可以显示角标内容 +- 设置 `show-switch` 属性,可以显示 switch 开关 + +```html + + + + + +``` + +### 左侧显示略缩图、图标 + +- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图 +- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标 + +```html + + + + +``` + +### 开启点击反馈和右侧箭头 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头 +- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` + +```html + + + + + + + +``` + + +### 聊天列表示例 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` +- 设置 `to` 属性,可以跳转页面 +- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示 +- `avatar` 和 `avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 ,`avatar` 属性将失效 +- 可以通过默认插槽自定义列表右侧内容 + +```html + + + + + + + + + + + + + + + + + 刚刚 + + + + + + + +``` + +```javascript + +export default { + components: {}, + data() { + return { + avatarList: [{ + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }] + } + } +} + +``` + + +```css + +.chat-custom-right { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + align-items: flex-end; +} + +.chat-custom-text { + font-size: 12px; + color: #999; +} + +``` + +## API + +### List Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +border |Boolean |true | 是否显示边框 + + +### ListItem Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +ellipsis |Number |0 | title 是否溢出隐藏,可选值,0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】 +thumb |String |- | 左侧缩略图,若thumb有值,则不会显示扩展图标 +thumbSize |String |medium | 略缩图尺寸,可选值,lg:大图; medium:一般; sm:小图; +showBadge |Boolean |false | 是否显示数字角标 +badgeText |String |- | 数字角标内容 +badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21) +badgeStyle |Object |- | 数字角标样式,使用uni-badge的custom-style参数 +rightText |String |- | 右侧文字内容 +disabled |Boolean |false | 是否禁用 +showArrow |Boolean |true | 是否显示箭头图标 +link |String |navigateTo | 新页面跳转方式,可选值见下表 +to |String |- | 新页面跳转地址,如填写此属性,click 会返回页面是否跳转成功 +clickable |Boolean |false | 是否开启点击反馈 +showSwitch |Boolean |false | 是否显示Switch +switchChecked |Boolean |false | Switch是否被选中 +showExtraIcon |Boolean |false | 左侧是否显示扩展图标 +extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28) +direction | String |row | 排版方向,可选值,row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排,也受此属性控制 + + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItem Events + +事件称名 |说明 |返回参数 +:-: |:-: |:-: +click |点击 uniListItem 触发事件,需开启点击反馈 |- +switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked} + + + +### ListItem Slots + +名称 | 说明 +:-: | :-: +header | 左/上内容插槽,可完全自定义默认显示 +body | 中间内容插槽,可完全自定义中间内容 +footer | 右/下内容插槽,可完全自定义右侧内容 + + +> **通过插槽扩展** +> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现 +> 如果 `uni-list-item` 组件内置属性样式无法满足需求,可以使用插槽来自定义uni-list-item里的内容。 +> uni-list-item提供了3个可扩展的插槽:`header`、`body`、`footer` +> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分 +> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分 +> 开发者可以只用1个插槽,也可以3个一起使用。在插槽中可自主编写view标签,实现自己所需的效果。 + + +**示例** + +```html + + + + + + + + + 自定义插槽 + + + + +``` + + + + + +### ListItemChat Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +clickable |Boolean |false | 是否开启点击反馈 +badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点 +badgePositon |String |right | 角标位置 +link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表 +clickable |Boolean |false | 是否开启点击反馈 +to |String |- | 跳转页面地址,如填写此属性,click 会返回页面是否跳转成功 +time |String |- | 右侧时间显示 +avatarCircle |Boolean |false | 是否显示圆形头像 +avatar |String |- | 头像地址,avatarCircle 不填时生效 +avatarList |Array |- | 头像组,格式为 [{url:''}] + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItemChat Slots + +名称 | 说明 +:- | :- +default | 自定义列表右侧内容(包括时间和角标显示) + +### ListItemChat Events +事件称名 | 说明 | 返回参数 +:-: | :-: | :-: +@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息 + + + + + + +## 基于uni-list扩展的页面模板 + +通过扩展插槽,可实现多种常见样式的列表 + +**新闻列表类** + +1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546) +2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583) +3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584) +4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585) +5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586) +6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587) +7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588) + +**商品列表类** + +1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651) +2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671) +3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list) \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-load-more/changelog.md b/im-uniapp/uni_modules/uni-load-more/changelog.md new file mode 100644 index 0000000..8f03f1d --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/changelog.md @@ -0,0 +1,19 @@ +## 1.3.3(2022-01-20) +- 新增 showText属性 ,是否显示文本 +## 1.3.2(2022-01-19) +- 修复 nvue 平台下不显示文本的bug +## 1.3.1(2022-01-19) +- 修复 微信小程序平台样式选择器报警告的问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-load-more](https://uniapp.dcloud.io/component/uniui/uni-load-more) +## 1.2.1(2021-08-24) +- 新增 支持国际化 +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.8(2021-05-12) +- 新增 组件示例地址 +## 1.1.7(2021-03-30) +- 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +## 1.1.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json new file mode 100644 index 0000000..a4f14a5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "Pull up to show more", + "uni-load-more.contentrefresh": "loading...", + "uni-load-more.contentnomore": "No more data" +} diff --git a/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json new file mode 100644 index 0000000..f15d510 --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉显示更多", + "uni-load-more.contentrefresh": "正在加载...", + "uni-load-more.contentnomore": "没有更多数据了" +} diff --git a/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json new file mode 100644 index 0000000..a255c6d --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉顯示更多", + "uni-load-more.contentrefresh": "正在加載...", + "uni-load-more.contentnomore": "沒有更多數據了" +} diff --git a/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue new file mode 100644 index 0000000..e5eff4d --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-load-more/package.json b/im-uniapp/uni_modules/uni-load-more/package.json new file mode 100644 index 0000000..2fa6f04 --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-load-more", + "displayName": "uni-load-more 加载更多", + "version": "1.3.3", + "description": "LoadMore 组件,常用在列表里面,做滚动加载使用。", + "keywords": [ + "uni-ui", + "uniui", + "加载更多", + "load-more" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-load-more/readme.md b/im-uniapp/uni_modules/uni-load-more/readme.md new file mode 100644 index 0000000..54dc1fa --- /dev/null +++ b/im-uniapp/uni_modules/uni-load-more/readme.md @@ -0,0 +1,14 @@ + + +### LoadMore 加载更多 +> **组件名:uni-load-more** +> 代码块: `uLoadMore` + + +用于列表中,做滚动加载使用,展示 loading 的各种状态。 + + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-load-more) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-nav-bar/changelog.md b/im-uniapp/uni_modules/uni-nav-bar/changelog.md new file mode 100644 index 0000000..0f9a2f1 --- /dev/null +++ b/im-uniapp/uni_modules/uni-nav-bar/changelog.md @@ -0,0 +1,51 @@ +## 1.3.11(2023-03-29) +- 修复 自定义状态栏高度闪动BUG +## 1.3.10(2023-03-29) +- 修复 暗黑模式下边线颜色错误的bug +## 1.3.9(2022-10-13) +- 修复 条件编译错误的bug +## 1.3.8(2022-10-12) +- 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.7(2022-08-11) +- 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.6(2022-06-30) +- 修复 组件示例中插槽用法无法显示内容的bug +## 1.3.5(2022-05-24) +- 新增 stat 属性 ,可开启统计title 上报 ,仅使用了title 属性且项目开启了uni统计生效 +## 1.3.4(2022-01-24) +- 更新 组件示例 +## 1.3.3(2022-01-24) +- 新增 left-width/right-width属性 ,可修改左右两侧的宽度 +## 1.3.2(2022-01-18) +- 修复 在vue下,标题不垂直居中的bug +## 1.3.1(2022-01-18) +- 修复 height 属性类型错误 +## 1.3.0(2022-01-18) +- 新增 height 属性,可修改组件高度 +- 新增 dark 属性可可开启暗黑模式 +- 优化 标题字数过多显示省略号 +- 优化 插槽,插入内容可完全覆盖 +## 1.2.1(2022-01-10) +- 修复 color 属性不生效的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-nav-bar](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-30) +- 修复 在nvue下fixed为true,宽度不能撑满的Bug +## 1.0.9(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.8(2021-04-14) +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug + +## 1.0.7(2021-02-25) +- 修复 easycom 下,找不到 uni-status-bar 的bug + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue b/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue new file mode 100644 index 0000000..c890860 --- /dev/null +++ b/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue b/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue new file mode 100644 index 0000000..4ac73ae --- /dev/null +++ b/im-uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-nav-bar/package.json b/im-uniapp/uni_modules/uni-nav-bar/package.json new file mode 100644 index 0000000..240ae95 --- /dev/null +++ b/im-uniapp/uni_modules/uni-nav-bar/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-nav-bar", + "displayName": "uni-nav-bar 自定义导航栏", + "version": "1.3.11", + "description": "自定义导航栏组件,主要用于头部导航。", + "keywords": [ + "uni-ui", + "导航", + "导航栏", + "自定义导航栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-nav-bar/readme.md b/im-uniapp/uni_modules/uni-nav-bar/readme.md new file mode 100644 index 0000000..3934b32 --- /dev/null +++ b/im-uniapp/uni_modules/uni-nav-bar/readme.md @@ -0,0 +1,15 @@ + + +## NavBar 导航栏 +> **组件名:uni-nav-bar** +> 代码块: `uNavBar` + +导航栏组件,主要用于头部导航。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/im-uniapp/uni_modules/uni-notice-bar/changelog.md b/im-uniapp/uni_modules/uni-notice-bar/changelog.md new file mode 100644 index 0000000..d526811 --- /dev/null +++ b/im-uniapp/uni_modules/uni-notice-bar/changelog.md @@ -0,0 +1,18 @@ +## 1.2.1(2022-09-05) +- 新增 属性 fontSize,可修改文字大小。 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-notice-bar](https://uniapp.dcloud.io/component/uniui/uni-notice-bar) +## 1.1.1(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 组件示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue b/im-uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue new file mode 100644 index 0000000..98d4720 --- /dev/null +++ b/im-uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue @@ -0,0 +1,426 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-notice-bar/package.json b/im-uniapp/uni_modules/uni-notice-bar/package.json new file mode 100644 index 0000000..8d9b13c --- /dev/null +++ b/im-uniapp/uni_modules/uni-notice-bar/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-notice-bar", + "displayName": "uni-notice-bar 通告栏", + "version": "1.2.1", + "description": "NoticeBar 通告栏组件,常用于展示公告信息,可设为滚动公告", + "keywords": [ + "uni-ui", + "uniui", + "通告栏", + "公告", + "跑马灯" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-notice-bar/readme.md b/im-uniapp/uni_modules/uni-notice-bar/readme.md new file mode 100644 index 0000000..fb2ede2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-notice-bar/readme.md @@ -0,0 +1,13 @@ + + +## NoticeBar 通告栏 +> **组件名:uni-notice-bar** +> 代码块: `uNoticeBar` + + +通告栏组件 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-notice-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-number-box/changelog.md b/im-uniapp/uni_modules/uni-number-box/changelog.md new file mode 100644 index 0000000..aab060d --- /dev/null +++ b/im-uniapp/uni_modules/uni-number-box/changelog.md @@ -0,0 +1,29 @@ +## 1.2.3(2023-05-23) +更新示例工程 +## 1.2.2(2023-05-08) +- 修复 change 事件执行顺序错误的问题 +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-number-box](https://uniapp.dcloud.io/component/uniui/uni-number-box) +## 1.1.2(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-20) +- 修复 uni-number-box 浮点数运算不精确的 bug +- 修复 uni-number-box change 事件触发不正确的 bug +- 新增 uni-number-box v-model 双向绑定 +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 + +## 1.0.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 v-model +- 新增 支持 focus、blur 事件 +- 新增 支持 PC 端 diff --git a/im-uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue b/im-uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue new file mode 100644 index 0000000..40786f5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue @@ -0,0 +1,221 @@ + + + diff --git a/im-uniapp/uni_modules/uni-number-box/package.json b/im-uniapp/uni_modules/uni-number-box/package.json new file mode 100644 index 0000000..743d2e4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-number-box/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-number-box", + "displayName": "uni-number-box 数字输入框", + "version": "1.2.3", + "description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。", + "keywords": [ + "uni-ui", + "uniui", + "数字输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-number-box/readme.md b/im-uniapp/uni_modules/uni-number-box/readme.md new file mode 100644 index 0000000..affc56f --- /dev/null +++ b/im-uniapp/uni_modules/uni-number-box/readme.md @@ -0,0 +1,13 @@ + + +## NumberBox 数字输入框 +> **组件名:uni-number-box** +> 代码块: `uNumberBox` + + +带加减按钮的数字输入框。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-number-box) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-pagination/changelog.md b/im-uniapp/uni_modules/uni-pagination/changelog.md new file mode 100644 index 0000000..2e94adc --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/changelog.md @@ -0,0 +1,27 @@ +## 1.2.4(2022-09-19) +- 修复,未对主题色设置默认色,导致未引入 uni-scss 变量文件报错。 +- 修复,未对移动端当前页文字做主题色适配。 +## 1.2.3(2022-09-15) +- 修复未使用 uni-scss 主题色的 bug。 +## 1.2.2(2022-07-06) +- 修复 es 语言 i18n 错误 +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-pagination](https://uniapp.dcloud.io/component/uniui/uni-pagination) +## 1.1.2(2021-10-08) +- 修复 current 、value 属性未监听,导致高亮样式失效的 bug +## 1.1.1(2021-08-20) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-12) +- 新增 PC 和 移动端适配不同的 ui +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json new file mode 100644 index 0000000..d6e2897 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "prev", + "uni-pagination.nextText": "next", + "uni-pagination.piecePerPage": "piece/page" +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json new file mode 100644 index 0000000..604a113 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "anterior", + "uni-pagination.nextText": "prxima", + "uni-pagination.piecePerPage": "Art¨ªculo/P¨¢gina" +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json new file mode 100644 index 0000000..a7a0c77 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "précédente", + "uni-pagination.nextText": "suivante", + "uni-pagination.piecePerPage": "Articles/Pages" +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js new file mode 100644 index 0000000..2469dd0 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js @@ -0,0 +1,12 @@ +import en from './en.json' +import es from './es.json' +import fr from './fr.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + es, + fr, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json new file mode 100644 index 0000000..782bbe4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "上一页", + "uni-pagination.nextText": "下一页", + "uni-pagination.piecePerPage": "条/页" +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json new file mode 100644 index 0000000..180fddb --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "上一頁", + "uni-pagination.nextText": "下一頁", + "uni-pagination.piecePerPage": "條/頁" +} diff --git a/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue new file mode 100644 index 0000000..5305b5f --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue @@ -0,0 +1,465 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-pagination/package.json b/im-uniapp/uni_modules/uni-pagination/package.json new file mode 100644 index 0000000..862d5ab --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-pagination", + "displayName": "uni-pagination 分页器", + "version": "1.2.4", + "description": "Pagination 分页器组件,用于展示页码、请求数据等。", + "keywords": [ + "uni-ui", + "uniui", + "分页器", + "页码" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-pagination/readme.md b/im-uniapp/uni_modules/uni-pagination/readme.md new file mode 100644 index 0000000..97ea1d6 --- /dev/null +++ b/im-uniapp/uni_modules/uni-pagination/readme.md @@ -0,0 +1,11 @@ + + +## Pagination 分页器 +> **组件名:uni-pagination** +> 代码块: `uPagination` + + +分页器组件,用于展示页码、请求数据等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-pagination) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-popup/changelog.md b/im-uniapp/uni_modules/uni-popup/changelog.md new file mode 100644 index 0000000..bc59f07 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/changelog.md @@ -0,0 +1,68 @@ +## 1.8.3(2023-04-17) +- 修复 uni-popup 重复打开时的 bug +## 1.8.2(2023-02-02) +- uni-popup-dialog 组件新增 inputType 属性 +## 1.8.1(2022-12-01) +- 修复 nvue 下 v-show 报错 +## 1.8.0(2022-11-29) +- 优化 主题样式 +## 1.7.9(2022-04-02) +- 修复 弹出层内部无法滚动的bug +## 1.7.8(2022-03-28) +- 修复 小程序中高度错误的bug +## 1.7.7(2022-03-17) +- 修复 快速调用open出现问题的Bug +## 1.7.6(2022-02-14) +- 修复 safeArea 属性不能设置为false的bug +## 1.7.5(2022-01-19) +- 修复 isMaskClick 失效的bug +## 1.7.4(2022-01-19) +- 新增 cancelText \ confirmText 属性 ,可自定义文本 +- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色 +- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题 +## 1.7.3(2022-01-13) +- 修复 设置 safeArea 属性不生效的bug +## 1.7.2(2021-11-26) +- 优化 组件示例 +## 1.7.1(2021-11-26) +- 修复 vuedoc 文字错误 +## 1.7.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup) +## 1.6.2(2021-08-24) +- 新增 支持国际化 +## 1.6.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.6.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.5.0(2021-06-23) +- 新增 mask-click 遮罩层点击事件 +## 1.4.5(2021-06-22) +- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.4(2021-06-18) +- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.3(2021-06-08) +- 修复 错误的 watch 字段 +- 修复 safeArea 属性不生效的问题 +- 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.4.2(2021-05-12) +- 新增 组件示例地址 +## 1.4.1(2021-04-29) +- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +## 1.4.0 (2021-04-29) +- 新增 type 属性的 left\right 值,支持左右弹出 +- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- 新增 safeArea 属性,是否适配底部安全区 +- 修复 App\h5\微信小程序底部安全区占位不对的Bug +- 修复 App 端弹出等待的Bug +- 优化 提升低配设备性能,优化动画卡顿问题 +- 优化 更简单的组件自定义方式 +## 1.2.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.2.8(2021-02-05) +- 调整为uni_modules目录规范 +## 1.2.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 +- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端 diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js b/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js new file mode 100644 index 0000000..6ef26a2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue b/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000..b5eee79 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue b/im-uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000..91370a8 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,143 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue b/im-uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000..5be7624 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,187 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json new file mode 100644 index 0000000..7f1bd06 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "cancel", + "uni-popup.ok": "ok", + "uni-popup.placeholder": "pleace enter", + "uni-popup.title": "Hint", + "uni-popup.shareTitle": "Share to" +} diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json new file mode 100644 index 0000000..5e3003c --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "确定", + "uni-popup.placeholder": "请输入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json new file mode 100644 index 0000000..13e39eb --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "確定", + "uni-popup.placeholder": "請輸入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js b/im-uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js new file mode 100644 index 0000000..62dda46 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/popup.js b/im-uniapp/uni_modules/uni-popup/components/uni-popup/popup.js new file mode 100644 index 0000000..c4e5781 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/popup.js @@ -0,0 +1,26 @@ + +export default { + data() { + return { + + } + }, + created(){ + this.popup = this.getParent() + }, + methods:{ + /** + * 获取父元素实例 + */ + getParent(name = 'uniPopup') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + } +} diff --git a/im-uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue b/im-uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000..54afee2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue @@ -0,0 +1,473 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-popup/package.json b/im-uniapp/uni_modules/uni-popup/package.json new file mode 100644 index 0000000..f40556b --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-popup", + "displayName": "uni-popup 弹出层", + "version": "1.8.3", + "description": " Popup 组件,提供常用的弹层", + "keywords": [ + "uni-ui", + "弹出层", + "弹窗", + "popup", + "弹框" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-popup/readme.md b/im-uniapp/uni_modules/uni-popup/readme.md new file mode 100644 index 0000000..fdad4b3 --- /dev/null +++ b/im-uniapp/uni_modules/uni-popup/readme.md @@ -0,0 +1,17 @@ + + +## Popup 弹出层 +> **组件名:uni-popup** +> 代码块: `uPopup` +> 关联组件:`uni-transition` + + +弹出层组件,在应用中弹出一个消息提示窗口、提示框等 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/im-uniapp/uni_modules/uni-rate/changelog.md b/im-uniapp/uni_modules/uni-rate/changelog.md new file mode 100644 index 0000000..8a98a61 --- /dev/null +++ b/im-uniapp/uni_modules/uni-rate/changelog.md @@ -0,0 +1,25 @@ +## 1.3.1(2022-02-25) +- 修复 条件判断 `NaN` 错误的 bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-rate](https://uniapp.dcloud.io/component/uniui/uni-rate) +## 1.2.2(2021-09-10) +- 优化 默认值修改为 0 颗星 +## 1.2.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-05-12) +- 新增 组件示例地址 +## 1.1.1(2021-04-21) +- 修复 布局变化后 uni-rate 星星计算不准确的 bug +- 优化 添加依赖 uni-icons, 导入 uni-rate 自动下载依赖 +## 1.1.0(2021-04-16) +- 修复 uni-rate 属性 margin 值为 string 组件失效的 bug + +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 支持 pc 端 diff --git a/im-uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue b/im-uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue new file mode 100644 index 0000000..857f5f9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-rate/package.json b/im-uniapp/uni_modules/uni-rate/package.json new file mode 100644 index 0000000..64e8e33 --- /dev/null +++ b/im-uniapp/uni_modules/uni-rate/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-rate", + "displayName": "uni-rate 评分", + "version": "1.3.1", + "description": "Rate 评分组件,可自定义评分星星图标的大小、间隔、评分数。", + "keywords": [ + "uni-ui", + "uniui", + "评分" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-rate/readme.md b/im-uniapp/uni_modules/uni-rate/readme.md new file mode 100644 index 0000000..eae7b5c --- /dev/null +++ b/im-uniapp/uni_modules/uni-rate/readme.md @@ -0,0 +1,12 @@ + + +## Rate 评分 +> **组件名:uni-rate** +> 代码块: `uRate` +> 关联组件:`uni-icons` + + +评分组件,多用于购买商品后,对商品进行评价等场景 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-rate) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-row/changelog.md b/im-uniapp/uni_modules/uni-row/changelog.md new file mode 100644 index 0000000..5b465bc --- /dev/null +++ b/im-uniapp/uni_modules/uni-row/changelog.md @@ -0,0 +1,10 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-row](https://uniapp.dcloud.io/component/uniui/uni-row) +## 0.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 新增uni-row组件 diff --git a/im-uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue b/im-uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue new file mode 100644 index 0000000..84e2deb --- /dev/null +++ b/im-uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue b/im-uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue new file mode 100644 index 0000000..f8e8542 --- /dev/null +++ b/im-uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-row/package.json b/im-uniapp/uni_modules/uni-row/package.json new file mode 100644 index 0000000..3f52fa6 --- /dev/null +++ b/im-uniapp/uni_modules/uni-row/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-row", + "displayName": "uni-row 布局-行", + "version": "1.0.0", + "description": "流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。", + "keywords": [ + "uni-ui", + "uniui", + "栅格", + "布局", + "layout" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-row/readme.md b/im-uniapp/uni_modules/uni-row/readme.md new file mode 100644 index 0000000..3c9c8b9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-row/readme.md @@ -0,0 +1,10 @@ +## Layout 布局 + +> **组件名 uni-row、uni-col** +> 代码块: `uRow`、`uCol` + + +流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-row) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-scss/changelog.md b/im-uniapp/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000..b863bb0 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/im-uniapp/uni_modules/uni-scss/index.scss b/im-uniapp/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000..1744a5f --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/im-uniapp/uni_modules/uni-scss/package.json b/im-uniapp/uni_modules/uni-scss/package.json new file mode 100644 index 0000000..7cc0ccb --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-scss/readme.md b/im-uniapp/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000..b7d1c25 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-scss/styles/index.scss b/im-uniapp/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000..ffac4fe --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_border.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000..12a11c3 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_color.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000..1ededd9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000..9a0428b --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_space.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000..3c89528 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000..689afec --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_text.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000..a34d08f --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/im-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss b/im-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000..557d3d7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/im-uniapp/uni_modules/uni-scss/styles/tools/functions.scss b/im-uniapp/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000..ac6f63e --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/im-uniapp/uni_modules/uni-scss/theme.scss b/im-uniapp/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000..80ee62f --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/im-uniapp/uni_modules/uni-scss/variables.scss b/im-uniapp/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000..1c062d4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/im-uniapp/uni_modules/uni-search-bar/changelog.md b/im-uniapp/uni_modules/uni-search-bar/changelog.md new file mode 100644 index 0000000..a01fcf2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/changelog.md @@ -0,0 +1,35 @@ +## 1.2.4(2023-05-09) +- 修复 i18n 国际化不正确的 Bug +## 1.2.3(2022-05-24) +- 新增 readonly 属性,组件只读 +## 1.2.2(2022-05-06) +- 修复 vue3 input 事件不生效的bug +## 1.2.1(2022-05-06) +- 修复 多余代码导致的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-search-bar](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +## 1.1.2(2021-08-30) +- 修复 value 属性与 modelValue 属性不兼容的Bug +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 项目示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-04-15) +- uni-ui 新增 uni-search-bar 的 focus 事件 + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持双向绑定 +- 更改 input 事件的返回值,e={value:Number} --> e=value +- 新增 支持图标插槽 +- 新增 支持 clear、blur 事件 +- 新增 支持 focus 属性 +- 去掉组件背景色 diff --git a/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json new file mode 100644 index 0000000..dd083a5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "Search enter content" +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json new file mode 100644 index 0000000..d2a1ced --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "取消", + "uni-search-bar.placeholder": "请输入搜索内容" +} diff --git a/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json new file mode 100644 index 0000000..f1c96bc --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "取消", + "uni-search-bar.placeholder": "請輸入搜索內容" +} diff --git a/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue new file mode 100644 index 0000000..e0a7372 --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-search-bar/package.json b/im-uniapp/uni_modules/uni-search-bar/package.json new file mode 100644 index 0000000..ede02ef --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-search-bar", + "displayName": "uni-search-bar 搜索栏", + "version": "1.2.4", + "description": "搜索栏组件,通常用于搜索商品、文章等", + "keywords": [ + "uni-ui", + "uniui", + "搜索框", + "搜索栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-search-bar/readme.md b/im-uniapp/uni_modules/uni-search-bar/readme.md new file mode 100644 index 0000000..253092f --- /dev/null +++ b/im-uniapp/uni_modules/uni-search-bar/readme.md @@ -0,0 +1,14 @@ + + +## SearchBar 搜索栏 + +> **组件名:uni-search-bar** +> 代码块: `uSearchBar` + + +搜索栏组件 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-section/changelog.md b/im-uniapp/uni_modules/uni-section/changelog.md new file mode 100644 index 0000000..738f2b3 --- /dev/null +++ b/im-uniapp/uni_modules/uni-section/changelog.md @@ -0,0 +1,2 @@ +## 0.0.1(2022-07-22) +- 初始化 diff --git a/im-uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue b/im-uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue new file mode 100644 index 0000000..9a52e0b --- /dev/null +++ b/im-uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue @@ -0,0 +1,167 @@ + + + + diff --git a/im-uniapp/uni_modules/uni-section/package.json b/im-uniapp/uni_modules/uni-section/package.json new file mode 100644 index 0000000..0a31fb5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-section/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-section", + "displayName": "uni-section 标题栏", + "version": "0.0.1", + "description": "标题栏组件", + "keywords": [ + "uni-ui", + "uniui", + "标题栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-section/readme.md b/im-uniapp/uni_modules/uni-section/readme.md new file mode 100644 index 0000000..d47faab --- /dev/null +++ b/im-uniapp/uni_modules/uni-section/readme.md @@ -0,0 +1,8 @@ +## Section 标题栏 +> **组件名:uni-section** +> 代码块: `uSection` + +uni-section 组件主要用于文章、列表详情等标题展示 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-section) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-segmented-control/changelog.md b/im-uniapp/uni_modules/uni-segmented-control/changelog.md new file mode 100644 index 0000000..a44385d --- /dev/null +++ b/im-uniapp/uni_modules/uni-segmented-control/changelog.md @@ -0,0 +1,9 @@ +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-segmented-control](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.5(2021-05-12) +- 新增 项目示例地址 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue b/im-uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue new file mode 100644 index 0000000..044a495 --- /dev/null +++ b/im-uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-segmented-control/package.json b/im-uniapp/uni_modules/uni-segmented-control/package.json new file mode 100644 index 0000000..6cae41d --- /dev/null +++ b/im-uniapp/uni_modules/uni-segmented-control/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-segmented-control", + "displayName": "uni-segmented-control 分段器", + "version": "1.2.0", + "description": "分段器由至少 2 个分段控件组成,用作不同视图的显示", + "keywords": [ + "uni-ui", + "uniui", + "分段器", + "segement", + "顶部选择" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-segmented-control/readme.md b/im-uniapp/uni_modules/uni-segmented-control/readme.md new file mode 100644 index 0000000..3527b03 --- /dev/null +++ b/im-uniapp/uni_modules/uni-segmented-control/readme.md @@ -0,0 +1,13 @@ + + +## SegmentedControl 分段器 +> **组件名:uni-segmented-control** +> 代码块: `uSegmentedControl` + + +用作不同视图的显示 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-steps/changelog.md b/im-uniapp/uni_modules/uni-steps/changelog.md new file mode 100644 index 0000000..cb9d367 --- /dev/null +++ b/im-uniapp/uni_modules/uni-steps/changelog.md @@ -0,0 +1,16 @@ +## 1.1.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-steps](https://uniapp.dcloud.io/component/uniui/uni-steps) +## 1.0.8(2021-05-12) +- 新增 项目示例地址 +## 1.0.7(2021-05-06) +- 修复 uni-steps 横向布局时,多行文字高度不合理的 bug +## 1.0.6(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue b/im-uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue new file mode 100644 index 0000000..a6c8f28 --- /dev/null +++ b/im-uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-steps/package.json b/im-uniapp/uni_modules/uni-steps/package.json new file mode 100644 index 0000000..c687b40 --- /dev/null +++ b/im-uniapp/uni_modules/uni-steps/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-steps", + "displayName": "uni-steps 步骤条", + "version": "1.1.1", + "description": "步骤条组件,提供横向和纵向两种布局格式。", + "keywords": [ + "uni-ui", + "uniui", + "步骤条", + "时间轴" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-steps/readme.md b/im-uniapp/uni_modules/uni-steps/readme.md new file mode 100644 index 0000000..da7a4bf --- /dev/null +++ b/im-uniapp/uni_modules/uni-steps/readme.md @@ -0,0 +1,13 @@ + + +## Steps 步骤条 +> **组件名:uni-steps** +> 代码块: `uSteps` + + +步骤条,常用于显示进度 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-steps) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-swipe-action/changelog.md b/im-uniapp/uni_modules/uni-swipe-action/changelog.md new file mode 100644 index 0000000..c916427 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/changelog.md @@ -0,0 +1,43 @@ +## 1.3.8(2023-04-13) +- 修复`uni-swipe-action`和`uni-swipe-action-item`不同时使用导致 closeOther 方法报错的 bug +## 1.3.7(2022-06-06) +- 修复 vue3 下使用组件不能正常运行的Bug +## 1.3.6(2022-05-31) +- 修复 h5端点击click触发两次的Bug +## 1.3.5(2022-05-23) +- 修复 isPC 找不到的Bug +## 1.3.4(2022-05-19) +- 修复 在 nvue 下 disabled 失效的bug +## 1.3.3(2022-03-31) +- 修复 按钮字体大小不能设置的bug +## 1.3.2(2022-03-16) +- 修复 h5和app端下报el错误的bug +## 1.3.1(2022-03-07) +- 修复 HBuilderX 1.4.X 版本中,h5和app端下报错的bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-swipe-action](https://uniapp.dcloud.io/component/uniui/uni-swipe-action) +## 1.2.4(2021-08-20) +- 优化 close-all 方法 +## 1.2.3(2021-08-20) +- 新增 close-all 方法,关闭所有已打开的组件 +## 1.2.2(2021-08-17) +- 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +## 1.2.1(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- 修复 跨页面修改组件数据 ,导致不能滑动的问题 +## 1.1.10(2021-06-17) +- 修复 按钮点击执行两次的bug +## 1.1.9(2021-05-12) +- 新增 项目示例地址 +## 1.1.8(2021-03-26) +- 修复 微信小程序 nv_navigator is not defined 报错的bug +## 1.1.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 左侧滑动 +- 新增 插槽使用方式 +- 新增 threshold 属性,可以控制滑动缺省值 +- 优化 长列表滚动性能 +- 修复 滚动页面时触发组件滑动的Bug diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js new file mode 100644 index 0000000..707e432 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js @@ -0,0 +1,302 @@ +let bindIngXMixins = {} + +// #ifdef APP-NVUE +const BindingX = uni.requireNativePlugin('bindingx'); +const dom = uni.requireNativePlugin('dom'); +const animation = uni.requireNativePlugin('animation'); + +bindIngXMixins = { + data() { + return {} + }, + + watch: { + show(newVal) { + if (this.autoClose) return + if (this.stop) return + this.stop = true + if (newVal) { + this.open(newVal) + } else { + this.close() + } + }, + leftOptions() { + this.getSelectorQuery() + this.init() + }, + rightOptions(newVal) { + this.init() + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.box = this.getEl(this.$refs['selector-box--hock']) + this.selector = this.getEl(this.$refs['selector-content--hock']); + this.leftButton = this.getEl(this.$refs['selector-left-button--hock']); + this.rightButton = this.getEl(this.$refs['selector-right-button--hock']); + this.init() + }, + // beforeDestroy() { + // this.swipeaction.children.forEach((item, index) => { + // if (item === this) { + // this.swipeaction.children.splice(index, 1) + // } + // }) + // }, + methods: { + init() { + this.$nextTick(() => { + this.x = 0 + this.button = { + show: false + } + setTimeout(() => { + this.getSelectorQuery() + }, 200) + }) + }, + onClick(index, item, position) { + this.$emit('click', { + content: item, + index, + position + }) + }, + touchstart(e) { + // fix by mehaotian 禁止滑动 + if (this.disabled) return + // 每次只触发一次,避免多次监听造成闪烁 + if (this.stop) return + this.stop = true + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + let expression = this.range(this.x, -rightWidth, leftWidth) + let leftExpression = this.range(this.x - leftWidth, -leftWidth, 0) + let rightExpression = this.range(this.x + rightWidth, 0, rightWidth) + + this.eventpan = BindingX.bind({ + anchor: this.box, + eventType: 'pan', + props: [{ + element: this.selector, + property: 'transform.translateX', + expression + }, { + element: this.leftButton, + property: 'transform.translateX', + expression: leftExpression + }, { + element: this.rightButton, + property: 'transform.translateX', + expression: rightExpression + }, ] + }, (e) => { + // nope + if (e.state === 'end') { + this.x = e.deltaX + this.x; + this.isclick = true + this.bindTiming(e.deltaX) + } + }); + }, + touchend(e) { + if (this.isopen !== 'none' && !this.isclick) { + this.open('none') + } + }, + bindTiming(x) { + const left = this.x + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + const threshold = this.threshold + if (!this.isopen || this.isopen === 'none') { + if (left > threshold) { + this.open('left') + } else if (left < -threshold) { + this.open('right') + } else { + this.open('none') + } + } else { + if ((x > -leftWidth && x < 0) || x > rightWidth) { + if ((x > -threshold && x < 0) || (x - rightWidth > threshold)) { + this.open('left') + } else { + this.open('none') + } + } else { + if ((x < threshold && x > 0) || (x + leftWidth < -threshold)) { + this.open('right') + } else { + this.open('none') + } + } + } + }, + + /** + * 移动范围 + * @param {Object} num + * @param {Object} mix + * @param {Object} max + */ + range(num, mix, max) { + return `min(max(x+${num}, ${mix}), ${max})` + }, + + /** + * 开启swipe + */ + open(type) { + this.animation(type) + }, + + /** + * 关闭swipe + */ + close() { + this.animation('none') + }, + + /** + * 开启关闭动画 + * @param {Object} type + */ + animation(type) { + const time = 300 + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + if (this.eventpan && this.eventpan.token) { + BindingX.unbind({ + token: this.eventpan.token, + eventType: 'pan' + }) + } + + switch (type) { + case 'left': + Promise.all([ + this.move(this.selector, leftWidth), + this.move(this.leftButton, 0), + this.move(this.rightButton, rightWidth * 2) + ]).then(() => { + this.setEmit(leftWidth, type) + }) + break + case 'right': + Promise.all([ + this.move(this.selector, -rightWidth), + this.move(this.leftButton, -leftWidth * 2), + this.move(this.rightButton, 0) + ]).then(() => { + this.setEmit(-rightWidth, type) + }) + break + default: + Promise.all([ + this.move(this.selector, 0), + this.move(this.leftButton, -leftWidth), + this.move(this.rightButton, rightWidth) + ]).then(() => { + this.setEmit(0, type) + }) + + } + }, + setEmit(x, type) { + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + this.isopen = this.isopen || 'none' + this.stop = false + this.isclick = false + // 只有状态不一致才会返回结果 + if (this.isopen !== type && this.x !== x) { + if (type === 'left' && leftWidth > 0) { + this.$emit('change', 'left') + } + if (type === 'right' && rightWidth > 0) { + this.$emit('change', 'right') + } + if (type === 'none') { + this.$emit('change', 'none') + } + } + this.x = x + this.isopen = type + }, + move(ref, value) { + return new Promise((resolve, reject) => { + animation.transition(ref, { + styles: { + transform: `translateX(${value})`, + }, + duration: 150, //ms + timingFunction: 'linear', + needLayout: false, + delay: 0 //ms + }, function(res) { + resolve(res) + }) + }) + + }, + + /** + * 获取ref + * @param {Object} el + */ + getEl(el) { + return el.ref + }, + /** + * 获取节点信息 + */ + getSelectorQuery() { + Promise.all([ + this.getDom('left'), + this.getDom('right'), + ]).then((data) => { + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + + if (show === 'none') { + // this.close() + } else { + this.open(show) + } + + }) + + }, + getDom(str) { + return new Promise((resolve, reject) => { + dom.getComponentRect(this.$refs[`selector-${str}-button--hock`], (data) => { + if (data) { + this.button[str] = data.size + resolve(data) + } else { + reject() + } + }) + }) + } + } +} + +// #endif + +export default bindIngXMixins diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js new file mode 100644 index 0000000..917cb48 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js @@ -0,0 +1,12 @@ +export function isPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (let v = 0; v < Agents.length - 1; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + return flag; +} diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js new file mode 100644 index 0000000..35c796b --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js @@ -0,0 +1,195 @@ +export default { + data() { + return { + x: 0, + transition: false, + width: 0, + viewWidth: 0, + swipeShow: 0 + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + if (newVal && newVal !== 'none') { + this.transition = true + this.open(newVal) + } else { + this.close() + } + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.isopen = false + setTimeout(() => { + this.getQuerySelect() + }, 50) + }, + methods: { + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + /** + * 移动触发 + * @param {Object} e + */ + onChange(e) { + this.moveX = e.detail.x + this.isclose = false + }, + touchstart(e) { + this.transition = false + this.isclose = true + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + touchmove(e) {}, + touchend(e) { + // 0的位置什么都不执行 + if (this.isclose && this.isopen === 'none') return + if (this.isclose && this.isopen !== 'none') { + this.transition = true + this.close() + } else { + this.move(this.moveX + this.leftWidth) + } + }, + + /** + * 移动 + * @param {Object} moveX + */ + move(moveX) { + // 打开关闭的处理逻辑不太一样 + this.transition = true + // 未打开状态 + if (!this.isopen || this.isopen === 'none') { + if (moveX > this.threshold) { + this.open('left') + } else if (moveX < -this.threshold) { + this.open('right') + } else { + this.close() + } + } else { + if (moveX < 0 && moveX < this.rightWidth) { + const rightX = this.rightWidth + moveX + if (rightX < this.threshold) { + this.open('right') + } else { + this.close() + } + } else if (moveX > 0 && moveX < this.leftWidth) { + const leftX = this.leftWidth - moveX + if (leftX < this.threshold) { + this.open('left') + } else { + this.close() + } + } + + } + + }, + + /** + * 打开 + */ + open(type) { + this.x = this.moveX + this.animation(type) + }, + + /** + * 关闭 + */ + close() { + this.x = this.moveX + // TODO 解决 x 值不更新的问题,所以会多触发一次 nextTick ,待优化 + this.$nextTick(() => { + this.x = -this.leftWidth + if (this.isopen !== 'none') { + this.$emit('change', 'none') + } + this.isopen = 'none' + }) + }, + + /** + * 执行结束动画 + * @param {Object} type + */ + animation(type) { + this.$nextTick(() => { + if (type === 'left') { + this.x = 0 + } else { + this.x = -this.rightWidth - this.leftWidth + } + + if (this.isopen !== type) { + this.$emit('change', type) + } + this.isopen = type + }) + + }, + getSlide(x) {}, + getQuerySelect() { + const query = uni.createSelectorQuery().in(this); + query.selectAll('.movable-view--hock').boundingClientRect(data => { + this.leftWidth = data[1].width + this.rightWidth = data[2].width + this.width = data[0].width + this.viewWidth = this.width + this.rightWidth + this.leftWidth + if (this.leftWidth === 0) { + // TODO 疑似bug ,初始化的时候如果x 是0,会导致移动位置错误,所以让元素超出一点 + this.x = -0.1 + } else { + this.x = -this.leftWidth + } + this.moveX = this.x + this.$nextTick(() => { + this.swipeShow = 1 + }) + + if (!this.buttonWidth) { + this.disabledView = true + } + + if (this.autoClose) return + if (this.show !== 'none') { + this.transition = true + this.open(this.shows) + } + }).exec(); + + } + } +} diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js new file mode 100644 index 0000000..d389bce --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js @@ -0,0 +1,260 @@ +let otherMixins = {} + +// #ifndef APP-PLUS|| MP-WEIXIN || H5 +const MIN_DISTANCE = 10; +otherMixins = { + data() { + // TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug + const elClass = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` + return { + uniShow: false, + left: 0, + buttonShow: 'none', + ani: false, + moveLeft: '', + elClass + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + this.openState(newVal) + }, + left() { + this.moveLeft = `translateX(${this.left}px)` + }, + buttonShow(newVal) { + if (this.autoClose) return + this.openState(newVal) + }, + leftOptions() { + this.init() + }, + rightOptions() { + this.init() + } + }, + mounted() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + this.init() + }, + methods: { + init() { + clearTimeout(this.timer) + this.timer = setTimeout(() => { + this.getSelectorQuery() + }, 100) + // 移动距离 + this.left = 0 + this.x = 0 + }, + + closeSwipe(e) { + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + touchstart(e) { + if (this.disabled) return + this.ani = false + this.x = this.left || 0 + this.stopTouchStart(e) + this.autoClose && this.closeSwipe() + }, + touchmove(e) { + if (this.disabled) return + // 是否可以滑动页面 + this.stopTouchMove(e); + if (this.direction !== 'horizontal') { + return; + } + this.move(this.x + this.deltaX) + return false + }, + touchend() { + if (this.disabled) return + this.moveDirection(this.left) + }, + /** + * 设置移动距离 + * @param {Object} value + */ + move(value) { + value = value || 0 + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + // 获取可滑动范围 + this.left = this.range(value, -rightWidth, leftWidth); + }, + + /** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ + range(num, min, max) { + return Math.min(Math.max(num, min), max); + }, + /** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + */ + moveDirection(left) { + const threshold = this.threshold + const isopen = this.isopen || 'none' + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + if (this.deltaX === 0) { + this.openState('none') + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > + 0 && rightWidth + + left < threshold)) { + // right + this.openState('right') + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > + 0 && + leftWidth - left < threshold)) { + // left + this.openState('left') + } else { + // default + this.openState('none') + } + }, + + /** + * 开启状态 + * @param {Boolean} type + */ + openState(type) { + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + let left = '' + this.isopen = this.isopen ? this.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + + if (this.isopen !== type) { + this.throttle = true + this.$emit('change', type) + } + + this.isopen = type + // 添加动画类 + this.ani = true + this.$nextTick(() => { + this.move(left) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 + }, + close() { + this.openState('none') + }, + getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; + }, + + /** + * 重置滑动状态 + * @param {Object} event + */ + resetTouchStatus() { + this.direction = ''; + this.deltaX = 0; + this.deltaY = 0; + this.offsetX = 0; + this.offsetY = 0; + }, + + /** + * 设置滑动开始位置 + * @param {Object} event + */ + stopTouchStart(event) { + this.resetTouchStatus(); + const touch = event.touches[0]; + this.startX = touch.clientX; + this.startY = touch.clientY; + }, + + /** + * 滑动中,是否禁止打开 + * @param {Object} event + */ + stopTouchMove(event) { + const touch = event.touches[0]; + this.deltaX = touch.clientX - this.startX; + this.deltaY = touch.clientY - this.startY; + this.offsetX = Math.abs(this.deltaX); + this.offsetY = Math.abs(this.deltaY); + this.direction = this.direction || this.getDirection(this.offsetX, this.offsetY); + }, + + getSelectorQuery() { + const views = uni.createSelectorQuery().in(this) + views + .selectAll('.' + this.elClass) + .boundingClientRect(data => { + if (data.length === 0) return + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + this.leftWidth = data[0].width || 0 + this.rightWidth = data[1].width || 0 + this.buttonShow = show + }) + .exec() + } + } +} + +// #endif + +export default otherMixins diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js new file mode 100644 index 0000000..08de1c9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js @@ -0,0 +1,84 @@ +let mpMixins = {} +let is_pc = null +// #ifdef H5 +import { + isPC +} from "./isPC" +is_pc = isPC() +// #endif +// #ifdef APP-VUE|| MP-WEIXIN || H5 + +mpMixins = { + data() { + return { + is_show: 'none' + } + }, + watch: { + show(newVal) { + this.is_show = this.show + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.is_show = this.show + }, + methods: { + // wxs 中调用 + closeSwipe(e) { + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + + change(e) { + this.$emit('change', e.open) + if (this.is_show !== e.open) { + this.is_show = e.open + } + }, + + appTouchStart(e) { + if (is_pc) return + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + if (is_pc) return + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + onClickForPC(index, item, position) { + if (!is_pc) return + // #ifdef H5 + this.$emit('click', { + content: item, + index, + position + }) + // #endif + } + } +} + +// #endif +export default mpMixins diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js new file mode 100644 index 0000000..78f0ec6 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js @@ -0,0 +1,270 @@ +const MIN_DISTANCE = 10; +export default { + showWatch(newVal, oldVal, ownerInstance, instance, self) { + var state = self.state + var $el = ownerInstance.$el || ownerInstance.$vm && ownerInstance.$vm.$el + if (!$el) return + this.getDom(instance, ownerInstance, self) + if (newVal && newVal !== 'none') { + this.openState(newVal, instance, ownerInstance, self) + return + } + + if (state.left) { + this.openState('none', instance, ownerInstance, self) + } + this.resetTouchStatus(instance, self) + }, + + /** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ + touchstart(e, ownerInstance, self) { + let instance = e.instance; + let disabled = instance.getDataset().disabled + let state = self.state; + this.getDom(instance, ownerInstance, self) + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function() { + instance.removeClass('ani'); + ownerInstance.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + this.stopTouchStart(e, ownerInstance, self) + }, + + /** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ + touchmove(e, ownerInstance, self) { + let instance = e.instance; + // 删除之后已经那不到实例了 + if (!instance) return; + let disabled = instance.getDataset().disabled + let state = self.state + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + if (disabled) return + // 是否可以滑动页面 + this.stopTouchMove(e, self); + if (state.direction !== 'horizontal') { + return; + } + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + let x = state.x + state.deltaX + this.move(x, instance, ownerInstance, self) + }, + + /** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ + touchend(e, ownerInstance, self) { + let instance = e.instance; + let disabled = instance.getDataset().disabled + let state = self.state + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + this.moveDirection(state.left, instance, ownerInstance, self) + + }, + + /** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ + move(value, instance, ownerInstance, self) { + value = value || 0 + let state = self.state + let leftWidth = state.leftWidth + let rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = this.range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function() { + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + + }, + + /** + * 获取元素信息 + * @param {Object} instance + * @param {Object} ownerInstance + */ + getDom(instance, ownerInstance, self) { + var state = self.state + var $el = ownerInstance.$el || ownerInstance.$vm && ownerInstance.$vm.$el + var leftDom = $el.querySelector('.button-group--left') + var rightDom = $el.querySelector('.button-group--right') + + state.leftWidth = leftDom.offsetWidth || 0 + state.rightWidth = rightDom.offsetWidth || 0 + state.threshold = instance.getDataset().threshold + }, + + getDisabledType(value) { + return (typeof(value) === 'string' ? JSON.parse(value) : value) || false; + }, + + /** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ + range(num, min, max) { + return Math.min(Math.max(num, min), max); + }, + + + /** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ + moveDirection(left, ins, ownerInstance, self) { + var state = self.state + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + this.openState('none', ins, ownerInstance, self) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && + rightWidth + + left < threshold)) { + // right + this.openState('right', ins, ownerInstance, self) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + this.openState('left', ins, ownerInstance, self) + } else { + // default + this.openState('none', ins, ownerInstance, self) + } + }, + + + /** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ + openState(type, ins, ownerInstance, self) { + let state = self.state + let leftWidth = state.leftWidth + let rightWidth = state.rightWidth + let left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(() => { + ins.addClass('ani'); + this.move(left, ins, ownerInstance, self) + }) + }, + + + getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; + }, + + /** + * 重置滑动状态 + * @param {Object} event + */ + resetTouchStatus(instance, self) { + let state = self.state; + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; + }, + + /** + * 设置滑动开始位置 + * @param {Object} event + */ + stopTouchStart(event, ownerInstance, self) { + let instance = event.instance; + let state = self.state + this.resetTouchStatus(instance, self); + var touch = event.touches[0]; + state.startX = touch.clientX; + state.startY = touch.clientY; + }, + + /** + * 滑动中,是否禁止打开 + * @param {Object} event + */ + stopTouchMove(event, self) { + let instance = event.instance; + let state = self.state; + let touch = event.touches[0]; + + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetY = Math.abs(state.deltaY); + state.offsetX = Math.abs(state.deltaX); + state.direction = state.direction || this.getDirection(state.offsetX, state.offsetY); + } +} diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue new file mode 100644 index 0000000..d79c297 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue @@ -0,0 +1,347 @@ + + + + + + diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs new file mode 100644 index 0000000..b394244 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs @@ -0,0 +1,341 @@ +var MIN_DISTANCE = 10; + +/** + * 判断当前是否为H5、app-vue + */ +var IS_HTML5 = false +if (typeof window === 'object') IS_HTML5 = true + +/** + * 监听页面内值的变化,主要用于动态开关swipe-action + * @param {Object} newValue + * @param {Object} oldValue + * @param {Object} ownerInstance + * @param {Object} instance + */ +function showWatch(newVal, oldVal, ownerInstance, instance) { + var state = instance.getState() + getDom(instance, ownerInstance) + if (newVal && newVal !== 'none') { + openState(newVal, instance, ownerInstance) + return + } + + if (state.left) { + openState('none', instance, ownerInstance) + } + resetTouchStatus(instance) +} + +/** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ +function touchstart(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState(); + getDom(instance, ownerInstance) + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function() { + instance.removeClass('ani'); + ownerInstance.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + stopTouchStart(e, ownerInstance) +} + +/** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchmove(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 是否可以滑动页面 + stopTouchMove(e); + if (state.direction !== 'horizontal') { + return; + } + + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + + move(state.x + state.deltaX, instance, ownerInstance) +} + +/** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchend(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + moveDirection(state.left, instance, ownerInstance) + +} + +/** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ +function move(value, instance, ownerInstance) { + value = value || 0 + var state = instance.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function() { + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + +} + +/** + * 获取元素信息 + * @param {Object} instance + * @param {Object} ownerInstance + */ +function getDom(instance, ownerInstance) { + var state = instance.getState() + var leftDom = ownerInstance.selectComponent('.button-group--left') + var rightDom = ownerInstance.selectComponent('.button-group--right') + var leftStyles = { + width: 0 + } + var rightStyles = { + width: 0 + } + leftStyles = leftDom.getBoundingClientRect() + rightStyles = rightDom.getBoundingClientRect() + + state.leftWidth = leftStyles.width || 0 + state.rightWidth = rightStyles.width || 0 + state.threshold = instance.getDataset().threshold +} + +/** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ +function range(num, min, max) { + return Math.min(Math.max(num, min), max); +} + + +/** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ +function moveDirection(left, ins, ownerInstance) { + var state = ins.getState() + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + openState('none', ins, ownerInstance) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && + rightWidth + + left < threshold)) { + // right + openState('right', ins, ownerInstance) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + openState('left', ins, ownerInstance) + } else { + // default + openState('none', ins, ownerInstance) + } +} + + +/** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ +function openState(type, ins, ownerInstance) { + var state = ins.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + var left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(function() { + ins.addClass('ani'); + move(left, ins, ownerInstance) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 +} + + +function getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; +} + +/** + * 重置滑动状态 + * @param {Object} event + */ +function resetTouchStatus(instance) { + var state = instance.getState(); + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; +} + +/** + * 设置滑动开始位置 + * @param {Object} event + */ +function stopTouchStart(event) { + var instance = event.instance; + var state = instance.getState(); + resetTouchStatus(instance); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.startX = touch.clientX; + state.startY = touch.clientY; +} + +/** + * 滑动中,是否禁止打开 + * @param {Object} event + */ +function stopTouchMove(event) { + var instance = event.instance; + var state = instance.getState(); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetY = Math.abs(state.deltaY); + state.offsetX = Math.abs(state.deltaX); + state.direction = state.direction || getDirection(state.offsetX, state.offsetY); +} + +function isPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (var v = 0; v < Agents.length - 1; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + return flag; +} + +var movable = false + +function mousedown(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchstart(e, ins) + movable = true +} + +function mousemove(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + if (!movable) return + touchmove(e, ins) +} + +function mouseup(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchend(e, ins) + movable = false +} + +function mouseleave(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + movable = false +} + +module.exports = { + showWatch: showWatch, + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + mousedown: mousedown, + mousemove: mousemove, + mouseup: mouseup, + mouseleave: mouseleave +} diff --git a/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue new file mode 100644 index 0000000..4971782 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-swipe-action/package.json b/im-uniapp/uni_modules/uni-swipe-action/package.json new file mode 100644 index 0000000..69135de --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-swipe-action", + "displayName": "uni-swipe-action 滑动操作", + "version": "1.3.8", + "description": "SwipeAction 滑动操作操作组件", + "keywords": [ + "", + "uni-ui", + "uniui", + "滑动删除", + "侧滑删除" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/im-uniapp/uni_modules/uni-swipe-action/readme.md b/im-uniapp/uni_modules/uni-swipe-action/readme.md new file mode 100644 index 0000000..93a5cac --- /dev/null +++ b/im-uniapp/uni_modules/uni-swipe-action/readme.md @@ -0,0 +1,11 @@ + + +## SwipeAction 滑动操作 +> **组件名:uni-swipe-action** +> 代码块: `uSwipeAction`、`uSwipeActionItem` + + +通过滑动触发选项的容器 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swipe-action) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-swiper-dot/changelog.md b/im-uniapp/uni_modules/uni-swiper-dot/changelog.md new file mode 100644 index 0000000..85cf54d --- /dev/null +++ b/im-uniapp/uni_modules/uni-swiper-dot/changelog.md @@ -0,0 +1,12 @@ +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-swiper-dot](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.6(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 clickItem 事件,支持指示点控制轮播 +- 新增 支持 pc 可用 diff --git a/im-uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue b/im-uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue new file mode 100644 index 0000000..e66b6c7 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-swiper-dot/package.json b/im-uniapp/uni_modules/uni-swiper-dot/package.json new file mode 100644 index 0000000..f2dd8d2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swiper-dot/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-swiper-dot", + "displayName": "uni-swiper-dot 轮播图指示点", + "version": "1.2.0", + "description": "自定义轮播图指示点组件", + "keywords": [ + "uni-ui", + "uniui", + "轮播图指示点", + "dot", + "swiper" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-swiper-dot/readme.md b/im-uniapp/uni_modules/uni-swiper-dot/readme.md new file mode 100644 index 0000000..7d397e2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-swiper-dot/readme.md @@ -0,0 +1,11 @@ + + +## SwiperDot 轮播图指示点 +> **组件名:uni-swiper-dot** +> 代码块: `uSwiperDot` + + +自定义轮播图指示点 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-table/changelog.md b/im-uniapp/uni_modules/uni-table/changelog.md new file mode 100644 index 0000000..9f87c67 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/changelog.md @@ -0,0 +1,27 @@ +## 1.2.3(2023-03-28) +- 修复 在vue3模式下可能会出现错误的问题 +## 1.2.2(2022-11-29) +- 优化 主题样式 +## 1.2.1(2022-06-06) +- 修复 微信小程序存在无使用组件的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-table](https://uniapp.dcloud.io/component/uniui/uni-table) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-07-08) +- 新增 uni-th 支持 date 日期筛选范围 +## 1.0.6(2021-07-05) +- 新增 uni-th 支持 range 筛选范围 +## 1.0.5(2021-06-28) +- 新增 uni-th 筛选功能 +## 1.0.4(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.3(2021-04-16) +- 新增 sortable 属性,是否开启单列排序 +- 优化 表格多选逻辑 +## 1.0.2(2021-03-22) +- uni-tr 添加 disabled 属性,用于 type=selection 时,设置某行是否可由全选按钮控制 +## 1.0.1(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue b/im-uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue new file mode 100644 index 0000000..21d9527 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue @@ -0,0 +1,455 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue b/im-uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue new file mode 100644 index 0000000..fbe1bdc --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue b/im-uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue new file mode 100644 index 0000000..9ce93e9 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue b/im-uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue new file mode 100644 index 0000000..df22a71 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue @@ -0,0 +1,511 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue b/im-uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue new file mode 100644 index 0000000..14889dd --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue b/im-uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue new file mode 100644 index 0000000..0dd18cd --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue b/im-uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue new file mode 100644 index 0000000..1089187 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue b/im-uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue new file mode 100644 index 0000000..f9b9671 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-table/i18n/en.json b/im-uniapp/uni_modules/uni-table/i18n/en.json new file mode 100644 index 0000000..e32023c --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/en.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Reset", + "filter-dropdown.search": "Search", + "filter-dropdown.submit": "Submit", + "filter-dropdown.filter": "Filter", + "filter-dropdown.gt": "Greater or equal to", + "filter-dropdown.lt": "Less than or equal to", + "filter-dropdown.date": "Date" +} diff --git a/im-uniapp/uni_modules/uni-table/i18n/es.json b/im-uniapp/uni_modules/uni-table/i18n/es.json new file mode 100644 index 0000000..9afd04b --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/es.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Reiniciar", + "filter-dropdown.search": "Búsqueda", + "filter-dropdown.submit": "Entregar", + "filter-dropdown.filter": "Filtrar", + "filter-dropdown.gt": "Mayor o igual a", + "filter-dropdown.lt": "Menos que o igual a", + "filter-dropdown.date": "Fecha" +} diff --git a/im-uniapp/uni_modules/uni-table/i18n/fr.json b/im-uniapp/uni_modules/uni-table/i18n/fr.json new file mode 100644 index 0000000..b006237 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/fr.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Réinitialiser", + "filter-dropdown.search": "Chercher", + "filter-dropdown.submit": "Soumettre", + "filter-dropdown.filter": "Filtre", + "filter-dropdown.gt": "Supérieur ou égal à", + "filter-dropdown.lt": "Inférieur ou égal à", + "filter-dropdown.date": "Date" +} diff --git a/im-uniapp/uni_modules/uni-table/i18n/index.js b/im-uniapp/uni_modules/uni-table/i18n/index.js new file mode 100644 index 0000000..2469dd0 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/index.js @@ -0,0 +1,12 @@ +import en from './en.json' +import es from './es.json' +import fr from './fr.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + es, + fr, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/im-uniapp/uni_modules/uni-table/i18n/zh-Hans.json b/im-uniapp/uni_modules/uni-table/i18n/zh-Hans.json new file mode 100644 index 0000000..862af17 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "重置", + "filter-dropdown.search": "搜索", + "filter-dropdown.submit": "确定", + "filter-dropdown.filter": "筛选", + "filter-dropdown.gt": "大于等于", + "filter-dropdown.lt": "小于等于", + "filter-dropdown.date": "日期范围" +} diff --git a/im-uniapp/uni_modules/uni-table/i18n/zh-Hant.json b/im-uniapp/uni_modules/uni-table/i18n/zh-Hant.json new file mode 100644 index 0000000..64f8061 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/i18n/zh-Hant.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "重置", + "filter-dropdown.search": "搜索", + "filter-dropdown.submit": "確定", + "filter-dropdown.filter": "篩選", + "filter-dropdown.gt": "大於等於", + "filter-dropdown.lt": "小於等於", + "filter-dropdown.date": "日期範圍" +} diff --git a/im-uniapp/uni_modules/uni-table/package.json b/im-uniapp/uni_modules/uni-table/package.json new file mode 100644 index 0000000..7c2f91c --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-table", + "displayName": "uni-table 表格", + "version": "1.2.3", + "description": "表格组件,多用于展示多条结构类似的数据,如", + "keywords": [ + "uni-ui", + "uniui", + "table", + "表格" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-datetime-picker"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "n", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-table/readme.md b/im-uniapp/uni_modules/uni-table/readme.md new file mode 100644 index 0000000..bb08c79 --- /dev/null +++ b/im-uniapp/uni_modules/uni-table/readme.md @@ -0,0 +1,13 @@ + + +## Table 表单 +> 组件名:``uni-table``,代码块: `uTable`。 + +用于展示多条结构类似的数据 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-table) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + diff --git a/im-uniapp/uni_modules/uni-tag/changelog.md b/im-uniapp/uni_modules/uni-tag/changelog.md new file mode 100644 index 0000000..c0c5839 --- /dev/null +++ b/im-uniapp/uni_modules/uni-tag/changelog.md @@ -0,0 +1,21 @@ +## 2.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-tag](https://uniapp.dcloud.io/component/uniui/uni-tag) +## 2.0.0(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +- 移除 插槽 +- 移除 type 属性的 royal 选项 +## 1.1.1(2021-08-11) +- type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-06-18) +- 修复 uni-tag 在字节跳动小程序上 css 类名编译错误的 bug +## 1.0.6(2021-06-04) +- 修复 未定义 sass 变量 "$uni-color-royal" 的bug +## 1.0.5(2021-05-10) +- 修复 royal 类型无效的bug +- 修复 uni-tag 宽度不自适应的bug +- 新增 uni-tag 支持属性 custom-style 自定义样式 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue b/im-uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue new file mode 100644 index 0000000..418c955 --- /dev/null +++ b/im-uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-tag/package.json b/im-uniapp/uni_modules/uni-tag/package.json new file mode 100644 index 0000000..1878088 --- /dev/null +++ b/im-uniapp/uni_modules/uni-tag/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-tag", + "displayName": "uni-tag 标签", + "version": "2.1.0", + "description": "Tag 组件,用于展示1个或多个文字标签,可点击切换选中、不选中的状态。", + "keywords": [ + "uni-ui", + "uniui", + "", + "tag", + "标签" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-tag/readme.md b/im-uniapp/uni_modules/uni-tag/readme.md new file mode 100644 index 0000000..6e78ff5 --- /dev/null +++ b/im-uniapp/uni_modules/uni-tag/readme.md @@ -0,0 +1,13 @@ + + +## Tag 标签 +> **组件名:uni-tag** +> 代码块: `uTag` + + +用于展示1个或多个文字标签,可点击切换选中、不选中的状态 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tag) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/im-uniapp/uni_modules/uni-title/changelog.md b/im-uniapp/uni_modules/uni-title/changelog.md new file mode 100644 index 0000000..7626216 --- /dev/null +++ b/im-uniapp/uni_modules/uni-title/changelog.md @@ -0,0 +1,10 @@ +## 1.1.1(2022-05-19) +- 修改组件描述 +## 1.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-title](https://uniapp.dcloud.io/component/uniui/uni-title) +## 1.0.2(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.1(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/im-uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue b/im-uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue new file mode 100644 index 0000000..bf4f926 --- /dev/null +++ b/im-uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-title/package.json b/im-uniapp/uni_modules/uni-title/package.json new file mode 100644 index 0000000..2249f5a --- /dev/null +++ b/im-uniapp/uni_modules/uni-title/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-title", + "displayName": "uni-title 章节标题", + "version": "1.1.1", + "description": "章节标题,通常用于记录页面标题,使用当前组件,uni-app 如果开启统计,将会自动统计页面标题", + "keywords": [ + "uni-ui", + "uniui", + "标题", + "章节", + "章节标题", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-title/readme.md b/im-uniapp/uni_modules/uni-title/readme.md new file mode 100644 index 0000000..0e60b1b --- /dev/null +++ b/im-uniapp/uni_modules/uni-title/readme.md @@ -0,0 +1,14 @@ + + +## Title 标题 +> **组件名:uni-title** +> 代码块: `uTitle` + + +章节标题,通常用于记录页面标题,使用当前组件,uni-app 如果开启统计,将会自动统计页面标题 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-title) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + diff --git a/im-uniapp/uni_modules/uni-tooltip/changelog.md b/im-uniapp/uni_modules/uni-tooltip/changelog.md new file mode 100644 index 0000000..00f1572 --- /dev/null +++ b/im-uniapp/uni_modules/uni-tooltip/changelog.md @@ -0,0 +1,10 @@ +## 0.2.1(2022-05-09) +- 修复 content 为空时仍然弹出的bug +## 0.2.0(2022-05-07) +**注意:破坏性更新** +- 更新 text 属性变更为 content +- 更新 移除 width 属性 +## 0.1.1(2022-04-27) +- 修复 组件根 text 嵌套组件 warning +## 0.1.0(2022-04-21) +- 初始化 diff --git a/im-uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue b/im-uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue new file mode 100644 index 0000000..ffbb6fa --- /dev/null +++ b/im-uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue @@ -0,0 +1,68 @@ + + + + + + diff --git a/im-uniapp/uni_modules/uni-tooltip/package.json b/im-uniapp/uni_modules/uni-tooltip/package.json new file mode 100644 index 0000000..b626efb --- /dev/null +++ b/im-uniapp/uni_modules/uni-tooltip/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-tooltip", + "displayName": "uni-tooltip", + "version": "0.2.1", + "description": "Tooltip 提示文字", + "keywords": [ + "uni-tooltip", + "uni-ui", + "tooltip", + "tip", + "文字提示" +], + "repository": "", +"engines": { + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无 ", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-tooltip/readme.md b/im-uniapp/uni_modules/uni-tooltip/readme.md new file mode 100644 index 0000000..faafa2e --- /dev/null +++ b/im-uniapp/uni_modules/uni-tooltip/readme.md @@ -0,0 +1,8 @@ +## Badge 数字角标 +> **组件名:uni-tooltip** +> 代码块: `uTooltip` + +数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景, + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tooltip) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/im-uniapp/uni_modules/uni-transition/changelog.md b/im-uniapp/uni_modules/uni-transition/changelog.md new file mode 100644 index 0000000..70c1cd4 --- /dev/null +++ b/im-uniapp/uni_modules/uni-transition/changelog.md @@ -0,0 +1,22 @@ +## 1.3.2(2023-05-04) +- 修复 NVUE 平台报错的问题 +## 1.3.1(2021-11-23) +- 修复 init 方法初始化问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition) +## 1.2.1(2021-09-27) +- 修复 init 方法不生效的 Bug +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.1(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的 Bug +## 1.1.0(2021-04-22) +- 新增 通过方法自定义动画 +- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式 +- 优化 动画触发逻辑,使动画更流畅 +- 优化 支持单独的动画类型 +- 优化 文档示例 +## 1.0.2(2021-02-05) +- 调整为 uni_modules 目录规范 diff --git a/im-uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js b/im-uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js new file mode 100644 index 0000000..8f89b18 --- /dev/null +++ b/im-uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js @@ -0,0 +1,131 @@ +// const defaultOption = { +// duration: 300, +// timingFunction: 'linear', +// delay: 0, +// transformOrigin: '50% 50% 0' +// } +// #ifdef APP-NVUE +const nvueAnimation = uni.requireNativePlugin('animation') +// #endif +class MPAnimation { + constructor(options, _this) { + this.options = options + // 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误 + this.animation = uni.createAnimation({ + ...options + }) + this.currentStepAnimates = {} + this.next = 0 + this.$ = _this + + } + + _nvuePushAnimates(type, args) { + let aniObj = this.currentStepAnimates[this.next] + let styles = {} + if (!aniObj) { + styles = { + styles: {}, + config: {} + } + } else { + styles = aniObj + } + if (animateTypes1.includes(type)) { + if (!styles.styles.transform) { + styles.styles.transform = '' + } + let unit = '' + if(type === 'rotate'){ + unit = 'deg' + } + styles.styles.transform += `${type}(${args+unit}) ` + } else { + styles.styles[type] = `${args}` + } + this.currentStepAnimates[this.next] = styles + } + _animateRun(styles = {}, config = {}) { + let ref = this.$.$refs['ani'].ref + if (!ref) return + return new Promise((resolve, reject) => { + nvueAnimation.transition(ref, { + styles, + ...config + }, res => { + resolve() + }) + }) + } + + _nvueNextAnimate(animates, step = 0, fn) { + let obj = animates[step] + if (obj) { + let { + styles, + config + } = obj + this._animateRun(styles, config).then(() => { + step += 1 + this._nvueNextAnimate(animates, step, fn) + }) + } else { + this.currentStepAnimates = {} + typeof fn === 'function' && fn() + this.isEnd = true + } + } + + step(config = {}) { + // #ifndef APP-NVUE + this.animation.step(config) + // #endif + // #ifdef APP-NVUE + this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config) + this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin + this.next++ + // #endif + return this + } + + run(fn) { + // #ifndef APP-NVUE + this.$.animationData = this.animation.export() + this.$.timer = setTimeout(() => { + typeof fn === 'function' && fn() + }, this.$.durationTime) + // #endif + // #ifdef APP-NVUE + this.isEnd = false + let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref + if(!ref) return + this._nvueNextAnimate(this.currentStepAnimates, 0, fn) + this.next = 0 + // #endif + } +} + + +const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d', + 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY', + 'translateZ' +] +const animateTypes2 = ['opacity', 'backgroundColor'] +const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom'] +animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => { + MPAnimation.prototype[type] = function(...args) { + // #ifndef APP-NVUE + this.animation[type](...args) + // #endif + // #ifdef APP-NVUE + this._nvuePushAnimates(type, args) + // #endif + return this + } +}) + +export function createAnimation(option, _this) { + if(!_this) return + clearTimeout(_this.timer) + return new MPAnimation(option, _this) +} diff --git a/im-uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue b/im-uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue new file mode 100644 index 0000000..bfbba93 --- /dev/null +++ b/im-uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/im-uniapp/uni_modules/uni-transition/package.json b/im-uniapp/uni_modules/uni-transition/package.json new file mode 100644 index 0000000..ea995a2 --- /dev/null +++ b/im-uniapp/uni_modules/uni-transition/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-transition", + "displayName": "uni-transition 过渡动画", + "version": "1.3.2", + "description": "元素的简单过渡动画", + "keywords": [ + "uni-ui", + "uniui", + "动画", + "过渡", + "过渡动画" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-transition/readme.md b/im-uniapp/uni_modules/uni-transition/readme.md new file mode 100644 index 0000000..2f8a77e --- /dev/null +++ b/im-uniapp/uni_modules/uni-transition/readme.md @@ -0,0 +1,11 @@ + + +## Transition 过渡动画 +> **组件名:uni-transition** +> 代码块: `uTransition` + + +元素过渡动画 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-ui/changelog.md b/im-uniapp/uni_modules/uni-ui/changelog.md new file mode 100644 index 0000000..61b26d0 --- /dev/null +++ b/im-uniapp/uni_modules/uni-ui/changelog.md @@ -0,0 +1,541 @@ +## 1.4.27(2023-04-23) +- uni-calendar 修复 某些情况 monthSwitch 未触发的Bug +- uni-calendar 修复 某些情况切换月份错误的Bug +- uni-data-picker 修复 更改 modelValue 报错的 bug +- uni-data-picker 修复 v-for 未使用 key 值控制台 warning +- uni-data-picker 修复代码合并时引发 value 属性为空时不渲染数据的问题 +- uni-data-picker 修复 localdata 不支持动态更新的bug +- uni-data-select 修复 微信小程序点击时会改变背景颜色的 bug +- uni-data-select 修复 禁用时会显示清空按钮 +- uni-data-select 优化 查询条件短期内多次变更只查询最后一次变更后的结果 +- uni-data-select 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue +- uni-datetime-picker 修复 日历 picker 修改年月后,自动选中当月1日 [详情](https://ask.dcloud.net.cn/question/165937) +- uni-datetime-picker 修复 小程序端 低版本 ios NaN [详情](https://ask.dcloud.net.cn/question/162979) +- uni-datetime-picker 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362) +- uni-datetime-picker 优化 值为空依然选中当天问题 +- uni-datetime-picker 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间 +- uni-datetime-picker 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间 +- uni-datetime-picker 优化 字节小程序日期时间范围选择,底部日期换行问题 +- uni-datetime-picker 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686) +- uni-datetime-picker 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684) +- uni-datetime-picker 优化 PC端输入日期格式错误时返回当前日期时间 +- uni-datetime-picker 优化 PC端输入日期时间超出 start、end 限制的Bug +- uni-datetime-picker 优化 移动端日期时间范围用法时间展示不完整问题 +- uni-datetime-picker 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679) +- uni-datetime-picker 修复 vue3 time-picker 无法显示绑定时分秒的Bug +- uni-datetime-picker 修复 字节小程序报错的Bug +- uni-datetime-picker 修复 某些情况切换月份错误的Bug +- uni-easyinput 修复 vue3 下 keyboardheightchange 事件报错的bug +- uni-easyinput 优化 trim 属性默认值 +- uni-easyinput 新增 cursor-spacing 属性 +- uni-fab 新增 pattern.icon 属性,可自定义图标 +- uni-file-picker 修复 手动上传删除一个文件后不能再上传的bug +- uni-forms 修复 required 参数无法动态绑定 +- uni-list 优化 uni-list-chat 具名插槽`header` 非app端套一层元素,方便使用时通过外层元素定位实现样式修改 +- uni-list uni-list-chat 新增 支持具名插槽`header` +- uni-list 新增 列表图标新增 customPrefix 属性 ,用法 [详见](https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html#icons-props) +- uni-nav-bar 修复 自定义状态栏高度闪动BUG +- uni-nav-bar 修复 暗黑模式下边线颜色错误的bug +- uni-popup 修复 uni-popup 重复打开时的 bug +- uni-popup uni-popup-dialog 组件新增 inputType 属性 +- uni-swipe-action 修复`uni-swipe-action`和`uni-swipe-action-item`不同时使用导致 closeOther 方法报错的 bug +- uni-table 修复 在vue3模式下可能会出现错误的问题 +## 1.4.26(2023-01-31) +- uni-badge 修复 运行/打包 控制台警告问题 +- uni-calendar 修复 某些情况切换月份错误问题 +- uni-data-select 修复 不关联服务空间报错的问题 +- uni-data-select 新增 属性 `format` 可用于格式化显示选项内容 +- uni-datetime-picker 修复 某些情况切换月份错误问题 +- uni-easyinput 新增 keyboardheightchange 事件,可监听键盘高度变化 +- uni-list 修复 无反馈效果呈现的bug +## 1.4.25(2023-01-11) +- uni-file-picker 新增 sourceType 属性, 可以自定义图片和视频选择的来源 +## 1.4.24(2023-01-11) +- uni-data-select 修复 当where变化时,数据不会自动更新的问题 +- uni-datetime-picker 修复 多次加载组件造成内存占用的 bug +- uni-datetime-picker 修复 vue3 下 i18n 国际化初始值不正确的 bug +- uni-easyinput 修复 props 中背景颜色无默认值的bug +- uni-list 修复 uni-list-chat 在vue3下跳转报错的bug +- uni-list 修复 uni-list-chat avatar属性 值为本地路径时错误的问题 +- uni-list 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题 +- uni-list 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题 +- uni-list 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug +- uni-list 修复 uni-list-item 的 customStyle 属性 padding值在nvue(vue2)下无效的bug +- uni-list uni-list-chat 新增 avatar 支持 fileId +- uni-list uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html) +- uni-list uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +- uni-list uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor +- uni-popup 修复 nvue 下 v-show 报错 +## 1.4.23(2022-10-25) +- uni-datetime-picker 修复,支付宝小程序样式错乱,[详情](https://github.com/dcloudio/uni-app/issues/3861) + +- uni-nav-bar 修复 条件编译错误的bug +- uni-nav-bar 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug +## 1.4.22(2022-09-19) +- 优化 部分组件适配 uni-scss 主题色 +- uni-badge 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473) +- uni-calendar 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件 +- uni-data-select 修复 微信小程序下拉框出现后选择会点击到蒙板后面的输入框 +- uni-data-select 修复 点击的位置不准确 +- uni-data-select 新增 支持 disabled 属性 +- uni-datetime-picker 修复,反向选择日期范围,日期显示异常,[详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false) +- uni-datetime-picker 修复 close事件无效的 bug +- uni-datetime-picker 修复 移动端 maskClick 无效的 bug,详见:[https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false](https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false) +- uni-fab 修复 小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false) +- uni-fab 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310) +- uni-forms 优化 根据 rules 自动添加 required 的问题 +- uni-forms 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +- uni-nav-bar 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug +- uni-notice-bar 新增 属性 fontSize,可修改文字大小。 +- uni-pagination 修复,未对主题色设置默认色,导致未引入 uni-scss 变量文件报错。 +- uni-pagination 修复,未对移动端当前页文字做主题色适配。 +- uni-pagination 修复 es 语言 i18n 错误 +## 1.4.21(2022-09-19) +- 修复,安装时未导入 uni-data-select 和 uni-tooltip 的问题。 +## 1.4.20(2022-07-25) +- uni-section 新增组件 +- uni-forms 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug + +## 1.4.19(2022-07-07) +- uni-data-picker 优化 pc端图标位置不正确的问题 +- uni-data-select 修复 pc端宽度异常的bug +## 1.4.18(2022-07-06) +- uni-forms 【重要】组件逻辑重构,部分用法旧版本不兼容,请注意兼容问题 +- uni-forms 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- uni-forms 新增 更多表单示例 +- uni-forms 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- uni-forms 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- uni-forms 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- uni-forms 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- uni-forms 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- uni-forms 修复 由 1.4.0 引发的 label 插槽不生效的bug +- uni-forms 修复 子组件找不到 setValue 报错的bug +- uni-forms 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- uni-forms 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +- uni-forms 修复 表单校验顺序无序问题 +- uni-forms 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +- uni-forms 优化 动态表单校验方式,废弃拼接name的方式 +- uni-breadcrumb 修复 微信小程序 separator 不显示问题 +- uni-data-checkbox 优化 在 uni-forms 中的依赖注入方式 +- uni-data-picker 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +- uni-data-picker 优化 显示样式 +- uni-data-select 优化 显示样式 +- uni-datetime-picker 修复 日历顶部年月及底部确认未国际化 bug +- uni-datetime-picker 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致 +- uni-easyinput 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容 +- uni-easyinput 新增 clear 事件,点击右侧叉号图标触发 +- uni-easyinput 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发 +- uni-easyinput 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等 +- uni-easyinput 优化 clearable 显示策略 +- uni-file-picker 修复 在uni-forms下样式不生效的bug +- uni-nav-bar 修复 组件示例中插槽用法无法显示内容的bug +- uni-swipe-action 修复 vue3 下使用组件不能正常运行的Bug +- uni-swipe-action 修复 h5端点击click触发两次的Bug +- uni-table 修复 微信小程序存在无使用组件的问题 +## 1.4.17(2022-06-30) +- 支持 ios 安全区 +## 1.4.16(2022-06-06) +- uni-breadcrumb 新增 支持 uni.scss 修改颜色 +- uni-data-select 修复 localdata 赋值不生效的 bug +- uni-data-select 新增 支持选项禁用(数据选项设置 disabled: true 即禁用) +- uni-data-select 修复 当 value 为 0 时选择不生效的 bug +- uni-easyinput 修复 关闭图标某些情况下无法取消的bug +- uni-fav 新增 stat 属性 ,是否开启uni统计功能 +- uni-goods-nav 新增 stat属性,是否开启uni统计功能 +- uni-group 新增 stat属性,是否开启uni统计功能 +- uni-nav-bar 新增 stat 属性 ,可开启统计 title 上报 ,仅使用了title 属性且项目开启了uni统计生效 +- uni-search-bar 新增 readonly 属性,组件只读 +- uni-swipe-action 修复 isPC 找不到的Bug +- uni-swipe-action 修复 在 nvue 下 disabled 失效的bug +- uni-tooltip 修复 content 为空时仍然弹出的bug +## 1.4.15(2022-05-07) +- uni-data-picker 修复 字节小程序 本地数据无法选择下一级的Bug +- uni-data-select 新增 记住上次的选项(仅 collection 存在时有效) +- uni-search-bar 修复 vue3 input 事件不生效的bug +- uni-search-bar 修复 多余代码导致的bug +- uni-tooltip 更新 text 属性变更为 content +- uni-tooltip 更新 移除 width 属性 +- uni-tooltip 修复 组件根 text 嵌套组件 warning +## 1.4.14(2022-04-18) +- uni-datetime-picker 修复 Vue3 下动态赋值,单选类型未响应的 bug +- uni-easyinput 修复 默认值不生效的bug +## 1.4.13(2022-04-02) +- uni-calendar 修复 条件编译 nvue 不支持的 css 样式 +- uni-calendar 修复 startDate、 endDate 属性失效的 bug +- uni-data-picker 修复 nvue 不支持的 v-show 的 bug +- uni-data-picker 修复 条件编译 nvue 不支持的 css 样式 +- uni-datetime-picker 修复 Vue3 下动态赋值未响应的 bug +- uni-easyinput 修复 value不能为0的bug +- uni-popup 修复 弹出层内部无法滚动的bug +- uni-popup 修复 小程序中高度错误的bug +- uni-popup 修复 快速调用open出现问题的Bug +- uni-rate 修复 条件判断 `NaN` 错误的 bug +- uni-swipe-action 修复 按钮字体大小不能设置的bug +- uni-swipe-action 修复 h5和app端下报el错误的bug +- uni-swipe-action 修复 HBuilderX 1.4.X 版本中,h5和app端下报错的bug +## 1.4.12(2022-02-19) +- uni-collapse 修复 初始化的时候 ,open 属性失效的bug +- uni-data-checkbox 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug +- uni-icons 优化 size 属性可以传入不带单位的字符串数值 +- uni-icons 优化 size 支持其他单位 +- uni-nav-bar 新增 left-width/right-width属性 ,可修改左右两侧的宽度 +- uni-popup 修复 safeArea 属性不能设置为false的bug +## 1.4.11(2022-01-21) +- uni-collapse 修复 微信小程序resize后组件收起的bug +- uni-countdown 修复 在微信小程序中样式不生效的bug +- uni-countdown 新增 update 方法 ,在动态更新时间后,刷新组件 +- uni-load-more 新增 showText属性 ,是否显示文本 +- uni-load-more 修复 nvue 平台下不显示文本的bug +- uni-load-more 修复 微信小程序平台样式选择器报警告的问题 +- uni-nav-bar 修复 在vue下,标题不垂直居中的bug +- uni-nav-bar 修复 height 属性类型错误 +- uni-nav-bar 新增 height 属性,可修改组件高度 +- uni-nav-bar 新增 dark 属性可可开启暗黑模式 +- uni-nav-bar 优化 标题字数过多显示省略号 +- uni-nav-bar 优化 插槽,插入内容可完全覆盖 +- uni-popup 修复 isMaskClick 失效的bug +- uni-popup 新增 cancelText \ confirmText 属性 ,可自定义文本 +- uni-popup 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色 +- uni-popup 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题 + +## 1.4.10(2022-01-17) +- uni-card 修复 在vue页面下略缩图显示不正常的bug +- uni-datetime-picker 修复 clear-icon 属性在小程序平台不生效的 bug +- uni-datetime-picker 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的 bug +- uni-fab 更新 组件依赖 +- +- uni-icons 修复 nvue 有些图标不显示的bug,兼容老版本图标 +- uni-icons 优化 示例可复制图标名称 +- uni-nav-bar 修复 color 属性不生效的bug +- uni-popup 修复 设置 safeArea 属性不生效的bug +- uni-popup 优化 组件示例 +- uni-popup 修复 vuedoc 文字错误 +## 1.4.9(2021-11-23) +- uni-ui 修复 vue3中某些scss变量无法找到的问题 +- uni-combox 优化 label、label-width 属性 +- uni-data-picker 修复 由上个版本引发的map、v-model等属性不生效的bug +- uni-file-picker 修复 参数为对象的情况下,url在某些情况显示错误的bug +- uni-icons 优化 兼容旧组件 type 值 +- uni-list 修复 在vue3中to属性在发行应用的时候报错的bug +- uni-scss 修复 vue3中scss语法兼容问题 +- uni-transition 修复 init 方法初始化问题 +## 1.4.8(2021-11-19) +- uni-fab 修复 阴影颜色不正确的bug +## 1.4.7(2021-11-19) +- uni-ui 新增 支持国际化 +- uni-ui 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- uni-ui 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-ui](https://uniapp.dcloud.io/component/uniui/uni-ui) +- uni-badge 修改 size 属性默认值调整为 small +- uni-badge 修改 type 属性,默认值调整为 error,info 替换 default +- uni-badge 修复 在字节小程序上样式不生效的 bug +- uni-calendar 修复 弹出层被 tabbar 遮盖 bug +- uni-card 重构插槽的用法 ,header 替换为 title +- uni-card 新增 actions 插槽 +- uni-card 新增 cover 封面图属性和插槽 +- uni-card 新增 padding 内容默认内边距离 +- uni-card 新增 margin 卡片默认外边距离 +- uni-card 新增 spacing 卡片默认内边距 +- uni-card 新增 shadow 卡片阴影属性 +- uni-card 取消 mode 属性,可使用组合插槽代替 +- uni-card 取消 note 属性 ,使用actions插槽代替 +- uni-collapse 优化 show-arrow 属性默认为true +- uni-collapse 新增 show-arrow 属性,控制是否显示右侧箭头 +- uni-countdown 新增 font-size 支持自定义字体大小 +- uni-data-checkbox 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题 +- uni-data-checkbox 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +- uni-data-checkbox 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-dateformat 优化 默认时间不再是当前时间,而是显示'-'字符 +- uni-datetime-picker 修复 hide-second 在移动端的 bug +- uni-datetime-picker 修复 单选赋默认值时,赋值日期未高亮的 bug +- uni-datetime-picker 修复 赋默认值时,移动端未正确显示时间的 bug +- uni-datetime-picker 新增 hide-second 属性,支持只使用时分,隐藏秒 +- uni-datetime-picker 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次 +- uni-datetime-picker 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法 +- uni-datetime-picker 优化 调整字号大小,美化日历界面 +- uni-datetime-picker 优化 范围选择器在 pc 端过宽的问题 +- uni-datetime-picker 新增 支持作为 uni-forms 子组件相关功能 +- uni-datetime-picker 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug +- uni-datetime-picker 修复 type 属性动态赋值无效的 bug +- uni-datetime-picker 修复 ‘确认’按钮被 tabbar 遮盖 bug +- uni-datetime-picker 修复 组件未赋值时范围选左、右日历相同的 bug +- uni-datetime-picker 修复 范围选未正确显示当前值的 bug +- uni-datetime-picker 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug +- uni-easyinput 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug +- uni-easyinput 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-file-picker 新增 参数中返回 fileID 字段 +- uni-file-picker 修复 腾讯云传入fileID 不能回显的bug +- uni-file-picker 修复 选择图片后,不能放大的问题 +- uni-file-picker 修复 由于 0.2.11 版本引起的不能回显图片的Bug +- uni-file-picker 新增 clearFiles(index) 方法,可以手动删除指定文件 +- uni-file-picker 修复 v-model 值设为 null 报错的Bug +- uni-file-picker 修复 return-type="object" 时,无法删除文件的Bug +- uni-file-picker 修复 auto-upload 属性失效的Bug +- uni-forms 修复 label 插槽不生效的bug +- uni-forms 修复 没有添加校验规则的字段依然报错的Bug +- uni-forms 修复 重置表单错误信息无法清除的问题 +- uni-forms 修复 表单验证只生效一次的问题 +- uni-icons 新增 更多图标 +- uni-icons 优化 自定义图标使用方式 +- uni-link 修复 在 nvue 下不显示的 bug +- uni-pagination 修复 current 、value 属性未监听,导致高亮样式失效的 bug +- uni-rate 优化 默认值修改为 0 颗星 +- uni-search-bar 修复 value 属性与 modelValue 属性不兼容的Bug +- uni-swipe-action 新增 close-all 方法,关闭所有已打开的组件 +- uni-swipe-action 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- uni-swipe-action 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- uni-swipe-action 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +- uni-tag 新增 提供组件设计资源,组件样式调整 +- uni-tag 移除 插槽 +- uni-tag 移除 type 属性的 royal 选项 +- uni-tag type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.4.2(2021-08-20) +- 新增 uni-ui 组件支持国际化 i18n +- uni-collapse 优化 show-arrow 属性默认为true +- uni-collapse 新增 show-arrow 属性,控制是否显示右侧箭头 +- uni-data-checkbox 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +- uni-easyinput 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug +- uni-file-picker 修复 由于 0.2.11 版本引起的不能回显图片的Bug +- uni-file-picker 新增 clearFiles(index) 方法,可以手动删除指定文件 +- uni-file-picker 修复 v-model 值设为 null 报错的Bug +- uni-swipe-action 新增 close-all 方法,关闭所有已打开的组件 +- uni-swipe-action 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- uni-swipe-action 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- uni-swipe-action 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +## 1.4.0(2021-08-13) +- uni-calendar 修复 弹出层被 tabbar 遮盖 bug +- uni-data-checkbox 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-dateformat 调整 默认时间不再是当前时间,而是显示'-'字符 +- uni-datetime-picker 新增 适配 vue3 +- uni-datetime-picker 新增 支持作为 uni-forms 子组件相关功能 +- uni-datetime-picker 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug +- uni-datetime-picker 修复 type 属性动态赋值无效的 bug +- uni-datetime-picker 修复 ‘确认’按钮被 tabbar 遮盖 bug +- uni-datetime-picker 修复 组件未赋值时范围选左、右日历相同的 bug +- uni-datetime-picker 修复 范围选未正确显示当前值的 bug +- uni-datetime-picker 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug +- uni-easyinput 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-file-picker 修复 return-type="object" 时,无法删除文件的Bug +- uni-file-picker 修复 auto-upload 属性失效的Bug +- uni-forms 修复 没有添加校验规则的字段依然报错的Bug +- uni-forms 修复 重置表单错误信息无法清除的问题 +- uni-forms 优化 组件文档 +- uni-forms 修复 表单验证只生效一次的问题 +- uni-tag type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.3.9(2021-08-02) +- uni-datetime-picker 新增 return-type 属性支持返回 date 日期对象 +- uni-file-picker 修复 fileExtname属性不指定值报错的Bug +- uni-file-picker 修复 在某种场景下图片不回显的Bug +- uni-link 支持自定义插槽 +## 1.3.8(2021-07-31) +- uni-ui 组件兼容 vue3 +- uni-collapse 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug +- uni-collapse 优化 组件示例 +- uni-collapse 新增 组件折叠动画 +- uni-collapse 新增 value\v-model 属性 ,动态修改面板折叠状态 +- uni-collapse 新增 title 插槽 ,可定义面板标题 +- uni-collapse 新增 border 属性 ,显示隐藏面板内容分隔线 +- uni-collapse 新增 title-border 属性 ,显示隐藏面板标题分隔线 +- uni-collapse 修复 resize 方法失效的Bug +- uni-collapse 修复 change 事件返回参数不正确的Bug +- uni-collapse 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法 +- uni-data-checkbox 优化 在uni-forms组件,与label不对齐的问题 +- uni-data-checkbox 修复 单选默认值为0不能选中的Bug +- uni-easyinput 优化 errorMessage 属性支持 Boolean 类型 +- uni-file-picker 修复 return-type为object下,返回值不正确的Bug +- uni-file-picker 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题 +- uni-file-picker 优化 h5平台下上传文件导致页面卡死的问题 +- uni-forms 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- uni-forms 修复 1.2.1 引起的示例在小程序平台报错的Bug +- uni-forms 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- uni-forms 修复 不指定name属性时,运行报错的Bug +- uni-forms 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- uni-forms 优化 组件示例,新增动态校验示例代码 +- uni-forms 优化 组件文档,使用方式更清晰 +- uni-list 修复 与其他组件嵌套使用时,点击失效的Bug +- uni-swipe-action 修复 跨页面修改组件数据 ,导致不能滑动的问题 +## 1.3.7(2021-07-16) +- uni-ui 兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- uni-datetime-picker 修复 单选日期类型,初始赋值后不在当前日历的 bug +- uni-datetime-picker 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效) +- uni-datetime-picker 优化 移动端移除显示框的清空按钮,无实际用途 +- uni-datetime-picker 修复 组件赋值为空,界面未更新的 bug +- uni-datetime-picker 修复 start 和 end 不能动态赋值的 bug +- uni-datetime-picker 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug +## 1.3.6(2021-07-09) +- uni-data-checkbox 优化 删除无用日志 +- uni-data-checkbox 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题 +- uni-data-checkbox 修复 nvue 黑框样式问题 +- uni-datetime-picker 修复 范围选择不能动态赋值的 bug +- uni-datetime-picker 修复 范围选择的初始时间在一个月内时,造成无法选择的bug +- uni-datetime-picker 优化 弹出层在超出视窗边缘定位不准确的问题 +- uni-datetime-picker 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug +- uni-datetime-picker 优化 弹出层在超出视窗边缘被遮盖的问题 +- uni-datetime-picker 新增 maskClick 事件 +- uni-datetime-picker 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px +- uni-datetime-picker 修复 范围选择时清空返回值不合理的bug,['', ''] -> [] +- uni-datetime-picker 新增 日期时间显示框支持插槽 +- uni-file-picker 修复 sourceType 缺少默认值导致 ios 无法选择文件 +- uni-file-picker 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改 +- uni-table 新增 uni-th 支持 date 日期筛选范围 +- uni-table 新增 uni-th 支持 range 筛选范围 +- uni-table 新增 uni-th 筛选功能 +## 1.3.5(2021-07-02) +- uni-card 优化 图文卡片无图片加载时,提供占位图标 +- uni-card 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持) +- uni-card 修复 thumbnail 不存在仍然占位的 bug +- uni-data-checkbox 修复 selectedTextColor 属性不生效的Bug +- uni-datetime-picker 优化 添加 uni-icons 依赖 +- uni-easyinput 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug +- uni-file-picker 修复 由 0.0.10 版本引发的 returnType 属性失效的问题 +- uni-file-picker 优化 文件上传后进度条消失时机 +- uni-file-picker 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug +- uni-forms 修复 pattern 属性在微信小程序平台无效的问题 +## 1.3.4(2021-06-25) +- uni-badge 优化 示例项目 +- uni-countdown 修复 uni-countdown 重复赋值跳两秒的 bug +- uni-easyinput 修复 passwordIcon 属性拼写错误的 bug +- uni-forms 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +- uni-forms 修复 只写setRules方法而导致校验不生效的Bug +- uni-forms 修复 由上个办法引发的错误提示文字错位的Bug +- uni-forms 修复 不设置 label 属性 ,无法设置label插槽的问题 +- uni-forms 修复 不设置label属性,label-width属性不生效的bug +- uni-forms 修复 setRules 方法与rules属性冲突的问题 +- uni-link 新增 download 属性,H5平台下载文件名 +- uni-popup 新增 mask-click 遮罩层点击事件 +- uni-popup 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +- uni-tag 修复 uni-tag 在字节跳动小程序上 css 类名编译错误的 bug +## 1.3.3(2021-06-18) +- uni-easyinput 新增 passwordIcon 属性,当type=password时是否显示小眼睛图标 +- uni-easyinput 修复 confirmType 属性不生效的问题 +- uni-easyinput 修复 disabled 状态可清出内容的 bug +- uni-file-picker 修复 删除文件时无法触发 v-model 的Bug +- uni-popup 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +- uni-popup 修复 错误的 watch 字段 +- uni-popup 修复 safeArea 属性不生效的问题 +- uni-popup 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.3.2(2021-06-04) +- uni-data-checkbox 新增 map 属性,可以方便映射text/value属性 +- uni-data-checkbox 修复 不关联服务空间的情况下组件报错的Bug +- uni-data-picker 修复 上个版本引出的本地数据无法选择带有children的2级节点 +- uni-forms 修复 动态删减数据导致报错的问题 +- uni-forms 新增 modelValue 属性 ,value 即将废弃 +- uni-forms 新增 uni-forms-item 可以设置单独的 rules +- uni-forms 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- uni-forms 优化 submit 事件重命名为 validate +- uni-data-picker 修复 无法加载云端数据的问题 +- uni-data-picker 修复 v-model无效问题 +- uni-data-picker 修复 loaddata 为空数据组时加载时间过长问题 +- uni-datetime-picker 修复 图标在小程序上不显示的 bug +- uni-datetime-picker 优化 重命名引用组件,避免潜在组件命名冲突 +- uni-datetime-picker 优化 代码目录扁平化 +- uni-tag 修复 未定义 sass 变量 "$uni-color-royal" 的bug +## 1.3.1(2021-05-14) +- uni-badge 新增 uni-badge 的 absolute 属性,支持定位 +- uni-badge 新增 uni-badge 的 offset 属性,支持定位偏移 +- uni-badge 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点 +- uni-badge 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+ +- uni-badge 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式 +- uni-badge 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug +- uni-badge 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug +- uni-badge 新增 uni-badge 属性 custom-style, 支持自定义样式 +- uni-datetime-picker 修复 ios 下不识别 '-' 日期格式的 bug +- uni-datetime-picker 优化 pc 下弹出层添加边框和阴影 +- uni-datetime-picker 修复 在 admin 中获取弹出层定位错误的bug +- uni-datetime-picker 修复 type 属性向下兼容,默认值从 date 变更为 datetime +- uni-datetime-picker 支持日历形式的日期+时间的范围选择 +- uni-steps 修复 uni-steps 横向布局时,多行文字高度不合理的 bug +- uni-countdown 修复 uni-countdown 不能控制倒计时的 bug +- uni-tag 修复 royal 类型无效的bug +- uni-tag 修复 uni-tag 宽度不自适应的bug +- uni-tag 新增 uni-tag 支持属性 custom-style 自定义样式 +- uni-link 新增 href 属性支持 tel:|mailto: +- uni-popup 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +- uni-popup 新增 type 属性的 left\right 值,支持左右弹出 +- uni-popup 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- uni-popup 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- uni-popup 新增 safeArea 属性,是否适配底部安全区 +- uni-popup 修复 App\h5\微信小程序底部安全区占位不对的Bug +- uni-popup 修复 App 端弹出等待的Bug +- uni-popup 优化 提升低配设备性能,优化动画卡顿问题 +- uni-popup 优化 更简单的组件自定义方式 +- uni-table 修复 示例项目缺少组件的Bug +- uni-forms 修复 自定义检验器失效的问题 +- uni-title 修复 示例项目缺少组件的Bug +- uni-transition 修复 示例项目缺少组件的Bug +- uni-swiper-dot 修复 示例项目缺少组件的Bug +- uni-ui 新增 组件示例地址 +## 1.3.0(2021-04-23) +- uni-combox 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-data-picker 修复 非树形数据有 where 属性查询报错的问题 +- uni-fav 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-goods-nav 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-nav-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-notice-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-number-box 修复 uni-number-box 浮点数运算不精确的 bug +- uni-number-box 修复 uni-number-box change 事件触发不正确的 bug +- uni-number-box 新增 uni-number-box v-model 双向绑定 +- uni-rate 修复 布局变化后 uni-rate 星星计算不准确的 bug +- uni-rate 优化 添加依赖 uni-icons, 导入 uni-rate 自动下载依赖 +- uni-search-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-steps 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-transition 新增 通过方法自定义动画 +- uni-transition 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式 +- uni-transition 优化 动画触发逻辑,使动画更流畅 +- uni-transition 优化 支持单独的动画类型 +- uni-transition 优化 文档示例 +## 1.2.13(2021-04-16) +- uni-ui 新增 uni-data-picker 支持云端非树形表结构数据 +- uni-ui 修复 uni-data-checkbox nvue 下无法选中的问题 +- uni-ui 修复 uni-data-picker 根节点 parent_field 字段等于null时选择界面错乱问题 +- uni-ui 修复 uni-file-picker 选择的文件非 file-extname 字段指定的扩展名报错的Bug +- uni-ui 修复 uni-swipe-action 报错 nv_navigator is not defined 的bug +- uni-ui 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +- uni-ui 优化 uni-file-picker file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔 +- uni-ui 优化 uni-pagination PC 和 移动端适配不同的 ui +- uni-ui 更新 uni-file-picker 组件示例 +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug +- uni-ui 新增 uni-search-bar 的 focus 事件 +- uni-ui 修复 uni-rate 属性 margin 值为 string 组件失效的 bug +- uni-data-picker 修复 本地数据概率无法回显时问题 +- uni-table 新增 sortable 属性,是否开启单列排序 +- uni-table 优化 表格多选逻辑 +## 1.2.12(2021-03-23) +- uni-ui 新增 uni-datetime-picker 的 hide-second 属性、border 属性; +- uni-ui 修复 uni-datetime-picker 选择跟显示的日期不一样的 bug, +- uni-ui 修复 uni-datetime-picker change事件触发2次的 bug +- uni-ui 修复 uni-datetime-picker 分、秒 end 范围错误的 bug +- uni-ui 新增 uni-tr selectable 属性,用于 type=selection 时,设置某行是否可由全选按钮控制 +- uni-ui 新增 uni-data-checkbox 新增 disabled属性,支持nvue +- uni-ui 优化 uni-data-checkbox 无选项时提示“暂无数据” +- uni-ui 优化 uni-data-checkbox 默认颜色显示 +- uni-ui 新增 uni-link href 属性支持 tel:|mailto: +- uni-ui 新增 uni-table 示例demo +- uni-ui 修复 uni-data-picker 微信小程序某些情况下无法选择的问题,事件无法触发的问题 +- uni-ui 修复 uni-nav-bar easycom 下,找不到 uni-status-bar 的bug +- uni-ui 修复 uni-easyinput 示例在 qq 小程序上的bug +- uni-ui 修复 uni-forms 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +- uni-ui 调整 cli 项目 建议使用 easycom 方式引用组件,如使用按需引用,需手动维护组件内部引用 + +## 1.2.11(2021-02-24) +- 调整为uni_modules目录规范 +- uni-data-picker 新增 数据驱动的picker选择器 +- uni-file-picker 新增 文件选择上传 +- uni-row 新增 栅格系统 +- uni-data-checkbox 优化 支持 nvue +- uni-forms 修复 偶发性获取表单值错误的Bug +- uni-forms 修复 校验 uni-data-picker value 为 0 时,返回值错误的Bug +- uni-forms 修复 uni-forms-item 组件隐藏时依然触发校验的bug +- uni-forms 优化 实时校验 +- uni-forms 优化 兼容nvue页面 +- uni-easyinput 优化 兼容nvue页面 +- uni-group 优化 兼容nvue页面 +- uni-popup 优化 组件适配 PC +- uni-fab 优化 适配 PC +- uni-swiper-dot 优化 适配 PC +- uni-rate 优化 适配 PC +- uni-notice-bar 优化 适配 PC +- uni-indexed-list 优化 适配 PC +- uni-combox 优化 适配 PC +- uni-transition 优化 适配 PC +- uni-nav-bar 优化 适配 PC +- uni-swipe-action 优化 适配 PC diff --git a/im-uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue b/im-uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue new file mode 100644 index 0000000..0970892 --- /dev/null +++ b/im-uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue @@ -0,0 +1,7 @@ + + + diff --git a/im-uniapp/uni_modules/uni-ui/package.json b/im-uniapp/uni_modules/uni-ui/package.json new file mode 100644 index 0000000..857ed51 --- /dev/null +++ b/im-uniapp/uni_modules/uni-ui/package.json @@ -0,0 +1,129 @@ +{ + "id": "uni-ui", + "displayName": "uni-ui", + "version": "1.4.27", + "description": "uni-ui 是基于uni-app的、全端兼容的、高性能UI框架", + "keywords": [ + "uni-ui", + "uniui", + "UI组件库", + "ui框架", + "ui库" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.10" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-calendar", + "uni-card", + "uni-collapse", + "uni-combox", + "uni-countdown", + "uni-data-checkbox", + "uni-data-picker", + "uni-data-select", + "uni-dateformat", + "uni-datetime-picker", + "uni-drawer", + "uni-easyinput", + "uni-fab", + "uni-fav", + "uni-file-picker", + "uni-forms", + "uni-goods-nav", + "uni-grid", + "uni-group", + "uni-icons", + "uni-indexed-list", + "uni-link", + "uni-list", + "uni-load-more", + "uni-nav-bar", + "uni-notice-bar", + "uni-number-box", + "uni-pagination", + "uni-popup", + "uni-rate", + "uni-row", + "uni-search-bar", + "uni-section", + "uni-segmented-control", + "uni-steps", + "uni-swipe-action", + "uni-swiper-dot", + "uni-table", + "uni-tag", + "uni-title", + "uni-tooltip", + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uni-ui/readme.md b/im-uniapp/uni_modules/uni-ui/readme.md new file mode 100644 index 0000000..23f8456 --- /dev/null +++ b/im-uniapp/uni_modules/uni-ui/readme.md @@ -0,0 +1,247 @@ +> 当前插件不包含示例页面 ,如需示例请在 HBuiderX 中新建 `hello uni-app > 扩展组件` 中查看 +> +> 代码示例地址 :[https://ext.dcloud.net.cn/plugin?id=4941](https://ext.dcloud.net.cn/plugin?id=4941) +> +> 组件演示地址:[https://hellouniapp.dcloud.net.cn](https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge) +> +> 组件文档地址:[https://uniapp.dcloud.io/component/uniui/uni-ui](https://uniapp.dcloud.io/component/uniui/uni-ui) + +# uni-ui 介绍 + +## uni-ui产品特点 + +### 1. 高性能 + +目前为止,在小程序和混合app领域,暂时还没有比 `uni-ui` 更高性能的框架。 +- 自动差量更新数据 + +虽然uni-app支持小程序自定义组件,所有小程序的ui库都可以用。但小程序自定义组件的ui库都需要使用setData手动更新数据,在大数据量时、或高频更新数据时,很容易产生性能问题。 + +而 `uni-ui` 属于vue组件,uni-app引擎底层自动diff更新数据。当然其实插件市场里众多vue组件都具备这个特点。 +- 优化逻辑层和视图层通讯折损 + +非H5,不管是小程序还是App,不管是app的webview渲染还是原生渲染,全都是逻辑层和视图层分离的。这里就有一个逻辑层和视图层通讯的折损问题。 +比如在视图层拖动一个可跟手的组件,由于通讯的损耗,用js监听很难做到实时跟手。 + +这时就需要使用css动画以及平台底层提供的wxs、bindingx等技术。不过这些技术都比较复杂,所以 `uni-ui` 里做了封装,在需要跟手式操作的ui组件,比如swiperaction列表项左滑菜单,就在底层使用了这些技术,实现了高性能的交互体验 +- 背景停止 + +很多ui组件是会一直动的,比如轮播图、跑马灯。即便这个窗体被新窗体挡住,它在背景层仍然在消耗着硬件资源。在Android的webview版本为chrome66以上,背景操作ui会引发很严重的性能问题,造成前台界面明显卡顿。 + +而 `uni-ui` 的组件,会自动判断自己的显示状态,在组件不再可见时,不会再消耗硬件资源。 + +### 2. 全端 + + `uni-ui` 的组件都是多端自适应的,底层会抹平很多小程序平台的差异或bug。 + +比如导航栏navbar组件,会自动处理不同端的状态栏。 +比如swiperaction组件,在app和微信小程序上会使用交互体验更好的wxs技术,但在不支持wxs的其他小程序端会使用js模拟类似效果。 + + `uni-ui` 还支持nvue原生渲染,[详见](https://github.com/dcloudio/uni-ui/tree/nvue-uni-ui) + +未来 `uni-ui` 还会支持pc等大屏设备。 + +### 3. 与uni统计自动集成实现免打点 + +uni统计是优秀的多端统计平台,见[tongji.dcloud.net.cn](https://tongji.dcloud.net.cn)。 + +除了一张报表看全端,它的另一个重要特点是免打点。 +比如使用 `uni-ui` 的navbar标题栏、收藏、购物车等组件,均可实现自动打点,统计页面标题等各种行为数据。 +当然你也可以关闭uni统计,这不是强制的。 + +### 4. 主题扩展 + + `uni-ui` 支持[uni.scss](https://uniapp.dcloud.io/collocation/uni-scss),可以方便的切换App的风格。 + +ui是一种需求非常发散的产品,DCloud官方也无意用 `uni-ui` 压制第三方ui插件的空间,但官方有义务在性能和多端方面提供一个开源的标杆给大家。 + +我们欢迎更多优秀的ui组件出现,也欢迎更多人贡献 `uni-ui` 的主题风格,满足更多用户的需求。 + + +## 快速开始 + +uni-ui支持 HBuilderX直接新建项目模板、npm安装和单独导入个别组件等多种使用方式 + +### 在HBuilderX 新建uni-app项目的模板中,选择uni-ui模板 +![HBuilderX内创建uni-ui项目](https://img.cdn.aliyun.dcloud.net.cn/uni-app/doc/create-uni-ui-project.jpg) + +由于uni-app独特的[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)技术,可以免引用、注册,直接使用各种符合规则的vue组件。 + +在代码区键入`u`,拉出各种内置或uni-ui的组件列表,选择其中一个,即可使用该组件。 + +光标放在组件名称上,按F1,可以查阅组件的文档。 + +![uni-ui代码块](https://img.cdn.aliyun.dcloud.net.cn/uni-app/doc/uni-ui-snippet.jpg) + +### 通过 uni_modules 单独安装组件 +如果你没有创建uni-ui项目模板,也可以在你的工程里,通过 uni_modules 单独安装需要的某个组件。下表为uni-ui的扩展组件清单,点击每个组件在详情页面可以导入组件到项目下,导入后直接使用即可,无需import和注册。 + +|组件名|组件说明| +|---|---| +|uni-badge|[数字角标](https://ext.dcloud.net.cn/plugin?name=uni-badge)| +|uni-calendar|[日历](https://ext.dcloud.net.cn/plugin?name=uni-calendar)| +|uni-card|[卡片](https://ext.dcloud.net.cn/plugin?name=uni-card)| +|uni-collapse|[折叠面板](https://ext.dcloud.net.cn/plugin?name=uni-collapse)| +|uni-combox|[组合框](https://ext.dcloud.net.cn/plugin?name=uni-combox)| +|uni-countdown|[倒计时](https://ext.dcloud.net.cn/plugin?name=uni-countdown)| +|uni-data-checkbox|[数据选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-checkbox)| +|uni-data-picker|[数据驱动的picker选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-picker)| +|uni-dateformat|[日期格式化](https://ext.dcloud.net.cn/plugin?name=uni-dateformat)| +|uni-datetime-picker|[日期选择器](https://ext.dcloud.net.cn/plugin?name=uni-datetime-picker)| +|uni-drawer|[抽屉](https://ext.dcloud.net.cn/plugin?name=uni-drawer)| +|uni-easyinput|[增强输入框](https://ext.dcloud.net.cn/plugin?name=uni-easyinput)| +|uni-fab|[悬浮按钮](https://ext.dcloud.net.cn/plugin?name=uni-fab)| +|uni-fav|[收藏按钮](https://ext.dcloud.net.cn/plugin?name=uni-fav)| +|uni-file-picker|[文件选择上传](https://ext.dcloud.net.cn/plugin?name=uni-file-picker)| +|uni-forms|[表单](https://ext.dcloud.net.cn/plugin?name=uni-forms)| +|uni-goods-nav|[商品导航](https://ext.dcloud.net.cn/plugin?name=uni-goods-nav)| +|uni-grid|[宫格](https://ext.dcloud.net.cn/plugin?name=uni-grid)| +|uni-group|[分组](https://ext.dcloud.net.cn/plugin?name=uni-group)| +|uni-icons|[图标](https://ext.dcloud.net.cn/plugin?name=uni-icons)| +|uni-indexed-list|[索引列表](https://ext.dcloud.net.cn/plugin?name=uni-indexed-list)| +|uni-link|[超链接](https://ext.dcloud.net.cn/plugin?name=uni-link)| +|uni-list|[列表](https://ext.dcloud.net.cn/plugin?name=uni-list)| +|uni-load-more|[加载更多](https://ext.dcloud.net.cn/plugin?name=uni-load-more)| +|uni-nav-bar|[自定义导航栏](https://ext.dcloud.net.cn/plugin?name=uni-nav-bar)| +|uni-notice-bar|[通告栏](https://ext.dcloud.net.cn/plugin?name=uni-notice-bar)| +|uni-number-box|[数字输入框](https://ext.dcloud.net.cn/plugin?name=uni-number-box)| +|uni-pagination|[分页器](https://ext.dcloud.net.cn/plugin?name=uni-pagination)| +|uni-popup|[弹出层](https://ext.dcloud.net.cn/plugin?name=uni-popup)| +|uni-rate|[评分](https://ext.dcloud.net.cn/plugin?name=uni-rate)| +|uni-row|[布局-行](https://ext.dcloud.net.cn/plugin?name=uni-row)| +|uni-search-bar|[搜索栏](https://ext.dcloud.net.cn/plugin?name=uni-search-bar)| +|uni-segmented-control|[分段器](https://ext.dcloud.net.cn/plugin?name=uni-segmented-control)| +|uni-steps|[步骤条](https://ext.dcloud.net.cn/plugin?name=uni-steps)| +|uni-swipe-action|[滑动操作](https://ext.dcloud.net.cn/plugin?name=uni-swipe-action)| +|uni-swiper-dot|[轮播图指示点](https://ext.dcloud.net.cn/plugin?name=uni-swiper-dot)| +|uni-table|[表格](https://ext.dcloud.net.cn/plugin?name=uni-table)| +|uni-tag|[标签](https://ext.dcloud.net.cn/plugin?name=uni-tag)| +|uni-title|[章节标题](https://ext.dcloud.net.cn/plugin?name=uni-title)| +|uni-transition|[过渡动画](https://ext.dcloud.net.cn/plugin?name=uni-transition)| + + +使用 `uni_modules` 方式安装组件库,可以直接通过插件市场导入,通过右键菜单快速更新组件,不需要引用、注册,直接在页面中使用 `uni-ui` 组件。[点击安装 uni-ui 组件库](https://ext.dcloud.net.cn/plugin?id=55) + +**注意:下载最新的组件目前仅支持 uni_modules ,非 uni_modules 版本最高支持到组件的1.2.10版本** + +如不能升级到 `uni_modules` 版本,可以使用 `uni_modules` 安装好对应组件,将组件拷贝到对应目录。 + +例如需更新 `uni-list`和`uni-badge` ,将 `uni_modules>uni-list>components`和`uni_modules>uni-badege>components`下所有目录拷贝到如下目录即可: + + +**目录示例** + +```json +┌─components 组件目录 +│ ├─uni-list list 列表目录 +│ │ └─uni-list.vue list 组件文件 +│ ├─uni-list-item list-item 列表目录 +│ │ └─uni-list-item.vue list 组件文件 +│ ├─uni-badge badge 角标目录 +│ │ └─uni-badge.vue badge 组件文件 +│ └─ //.... 更多组件文件 +├─pages 业务页面文件存放的目录 +│ ├─index +│ │ └─index.vue index示例页面 +├─main.js Vue初始化入口文件 +├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期 +├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见 +└─pages.json 配置页 + +``` + +### 通过 `uni_modules` 导入全部组件 +如果想一次把所有uni-ui组件导入到项目中,只需要导入一个 `uni-ui` 组件即可 [点击去导入](https://ext.dcloud.net.cn/plugin?id=55)。 + +如果没有自动导入其他组件,可以在 uni-ui 组件目录上右键选择 `安装三方插件依赖` 即可。 + + + +### npm安装 +在 `vue-cli` 项目中可以使用 `npm` 安装 `uni-ui` 库 ,或者直接在 `HBuilderX` 项目中使用 `npm` 。 + +> **注意** +> cli 项目默认是不编译 `node_modules` 下的组件的,导致条件编译等功能失效 ,导致组件异常 +> 需要在根目录创建 `vue.config.js` 文件 ,增加 `@dcloudio/uni-ui` 包的编译即可正常 +> ```javascript +> // vue.config.js +> module.exports = { +> transpileDependencies:['@dcloudio/uni-ui'] +> } +> ``` + + + +**准备 sass** + +`vue-cli` 项目请先安装 sass 及 sass-loader,如在 HBuliderX 中使用,可跳过此步。 + +- 安装 sass +``` + npm i sass -D 或 yarn add sass -D +``` + +- 安装 sass-loader +``` +npm i sass-loader@10.1.1 -D 或 yarn add sass-loader@10.1.1 -D +``` + +> 如果 `node` 版本小于 16 ,sass-loader 请使用低于 @11.0.0 的版本,[sass-loader@11.0.0 不支持 vue@2.6.12 ](https://stackoverflow.com/questions/66082397/typeerror-this-getoptions-is-not-a-function) +> 如果 `node` 版本大于 16 , `sass-loader` 建议使用 `v8.x` 版本 + +**安装 uni-ui** + +``` +npm i @dcloudio/uni-ui 或 yarn add @dcloudio/uni-ui +``` + + + +**配置easycom** + +使用 `npm` 安装好 `uni-ui` 之后,需要配置 `easycom` 规则,让 `npm` 安装的组件支持 `easycom` + +打开项目根目录下的 `pages.json` 并添加 `easycom` 节点: + +```javascript +// pages.json +{ + "easycom": { + "autoscan": true, + "custom": { + // uni-ui 规则如下配置 + "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" + } + }, + + // 其他内容 + pages:[ + // ... + ] +} + +``` + +在 ``template`` 中使用组件: + +```html + + + +``` + + **注意** + - uni-ui 现在只推荐使用 `easycom` ,如自己引用组件,可能会出现组件找不到的问题 + - 使用 npm 安装的组件,默认情况下 babel-loader 会忽略所有 node_modules 中的文件 ,导致条件编译失效,需要通过配置 `vue.config.js` 解决: + ```javascript + // 在根目录创建 vue.config.js 文件,并配置如下 + module.exports = { + transpileDependencies: ['@dcloudio/uni-ui'] + } + ``` + - uni-ui 是uni-app内置组件的扩展。注意与web开发不同,uni-ui不包括基础组件,它是基础组件的补充。web开发中有的开发者习惯用一个ui库完成所有开发,但在uni-app体系中,推荐开发者首先使用性能更高的基础组件,然后按需引入必要的扩展组件。 + - `uni-ui` 不支持使用 `Vue.use()` 的方式安装 + + +### 贡献代码 +在使用 `uni-ui` 中,如遇到无法解决的问题,请提 [Issues](https://github.com/dcloudio/uni-ui/issues) 给我们,假如您有更好的点子或更好的实现方式,也欢迎给我们提交 [PR](https://github.com/dcloudio/uni-ui/pulls) \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/LICENSE b/im-uniapp/uni_modules/uview-plus/LICENSE new file mode 100644 index 0000000..8e39ead --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 www.uviewui.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/README.md b/im-uniapp/uni_modules/uview-plus/README.md new file mode 100644 index 0000000..427a3ee --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/README.md @@ -0,0 +1,64 @@ +

+ logo +

+

uview-plus 3.0

+

多平台快速开发的UI框架

+ +[![stars](https://img.shields.io/github/stars/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus) +[![forks](https://img.shields.io/github/forks/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus) +[![issues](https://img.shields.io/github/issues/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus/issues) +[![release](https://img.shields.io/github/v/release/ijry/uview-plus?style=flat-square)](https://gitee.com/jry/uview-plus/releases) +[![license](https://img.shields.io/github/license/ijry/uview-plus?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License) + +## 说明 + +uview-plus,是uni-app全面兼容vue3/nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。 + +## [官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com) + + +## 预览 + +您可以通过**微信**扫码,查看最佳的演示效果。 +
+
+ + +## 链接 + +- [官方文档](https://uview-plus.jiangruyi.com) +- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html) +- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html) +- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html) + +## 交流反馈 + +欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html) + +## 关于PR + +> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。 +> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢! + +## 安装 + +#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus) + +请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容 + +## 快速上手 + +请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容 + +## 使用方法 +配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 + +```html + +``` + +## 版权信息 +uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。 + diff --git a/im-uniapp/uni_modules/uview-plus/changelog.md b/im-uniapp/uni_modules/uview-plus/changelog.md new file mode 100644 index 0000000..1d40799 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/changelog.md @@ -0,0 +1,242 @@ +## 3.3.5(2024-07-10) +picker组件支持hasInput模式 +## 3.3.4(2024-07-07) +fix: input组件双向绑定问题 #419 + +lazy-load完善emit + +优化通用小程序分享 + +## 3.3.2(2024-06-27) +fix: 在Nvue环境中编译,出现大量警告 #406 +## 3.3.1(2024-06-27) +u-button组件报错,找不到button mixins #407 +## 3.3.0(2024-06-27) +feat: checkbox支持label设置slot + +feat: modal增加customClass + +feat: navbar、popup、tabs、text支持customClass + +fix: cell组建缺少flex布局 + +fix: 修复微信小程序真机调试时快速输入出现文本回退问题 + +feat: tag增加默认slot + +公共mixin改造为按需导入语法 + +refactor: 组件props混入mixin改造为按需导入语法 + +fix: u-tabbar 安卓手机点击按钮变蓝问题 #396 + +feat: upload组建增加extension属性 + +fix: upload组件参数mode添加left + +fix: 修复阴影在非nvue时白色背景色不显示 + +## 3.2.24(2024-06-11) +fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新 +## 3.2.23(2024-05-30) +fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次 + +fix: #373 搜索组件up-search的@clear事件无效 + +fix: #372 ActionSheet 组件的取消按钮触发区域太小 + +## 3.2.22(2024-05-13) +上传组件支持微信小程序预览视频 + +修复折叠面板右侧箭头不显示 + +修复uxp2px + +## 3.2.21(2024-05-10) +fix: loading-icon修复flex布局 +## 3.2.20(2024-05-10) +修复瀑布流大小写#355 +## 3.2.19(2024-05-10) +去除意外的文件引入 +## 3.2.18(2024-05-09) +fix: 349 popup 组件设置 zIndex 属性后,组件渲染异常# +feat: 搜索框增加adjustPosition属性 +fix: #331增加u-action-sheet__cancel +优化mixin兼容性 +feat: #326 up-list增加下拉刷新功能 +fix: #319 优化up-tabs参数与定义匹配 +fix: index-list组件微信小程序端使用自定义导航栏异常 +fix: #285 pickerimmediateChange 写死为true +fix: #111 u-scroll-list组件,隐藏指示器后报错, 提示找不到ref +list增加微信小程序防抖配置 + +## 3.2.17(2024-05-08) +fix: 支付宝小程序二维码渲染 +## 3.2.16(2024-05-06) +修复tabs中,当前激活样式的undefined bug + +fix: #341u-code 倒计时没结束前退出,再次进入结束后退出界面,再次进入重新开始倒计时bug + +受到uni-app内置text样式影响修复 + +## 3.2.15(2024-04-28) +优化时间选择器hasInput模式初始化值 +## 3.2.14(2024-04-24) +去除pleaseSetTranspileDependencies + +http采用useStore + +## 3.2.13(2024-04-22) +修复modal标题样式 + +优化日期选择器hasInput模式宽度 + +## 3.2.12(2024-04-22) +修复color应用 +## 3.2.11(2024-04-18) +修复import化带来的问题 +## 3.2.10(2024-04-17) +完善input清空事件App端失效的兼容性 + +修复日历组件二次打开后当前月份显示不正确 + +## 3.2.9(2024-04-16) +组件内uni.$u用法改为import引入 + +规范化及兼容性增强 + +## 3.2.8(2024-04-15) +修复up-tag语法错 +## 3.2.7(2024-04-15) +修复下拉菜单背景色在支付宝小程序无效 + +setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置 + +## 3.2.6(2024-04-14) +修复某些情况下滑动单元格默认右侧按钮是展开的问题 +## 3.2.5(2024-04-13) +调整分段器尺寸及修复窗口大小改变时重新计算尺寸 + +多个组件支持cursor-pointer增强PC端体验 + +## 3.2.4(2024-04-12) +初步支持typescript +## 3.2.3(2024-04-12) +fix: 修复square属性在小程序下无效问题 + +fix:修复lastIndex异常导致的column异常问题 + +fix: alipayapp picker style + +feat(button): 添加用户同意隐私协议事件回调 + +fix: input switch password + +fix: 修复u-code组件keepRuning失效问题 + +feat: form-item添加labelPosition属性 + +新增dropdown组件 + +分段器支持内部current值 + +优化cell和action-sheet视觉大小 + +修复tabs文字换行 + +## 3.2.2(2024-04-11) +修复换行符问题 +## 3.2.1(2024-04-11) +修复演示H5二维码 + +fix: #270 ReadMore 展开阅读更多内容变化兼容 + +fix: #238Calendar组件maxDate修改为不能小于minDate + +checkbox支持独立使用 + +修复popup中在微信小程序中真机调试滚动失效 + +## 3.2.0(2024-04-10) +修复轮播图在nvue显示 +修复疑似u-slider名称被占用导致slider在App下不显示 +解决微信小程序提示 Some selectors are not allowed in component wxss +示例中u-前缀统一为up- +增加瀑布流与图片懒加载组件 +fix: #308修复tag组件缺失iconColor参数 +fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost +## 3.1.52(2024-04-07) +工具类方法调用import化改造 +新增up-copy复制组件 +## 3.1.51(2024-04-07) +优化时间选择器自带输入框格式化显示 +防止按钮文字换行 +修复订单列表模板滑动 +增加u-qrcode二维码组件 +## 3.1.49(2024-03-27) +日期时间组件支持自带输入框 +fix: popup弹窗滚动穿透问题 +fix: 修复小程序numberbox bug +## 3.1.48(2024-03-18) +fix:[plugin:uni:pre-css] Unbalanced delimiter found in string +## 3.1.47(2024-03-18) +fix: setConfig设置组件默认参数无效问题 +fix: 修复自定义图标无效问题 +feat: 增加u-form-item单独设置规则变量 +fix:#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况 + +## 3.1.46(2024-01-29) +beforeUnmount +## 3.1.45(2024-01-24) +fix: #262ext组件为超链接的情况下size属性不生效 +fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错 +fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错 +fix: #263支持支付宝小程序 +fix: #261u-input在直接修改v-model的绑定值时,每隔一次会无法出发change事件 +优化折叠面板兼容微信小程序 +## 3.1.42(2024-01-15) +修复u-number-box默认值0时在小程序不显示值 +优化u-code的timer判断 +优化支付宝小程序下textarea字数统计兼容 +优化u-calendar +## 3.1.41(2023-11-18) +#215优化u-cell图标容器间距问题 +## 3.1.40(2023-11-16) +修复u-slider双向绑定 +## 3.1.39(2023-11-10) +修复头条小程序不支持env(safe-area-inset-bottom) +优化#201u-grid 指定列数导致闪烁 +#193IndexList 索引列表 高度错误 +其他优化 +## 3.1.38(2023-10-08) +修复u-slider +## 3.1.37(2023-09-13) +完善emits定义及修复code-input双向数据绑定 +## 3.1.36(2023-08-08) +修复富文本事件名称大小写 +## 3.1.35(2023-08-02) +修复编译到支付宝小程序u-form报错 +## 3.1.34(2023-07-27) +修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错 +## 3.1.33(2023-07-13) +修复弹窗进入动画、模板页面样式等 +## 3.1.31(2023-07-11) +修复dayjs引用 +## 3.0.8(2022-07-12) +修复u-tag默认宽度撑满容器 +## 3.0.7(2022-07-12) +修复u-navbar自定义插槽演示示例 +## 3.0.6(2022-07-11) +修复u-image缺少emits申明 +## 3.0.5(2022-07-11) +修复u-upload缺少emits申明 +## 3.0.4(2022-07-10) +修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定 +## 3.0.3(2022-07-09) +启用自建演示二维码 +## 3.0.2(2022-07-09) +修复dayjs/clipboard等导致打包报错 +## 3.0.1(2022-07-09) +增加插件市场地址 +## 3.0.0(2022-07-09) +# uview-plus(vue3)初步发布 diff --git a/im-uniapp/uni_modules/uview-plus/components/u--form/u--form.vue b/im-uniapp/uni_modules/uview-plus/components/u--form/u--form.vue new file mode 100644 index 0000000..78e9dc4 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u--form/u--form.vue @@ -0,0 +1,80 @@ + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u--image/u--image.vue b/im-uniapp/uni_modules/uview-plus/components/u--image/u--image.vue new file mode 100644 index 0000000..5916065 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u--image/u--image.vue @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/components/u--input/u--input.vue b/im-uniapp/uni_modules/uview-plus/components/u--input/u--input.vue new file mode 100644 index 0000000..2a8933f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u--input/u--input.vue @@ -0,0 +1,74 @@ + + + \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/components/u--text/u--text.vue b/im-uniapp/uni_modules/uview-plus/components/u--text/u--text.vue new file mode 100644 index 0000000..9cdcf7b --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u--text/u--text.vue @@ -0,0 +1,45 @@ + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u--textarea/u--textarea.vue b/im-uniapp/uni_modules/uview-plus/components/u--textarea/u--textarea.vue new file mode 100644 index 0000000..becd23d --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u--textarea/u--textarea.vue @@ -0,0 +1,47 @@ + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/props.js b/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/props.js new file mode 100644 index 0000000..2f365f0 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/props.js @@ -0,0 +1,57 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 操作菜单是否展示 (默认false) + show: { + type: Boolean, + default: () => defProps.actionSheet.show + }, + // 标题 + title: { + type: String, + default: () => defProps.actionSheet.title + }, + // 选项上方的描述信息 + description: { + type: String, + default: () => defProps.actionSheet.description + }, + // 数据 + actions: { + type: Array, + default: () => defProps.actionSheet.actions + }, + // 取消按钮的文字,不为空时显示按钮 + cancelText: { + type: String, + default: () => defProps.actionSheet.cancelText + }, + // 点击某个菜单项时是否关闭弹窗 + closeOnClickAction: { + type: Boolean, + default: () => defProps.actionSheet.closeOnClickAction + }, + // 处理底部安全区(默认true) + safeAreaInsetBottom: { + type: Boolean, + default: () => defProps.actionSheet.safeAreaInsetBottom + }, + // 小程序的打开方式 + openType: { + type: String, + default: () => defProps.actionSheet.openType + }, + // 点击遮罩是否允许关闭 (默认true) + closeOnClickOverlay: { + type: Boolean, + default: () => defProps.actionSheet.closeOnClickOverlay + }, + // 圆角值 + round: { + type: [Boolean, String, Number], + default: () => defProps.actionSheet.round + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue b/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue new file mode 100644 index 0000000..3c28340 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue @@ -0,0 +1,281 @@ + + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-album/props.js b/im-uniapp/uni_modules/uview-plus/components/u-album/props.js new file mode 100644 index 0000000..eb0f181 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-album/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 图片地址,Array|Array形式 + urls: { + type: Array, + default: () => defProps.album.urls + }, + // 指定从数组的对象元素中读取哪个属性作为图片地址 + keyName: { + type: String, + default: () => defProps.album.keyName + }, + // 单图时,图片长边的长度 + singleSize: { + type: [String, Number], + default: () => defProps.album.singleSize + }, + // 多图时,图片边长 + multipleSize: { + type: [String, Number], + default: () => defProps.album.multipleSize + }, + // 多图时,图片水平和垂直之间的间隔 + space: { + type: [String, Number], + default: () => defProps.album.space + }, + // 单图时,图片缩放裁剪的模式 + singleMode: { + type: String, + default: () => defProps.album.singleMode + }, + // 多图时,图片缩放裁剪的模式 + multipleMode: { + type: String, + default: () => defProps.album.multipleMode + }, + // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量 + maxCount: { + type: [String, Number], + default: () => defProps.album.maxCount + }, + // 是否可以预览图片 + previewFullImage: { + type: Boolean, + default: () => defProps.album.previewFullImage + }, + // 每行展示图片数量,如设置,singleSize和multipleSize将会无效 + rowCount: { + type: [String, Number], + default: () => defProps.album.rowCount + }, + // 超出maxCount时是否显示查看更多的提示 + showMore: { + type: Boolean, + default: () => defProps.album.showMore + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-album/u-album.vue b/im-uniapp/uni_modules/uview-plus/components/u-album/u-album.vue new file mode 100644 index 0000000..4904019 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-album/u-album.vue @@ -0,0 +1,264 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/components/u-alert/props.js b/im-uniapp/uni_modules/uview-plus/components/u-alert/props.js new file mode 100644 index 0000000..9f150b5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-alert/props.js @@ -0,0 +1,46 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 显示文字 + title: { + type: String, + default: () => defProps.alert.title + }, + // 主题,success/warning/info/error + type: { + type: String, + default: () => defProps.alert.type + }, + // 辅助性文字 + description: { + type: String, + default: () => defProps.alert.description + }, + // 是否可关闭 + closable: { + type: Boolean, + default: () => defProps.alert.closable + }, + // 是否显示图标 + showIcon: { + type: Boolean, + default: () => defProps.alert.showIcon + }, + // 浅或深色调,light-浅色,dark-深色 + effect: { + type: String, + default: () => defProps.alert.effect + }, + // 文字是否居中 + center: { + type: Boolean, + default: () => defProps.alert.center + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.alert.fontSize + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-alert/u-alert.vue b/im-uniapp/uni_modules/uview-plus/components/u-alert/u-alert.vue new file mode 100644 index 0000000..dea66b1 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-alert/u-alert.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/props.js b/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/props.js new file mode 100644 index 0000000..96ed871 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/props.js @@ -0,0 +1,54 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 头像图片组 + urls: { + type: Array, + default: () => defProps.avatarGroup.urls + }, + // 最多展示的头像数量 + maxCount: { + type: [String, Number], + default: () => defProps.avatarGroup.maxCount + }, + // 头像形状 + shape: { + type: String, + default: () => defProps.avatarGroup.shape + }, + // 图片裁剪模式 + mode: { + type: String, + default: () => defProps.avatarGroup.mode + }, + // 超出maxCount时是否显示查看更多的提示 + showMore: { + type: Boolean, + default: () => defProps.avatarGroup.showMore + }, + // 头像大小 + size: { + type: [String, Number], + default: () => defProps.avatarGroup.size + }, + // 指定从数组的对象元素中读取哪个属性作为图片地址 + keyName: { + type: String, + default: () => defProps.avatarGroup.keyName + }, + // 头像之间的遮挡比例 + gap: { + type: [String, Number], + validator(value) { + return value >= 0 && value <= 1 + }, + default: () => defProps.avatarGroup.gap + }, + // 需额外显示的值 + extraValue: { + type: [Number, String], + default: () => defProps.avatarGroup.extraValue + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue b/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue new file mode 100644 index 0000000..fdfeaad --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-avatar/props.js b/im-uniapp/uni_modules/uview-plus/components/u-avatar/props.js new file mode 100644 index 0000000..8fa15ac --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-avatar/props.js @@ -0,0 +1,81 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +import test from '../../libs/function/test'; +export const props = defineMixin({ + props: { + // 头像图片路径(不能为相对路径) + src: { + type: String, + default: () => defProps.avatar.src + }, + // 头像形状,circle-圆形,square-方形 + shape: { + type: String, + default: () => defProps.avatar.shape + }, + // 头像尺寸 + size: { + type: [String, Number], + default: () => defProps.avatar.size + }, + // 裁剪模式 + mode: { + type: String, + default: () => defProps.avatar.mode + }, + // 显示的文字 + text: { + type: String, + default: () => defProps.avatar.text + }, + // 背景色 + bgColor: { + type: String, + default: () => defProps.avatar.bgColor + }, + // 文字颜色 + color: { + type: String, + default: () => defProps.avatar.color + }, + // 文字大小 + fontSize: { + type: [String, Number], + default: () => defProps.avatar.fontSize + }, + // 显示的图标 + icon: { + type: String, + default: () => defProps.avatar.icon + }, + // 显示小程序头像,只对百度,微信,QQ小程序有效 + mpAvatar: { + type: Boolean, + default: () => defProps.avatar.mpAvatar + }, + // 是否使用随机背景色 + randomBgColor: { + type: Boolean, + default: () => defProps.avatar.randomBgColor + }, + // 加载失败的默认头像(组件有内置默认图片) + defaultUrl: { + type: String, + default: () => defProps.avatar.defaultUrl + }, + // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 + colorIndex: { + type: [String, Number], + // 校验参数规则,索引在0-19之间 + validator(n) { + return test.range(n, [0, 19]) || n === '' + }, + default: () => defProps.avatar.colorIndex + }, + // 组件标识符 + name: { + type: String, + default: () => defProps.avatar.name + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-avatar/u-avatar.vue b/im-uniapp/uni_modules/uview-plus/components/u-avatar/u-avatar.vue new file mode 100644 index 0000000..71c0b22 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-avatar/u-avatar.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-back-top/props.js b/im-uniapp/uni_modules/uview-plus/components/u-back-top/props.js new file mode 100644 index 0000000..b714641 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-back-top/props.js @@ -0,0 +1,56 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 返回顶部的形状,circle-圆形,square-方形 + mode: { + type: String, + default: () => defProps.backtop.mode + }, + // 自定义图标 + icon: { + type: String, + default: () => defProps.backtop.icon + }, + // 提示文字 + text: { + type: String, + default: () => defProps.backtop.text + }, + // 返回顶部滚动时间 + duration: { + type: [String, Number], + default: () => defProps.backtop.duration + }, + // 滚动距离 + scrollTop: { + type: [String, Number], + default: () => defProps.backtop.scrollTop + }, + // 距离顶部多少距离显示,单位px + top: { + type: [String, Number], + default: () => defProps.backtop.top + }, + // 返回顶部按钮到底部的距离,单位px + bottom: { + type: [String, Number], + default: () => defProps.backtop.bottom + }, + // 返回顶部按钮到右边的距离,单位px + right: { + type: [String, Number], + default: () => defProps.backtop.right + }, + // 层级 + zIndex: { + type: [String, Number], + default: () => defProps.backtop.zIndex + }, + // 图标的样式,对象形式 + iconStyle: { + type: Object, + default: () => defProps.backtop.iconStyle + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-back-top/u-back-top.vue b/im-uniapp/uni_modules/uview-plus/components/u-back-top/u-back-top.vue new file mode 100644 index 0000000..fcdb045 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-back-top/u-back-top.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-badge/props.js b/im-uniapp/uni_modules/uview-plus/components/u-badge/props.js new file mode 100644 index 0000000..5ed924b --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-badge/props.js @@ -0,0 +1,79 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否显示圆点 + isDot: { + type: Boolean, + default: () => defProps.badge.isDot + }, + // 显示的内容 + value: { + type: [Number, String], + default: () => defProps.badge.value + }, + // 显示的内容 + modelValue: { + type: [Number, String], + default: () => defProps.badge.modelValue + }, + // 是否显示 + show: { + type: Boolean, + default: () => defProps.badge.show + }, + // 最大值,超过最大值会显示 '{max}+' + max: { + type: [Number, String], + default: () => defProps.badge.max + }, + // 主题类型,error|warning|success|primary + type: { + type: String, + default: () => defProps.badge.type + }, + // 当数值为 0 时,是否展示 Badge + showZero: { + type: Boolean, + default: () => defProps.badge.showZero + }, + // 背景颜色,优先级比type高,如设置,type参数会失效 + bgColor: { + type: [String, null], + default: () => defProps.badge.bgColor + }, + // 字体颜色 + color: { + type: [String, null], + default: () => defProps.badge.color + }, + // 徽标形状,circle-四角均为圆角,horn-左下角为直角 + shape: { + type: String, + default: () => defProps.badge.shape + }, + // 设置数字的显示方式,overflow|ellipsis|limit + // overflow会根据max字段判断,超出显示`${max}+` + // ellipsis会根据max判断,超出显示`${max}...` + // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数 + numberType: { + type: String, + default: () => defProps.badge.numberType + }, + // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 + offset: { + type: Array, + default: () => defProps.badge.offset + }, + // 是否反转背景和字体颜色 + inverted: { + type: Boolean, + default: () => defProps.badge.inverted + }, + // 是否绝对定位 + absolute: { + type: Boolean, + default: () => defProps.badge.absolute + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-badge/u-badge.vue b/im-uniapp/uni_modules/uview-plus/components/u-badge/u-badge.vue new file mode 100644 index 0000000..ecc843f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-badge/u-badge.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-button/nvue.scss b/im-uniapp/uni_modules/uview-plus/components/u-button/nvue.scss new file mode 100644 index 0000000..490db7d --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-button/nvue.scss @@ -0,0 +1,46 @@ +$u-button-active-opacity:0.75 !default; +$u-button-loading-text-margin-left:4px !default; +$u-button-text-color: #FFFFFF !default; +$u-button-text-plain-error-color:$u-error !default; +$u-button-text-plain-warning-color:$u-warning !default; +$u-button-text-plain-success-color:$u-success !default; +$u-button-text-plain-info-color:$u-info !default; +$u-button-text-plain-primary-color:$u-primary !default; +.u-button { + &--active { + opacity: $u-button-active-opacity; + } + + &--active--plain { + background-color: rgb(217, 217, 217); + } + + &__loading-text { + margin-left:$u-button-loading-text-margin-left; + } + + &__text, + &__loading-text { + color:$u-button-text-color; + } + + &__text--plain--error { + color:$u-button-text-plain-error-color; + } + + &__text--plain--warning { + color:$u-button-text-plain-warning-color; + } + + &__text--plain--success{ + color:$u-button-text-plain-success-color; + } + + &__text--plain--info { + color:$u-button-text-plain-info-color; + } + + &__text--plain--primary { + color:$u-button-text-plain-primary-color; + } +} \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/components/u-button/props.js b/im-uniapp/uni_modules/uview-plus/components/u-button/props.js new file mode 100644 index 0000000..d01f0ab --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-button/props.js @@ -0,0 +1,154 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否细边框 + hairline: { + type: Boolean, + default: () => defProps.button.hairline + }, + // 按钮的预置样式,info,primary,error,warning,success + type: { + type: String, + default: () => defProps.button.type + }, + // 按钮尺寸,large,normal,small,mini + size: { + type: String, + default: () => defProps.button.size + }, + // 按钮形状,circle(两边为半圆),square(带圆角) + shape: { + type: String, + default: () => defProps.button.shape + }, + // 按钮是否镂空 + plain: { + type: Boolean, + default: () => defProps.button.plain + }, + // 是否禁止状态 + disabled: { + type: Boolean, + default: () => defProps.button.disabled + }, + // 是否加载中 + loading: { + type: Boolean, + default: () => defProps.button.loading + }, + // 加载中提示文字 + loadingText: { + type: [String, Number], + default: () => defProps.button.loadingText + }, + // 加载状态图标类型 + loadingMode: { + type: String, + default: () => defProps.button.loadingMode + }, + // 加载图标大小 + loadingSize: { + type: [String, Number], + default: () => defProps.button.loadingSize + }, + // 开放能力,具体请看uniapp稳定关于button组件部分说明 + // https://uniapp.dcloud.io/component/button + openType: { + type: String, + default: () => defProps.button.openType + }, + // 用于 组件,点击分别会触发 组件的 submit/reset 事件 + // 取值为submit(提交表单),reset(重置表单) + formType: { + type: String, + default: () => defProps.button.formType + }, + // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 + // 只微信小程序、QQ小程序有效 + appParameter: { + type: String, + default: () => defProps.button.appParameter + }, + // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 + hoverStopPropagation: { + type: Boolean, + default: () => defProps.button.hoverStopPropagation + }, + // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 + lang: { + type: String, + default: () => defProps.button.lang + }, + // 会话来源,open-type="contact"时有效。只微信小程序有效 + sessionFrom: { + type: String, + default: () => defProps.button.sessionFrom + }, + // 会话内消息卡片标题,open-type="contact"时有效 + // 默认当前标题,只微信小程序有效 + sendMessageTitle: { + type: String, + default: () => defProps.button.sendMessageTitle + }, + // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 + // 默认当前分享路径,只微信小程序有效 + sendMessagePath: { + type: String, + default: () => defProps.button.sendMessagePath + }, + // 会话内消息卡片图片,open-type="contact"时有效 + // 默认当前页面截图,只微信小程序有效 + sendMessageImg: { + type: String, + default: () => defProps.button.sendMessageImg + }, + // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, + // 用户点击后可以快速发送小程序消息,open-type="contact"时有效 + showMessageCard: { + type: Boolean, + default: () => defProps.button.showMessageCard + }, + // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 + dataName: { + type: String, + default: () => defProps.button.dataName + }, + // 节流,一定时间内只能触发一次 + throttleTime: { + type: [String, Number], + default: () => defProps.button.throttleTime + }, + // 按住后多久出现点击态,单位毫秒 + hoverStartTime: { + type: [String, Number], + default: () => defProps.button.hoverStartTime + }, + // 手指松开后点击态保留时间,单位毫秒 + hoverStayTime: { + type: [String, Number], + default: () => defProps.button.hoverStayTime + }, + // 按钮文字,之所以通过props传入,是因为slot传入的话 + // nvue中无法控制文字的样式 + text: { + type: [String, Number], + default: () => defProps.button.text + }, + // 按钮图标 + icon: { + type: String, + default: () => defProps.button.icon + }, + // 按钮图标 + iconColor: { + type: String, + default: () => defProps.button.icon + }, + // 按钮颜色,支持传入linear-gradient渐变色 + color: { + type: String, + default: () => defProps.button.color + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-button/u-button.vue b/im-uniapp/uni_modules/uview-plus/components/u-button/u-button.vue new file mode 100644 index 0000000..77f5568 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-button/u-button.vue @@ -0,0 +1,503 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-button/vue.scss b/im-uniapp/uni_modules/uview-plus/components/u-button/vue.scss new file mode 100644 index 0000000..ceb6cea --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-button/vue.scss @@ -0,0 +1,81 @@ +// nvue下hover-class无效 +$u-button-before-top:50% !default; +$u-button-before-left:50% !default; +$u-button-before-width:100% !default; +$u-button-before-height:100% !default; +$u-button-before-transform:translate(-50%, -50%) !default; +$u-button-before-opacity:0 !default; +$u-button-before-background-color:#000 !default; +$u-button-before-border-color:#000 !default; +$u-button-active-before-opacity:.15 !default; +$u-button-icon-margin-left:4px !default; +$u-button-plain-u-button-info-color:$u-info; +$u-button-plain-u-button-success-color:$u-success; +$u-button-plain-u-button-error-color:$u-error; +$u-button-plain-u-button-warning-color:$u-error; + +.u-button { + width: 100%; + white-space: nowrap; + + &__text { + white-space: nowrap; + line-height: 1; + } + + &:before { + position: absolute; + top:$u-button-before-top; + left:$u-button-before-left; + width:$u-button-before-width; + height:$u-button-before-height; + border: inherit; + border-radius: inherit; + transform:$u-button-before-transform; + opacity:$u-button-before-opacity; + content: " "; + background-color:$u-button-before-background-color; + border-color:$u-button-before-border-color; + } + + &--active { + &:before { + opacity: .15 + } + } + + &__icon+&__text:not(:empty), + &__loading-text { + margin-left:$u-button-icon-margin-left; + } + + &--plain { + &.u-button--primary { + color: $u-primary; + } + } + + &--plain { + &.u-button--info { + color:$u-button-plain-u-button-info-color; + } + } + + &--plain { + &.u-button--success { + color:$u-button-plain-u-button-success-color; + } + } + + &--plain { + &.u-button--error { + color:$u-button-plain-u-button-error-color; + } + } + + &--plain { + &.u-button--warning { + color:$u-button-plain-u-button-warning-color; + } + } +} diff --git a/im-uniapp/uni_modules/uview-plus/components/u-calendar/header.vue b/im-uniapp/uni_modules/uview-plus/components/u-calendar/header.vue new file mode 100644 index 0000000..ccfe18b --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-calendar/header.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-calendar/month.vue b/im-uniapp/uni_modules/uview-plus/components/u-calendar/month.vue new file mode 100644 index 0000000..6062388 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-calendar/month.vue @@ -0,0 +1,585 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-calendar/props.js b/im-uniapp/uni_modules/uview-plus/components/u-calendar/props.js new file mode 100644 index 0000000..df990fd --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-calendar/props.js @@ -0,0 +1,147 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 日历顶部标题 + title: { + type: String, + default: () => defProps.calendar.title + }, + // 是否显示标题 + showTitle: { + type: Boolean, + default: () => defProps.calendar.showTitle + }, + // 是否显示副标题 + showSubtitle: { + type: Boolean, + default: () => defProps.calendar.showSubtitle + }, + // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 + mode: { + type: String, + default: () => defProps.calendar.mode + }, + // mode=range时,第一个日期底部的提示文字 + startText: { + type: String, + default: () => defProps.calendar.startText + }, + // mode=range时,最后一个日期底部的提示文字 + endText: { + type: String, + default: () => defProps.calendar.endText + }, + // 自定义列表 + customList: { + type: Array, + default: () => defProps.calendar.customList + }, + // 主题色,对底部按钮和选中日期有效 + color: { + type: String, + default: () => defProps.calendar.color + }, + // 最小的可选日期 + minDate: { + type: [String, Number], + default: () => defProps.calendar.minDate + }, + // 最大可选日期 + maxDate: { + type: [String, Number], + default: () => defProps.calendar.maxDate + }, + // 默认选中的日期,mode为multiple或range是必须为数组格式 + defaultDate: { + type: [Array, String, Date, null], + default: () => defProps.calendar.defaultDate + }, + // mode=multiple时,最多可选多少个日期 + maxCount: { + type: [String, Number], + default: () => defProps.calendar.maxCount + }, + // 日期行高 + rowHeight: { + type: [String, Number], + default: () => defProps.calendar.rowHeight + }, + // 日期格式化函数 + formatter: { + type: [Function, null], + default: () => defProps.calendar.formatter + }, + // 是否显示农历 + showLunar: { + type: Boolean, + default: () => defProps.calendar.showLunar + }, + // 是否显示月份背景色 + showMark: { + type: Boolean, + default: () => defProps.calendar.showMark + }, + // 确定按钮的文字 + confirmText: { + type: String, + default: () => defProps.calendar.confirmText + }, + // 确认按钮处于禁用状态时的文字 + confirmDisabledText: { + type: String, + default: () => defProps.calendar.confirmDisabledText + }, + // 是否显示日历弹窗 + show: { + type: Boolean, + default: () => defProps.calendar.show + }, + // 是否允许点击遮罩关闭日历 + closeOnClickOverlay: { + type: Boolean, + default: () => defProps.calendar.closeOnClickOverlay + }, + // 是否为只读状态,只读状态下禁止选择日期 + readonly: { + type: Boolean, + default: () => defProps.calendar.readonly + }, + // 是否展示确认按钮 + showConfirm: { + type: Boolean, + default: () => defProps.calendar.showConfirm + }, + // 日期区间最多可选天数,默认无限制,mode = range时有效 + maxRange: { + type: [Number, String], + default: () => defProps.calendar.maxRange + }, + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 + rangePrompt: { + type: String, + default: () => defProps.calendar.rangePrompt + }, + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 + showRangePrompt: { + type: Boolean, + default: () => defProps.calendar.showRangePrompt + }, + // 是否允许日期范围的起止时间为同一天,mode = range时有效 + allowSameDay: { + type: Boolean, + default: () => defProps.calendar.allowSameDay + }, + // 圆角值 + round: { + type: [Boolean, String, Number], + default: () => defProps.calendar.round + }, + // 最多展示月份数量 + monthNum: { + type: [Number, String], + default: 3 + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-calendar/u-calendar.vue b/im-uniapp/uni_modules/uview-plus/components/u-calendar/u-calendar.vue new file mode 100644 index 0000000..c8f77ff --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-calendar/u-calendar.vue @@ -0,0 +1,409 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-calendar/util.js b/im-uniapp/uni_modules/uview-plus/components/u-calendar/util.js new file mode 100644 index 0000000..18762c1 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-calendar/util.js @@ -0,0 +1,86 @@ +import dayjs from 'dayjs/esm/index' +export default { + methods: { + // 设置月份数据 + setMonth() { + // 月初是周几 + const day = dayjs(this.date).date(1).day() + const start = day == 0 ? 6 : day - 1 + + // 本月天数 + const days = dayjs(this.date).endOf('month').format('D') + + // 上个月天数 + const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D') + + // 日期数据 + const arr = [] + // 清空表格 + this.month = [] + + // 添加上月数据 + arr.push( + ...new Array(start).fill(1).map((e, i) => { + const day = prevDays - start + i + 1 + + return { + value: day, + disabled: true, + date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD') + } + }) + ) + + // 添加本月数据 + arr.push( + ...new Array(days - 0).fill(1).map((e, i) => { + const day = i + 1 + + return { + value: day, + date: dayjs(this.date).date(day).format('YYYY-MM-DD') + } + }) + ) + + // 添加下个月 + arr.push( + ...new Array(42 - days - start).fill(1).map((e, i) => { + const day = i + 1 + + return { + value: day, + disabled: true, + date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD') + } + }) + ) + + // 分割数组 + for (let n = 0; n < arr.length; n += 7) { + this.month.push( + arr.slice(n, n + 7).map((e, i) => { + e.index = i + n + + // 自定义信息 + const custom = this.customList.find((c) => c.date == e.date) + + // 农历 + if (this.lunar) { + const { + IDayCn, + IMonthCn + } = this.getLunar(e.date) + e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn + } + + return { + ...e, + ...custom + } + }) + ) + } + } + } +} diff --git a/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/props.js b/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/props.js new file mode 100644 index 0000000..7cca0ae --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/props.js @@ -0,0 +1,17 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: false + }, + // 输入一个中文后,是否自动切换到英文 + autoChange: { + type: Boolean, + default: false + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue b/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue new file mode 100644 index 0000000..347885e --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-car-keyboard/u-car-keyboard.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-cell-group/props.js b/im-uniapp/uni_modules/uview-plus/components/u-cell-group/props.js new file mode 100644 index 0000000..59c2012 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-cell-group/props.js @@ -0,0 +1,16 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 分组标题 + title: { + type: String, + default: () => defProps.cellGroup.title + }, + // 是否显示外边框 + border: { + type: Boolean, + default: () => defProps.cellGroup.border + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue b/im-uniapp/uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue new file mode 100644 index 0000000..40ed2dd --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-cell-group/u-cell-group.vue @@ -0,0 +1,67 @@ + + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-cell/props.js b/im-uniapp/uni_modules/uview-plus/components/u-cell/props.js new file mode 100644 index 0000000..73f5210 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-cell/props.js @@ -0,0 +1,112 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 标题 + title: { + type: [String, Number], + default: () => defProps.cell.title + }, + // 标题下方的描述信息 + label: { + type: [String, Number], + default: () => defProps.cell.label + }, + // 右侧的内容 + value: { + type: [String, Number], + default: () => defProps.cell.value + }, + // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址) + icon: { + type: String, + default: () => defProps.cell.icon + }, + // 是否禁用cell + disabled: { + type: Boolean, + default: () => defProps.cell.disabled + }, + // 是否显示下边框 + border: { + type: Boolean, + default: () => defProps.cell.border + }, + // 内容是否垂直居中(主要是针对右侧的value部分) + center: { + type: Boolean, + default: () => defProps.cell.center + }, + // 点击后跳转的URL地址 + url: { + type: String, + default: () => defProps.cell.url + }, + // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 + linkType: { + type: String, + default: () => defProps.cell.linkType + }, + // 是否开启点击反馈(表现为点击时加上灰色背景) + clickable: { + type: Boolean, + default: () => defProps.cell.clickable + }, + // 是否展示右侧箭头并开启点击反馈 + isLink: { + type: Boolean, + default: () => defProps.cell.isLink + }, + // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) + required: { + type: Boolean, + default: () => defProps.cell.required + }, + // 右侧的图标箭头 + rightIcon: { + type: String, + default: () => defProps.cell.rightIcon + }, + // 右侧箭头的方向,可选值为:left,up,down + arrowDirection: { + type: String, + default: () => defProps.cell.arrowDirection + }, + // 左侧图标样式 + iconStyle: { + type: [Object, String], + default: () => { + return defProps.cell.iconStyle + } + }, + // 右侧箭头图标的样式 + rightIconStyle: { + type: [Object, String], + default: () => { + return defProps.cell.rightIconStyle + } + }, + // 标题的样式 + titleStyle: { + type: [Object, String], + default: () => { + return defProps.cell.titleStyle + } + }, + // 单位元的大小,可选值为large + size: { + type: String, + default: () => defProps.cell.size + }, + // 点击cell是否阻止事件传播 + stop: { + type: Boolean, + default: () => defProps.cell.stop + }, + // 标识符,cell被点击时返回 + name: { + type: [Number, String], + default: () => defProps.cell.name + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-cell/u-cell.vue b/im-uniapp/uni_modules/uview-plus/components/u-cell/u-cell.vue new file mode 100644 index 0000000..66f48be --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-cell/u-cell.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/props.js b/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/props.js new file mode 100644 index 0000000..222d184 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/props.js @@ -0,0 +1,93 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 标识符 + name: { + type: String, + default: () => defProps.checkboxGroup.name + }, + // #ifdef VUE3 + // 绑定的值 + modelValue: { + type: Array, + default: () => defProps.checkboxGroup.value + }, + // #endif + // #ifdef VUE2 + // 绑定的值 + value: { + type: Array, + default: () => defProps.checkboxGroup.value + }, + // #endif + // 形状,circle-圆形,square-方形 + shape: { + type: String, + default: () => defProps.checkboxGroup.shape + }, + // 是否禁用全部checkbox + disabled: { + type: Boolean, + default: () => defProps.checkboxGroup.disabled + }, + + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: () => defProps.checkboxGroup.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: () => defProps.checkboxGroup.inactiveColor + }, + + // 整个组件的尺寸,默认px + size: { + type: [String, Number], + default: () => defProps.checkboxGroup.size + }, + // 布局方式,row-横向,column-纵向 + placement: { + type: String, + default: () => defProps.checkboxGroup.placement + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: () => defProps.checkboxGroup.labelSize + }, + // label的字体颜色 + labelColor: { + type: [String], + default: () => defProps.checkboxGroup.labelColor + }, + // 是否禁止点击文本操作 + labelDisabled: { + type: Boolean, + default: () => defProps.checkboxGroup.labelDisabled + }, + // 图标颜色 + iconColor: { + type: String, + default: () => defProps.checkboxGroup.iconColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: () => defProps.checkboxGroup.iconSize + }, + // 勾选图标的对齐方式,left-左边,right-右边 + iconPlacement: { + type: String, + default: () => defProps.checkboxGroup.iconPlacement + }, + // 竖向配列时,是否显示下划线 + borderBottom: { + type: Boolean, + default: () => defProps.checkboxGroup.borderBottom + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue b/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue new file mode 100644 index 0000000..0640d85 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-checkbox/props.js b/im-uniapp/uni_modules/uview-plus/components/u-checkbox/props.js new file mode 100644 index 0000000..66d8150 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-checkbox/props.js @@ -0,0 +1,76 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // checkbox的名称 + name: { + type: [String, Number, Boolean], + default: () => defProps.checkbox.name + }, + // 形状,square为方形,circle为圆型 + shape: { + type: String, + default: () => defProps.checkbox.shape + }, + // 整体的大小 + size: { + type: [String, Number], + default: () => defProps.checkbox.size + }, + // 是否默认选中 + checked: { + type: Boolean, + default: () => defProps.checkbox.checked + }, + // 是否禁用 + disabled: { + type: [String, Boolean], + default: () => defProps.checkbox.disabled + }, + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: () => defProps.checkbox.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: () => defProps.checkbox.inactiveColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: () => defProps.checkbox.iconSize + }, + // 图标颜色 + iconColor: { + type: String, + default: () => defProps.checkbox.iconColor + }, + // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 + label: { + type: [String, Number], + default: () => defProps.checkbox.label + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: () => defProps.checkbox.labelSize + }, + // label的颜色 + labelColor: { + type: String, + default: () => defProps.checkbox.labelColor + }, + // 是否禁止点击提示语选中复选框 + labelDisabled: { + type: [String, Boolean], + default: () => defProps.checkbox.labelDisabled + }, + // 是否独立使用 + usedAlone: { + type: [Boolean], + default: () => false + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue b/im-uniapp/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue new file mode 100644 index 0000000..0abee51 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/props.js b/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/props.js new file mode 100644 index 0000000..ff1eeaa --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/props.js @@ -0,0 +1,10 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + percentage: { + type: [String, Number], + default: () => defProps.circleProgress.percentage + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue b/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue new file mode 100644 index 0000000..6dd5c93 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-code-input/props.js b/im-uniapp/uni_modules/uview-plus/components/u-code-input/props.js new file mode 100644 index 0000000..0a4b1e8 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-code-input/props.js @@ -0,0 +1,90 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 键盘弹起时,是否自动上推页面 + adjustPosition: { + type: Boolean, + default: () => defProps.codeInput.adjustPosition + }, + // 最大输入长度 + maxlength: { + type: [String, Number], + default: () => defProps.codeInput.maxlength + }, + // 是否用圆点填充 + dot: { + type: Boolean, + default: () => defProps.codeInput.dot + }, + // 显示模式,box-盒子模式,line-底部横线模式 + mode: { + type: String, + default: () => defProps.codeInput.mode + }, + // 是否细边框 + hairline: { + type: Boolean, + default: () => defProps.codeInput.hairline + }, + // 字符间的距离 + space: { + type: [String, Number], + default: () => defProps.codeInput.space + }, + // #ifdef VUE3 + // 预置值 + modelValue: { + type: [String, Number], + default: () => defProps.codeInput.value + }, + // #endif + // #ifdef VUE2 + // 预置值 + value: { + type: [String, Number], + default: () => defProps.codeInput.value + }, + // #endif + // 是否自动获取焦点 + focus: { + type: Boolean, + default: () => defProps.codeInput.focus + }, + // 字体是否加粗 + bold: { + type: Boolean, + default: () => defProps.codeInput.bold + }, + // 字体颜色 + color: { + type: String, + default: () => defProps.codeInput.color + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.codeInput.fontSize + }, + // 输入框的大小,宽等于高 + size: { + type: [String, Number], + default: () => defProps.codeInput.size + }, + // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true + disabledKeyboard: { + type: Boolean, + default: () => defProps.codeInput.disabledKeyboard + }, + // 边框和线条颜色 + borderColor: { + type: String, + default: () => defProps.codeInput.borderColor + }, + // 是否禁止输入"."符号 + disabledDot: { + type: Boolean, + default: () => defProps.codeInput.disabledDot + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-code-input/u-code-input.vue b/im-uniapp/uni_modules/uview-plus/components/u-code-input/u-code-input.vue new file mode 100644 index 0000000..9504dda --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-code-input/u-code-input.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-code/props.js b/im-uniapp/uni_modules/uview-plus/components/u-code/props.js new file mode 100644 index 0000000..60977b5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-code/props.js @@ -0,0 +1,36 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 倒计时总秒数 + seconds: { + type: [String, Number], + default: () => defProps.code.seconds + }, + // 尚未开始时提示 + startText: { + type: String, + default: () => defProps.code.startText + }, + // 正在倒计时中的提示 + changeText: { + type: String, + default: () => defProps.code.changeText + }, + // 倒计时结束时的提示 + endText: { + type: String, + default: () => defProps.code.endText + }, + // 是否在H5刷新或各端返回再进入时继续倒计时 + keepRunning: { + type: Boolean, + default: () => defProps.code.keepRunning + }, + // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 + uniqueKey: { + type: String, + default: () => defProps.code.uniqueKey + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-code/u-code.vue b/im-uniapp/uni_modules/uview-plus/components/u-code/u-code.vue new file mode 100644 index 0000000..c0fc992 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-code/u-code.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-col/props.js b/im-uniapp/uni_modules/uview-plus/components/u-col/props.js new file mode 100644 index 0000000..b28decb --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-col/props.js @@ -0,0 +1,31 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 占父容器宽度的多少等分,总分为12份 + span: { + type: [String, Number], + default: () => defProps.col.span + }, + // 指定栅格左侧的间隔数(总12栏) + offset: { + type: [String, Number], + default: () => defProps.col.offset + }, + // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) + justify: { + type: String, + default: () => defProps.col.justify + }, + // 垂直对齐方式,可选值为top、center、bottom、stretch + align: { + type: String, + default: () => defProps.col.align + }, + // 文字对齐方式 + textAlign: { + type: String, + default: () => defProps.col.textAlign + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-col/u-col.vue b/im-uniapp/uni_modules/uview-plus/components/u-col/u-col.vue new file mode 100644 index 0000000..57a12d6 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-col/u-col.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/props.js new file mode 100644 index 0000000..fdc923b --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 标题 + title: { + type: String, + default: () => defProps.collapseItem.title + }, + // 标题右侧内容 + value: { + type: String, + default: () => defProps.collapseItem.value + }, + // 标题下方的描述信息 + label: { + type: String, + default: () => defProps.collapseItem.label + }, + // 是否禁用折叠面板 + disabled: { + type: Boolean, + default: () => defProps.collapseItem.disabled + }, + // 是否展示右侧箭头并开启点击反馈 + isLink: { + type: Boolean, + default: () => defProps.collapseItem.isLink + }, + // 是否开启点击反馈 + clickable: { + type: Boolean, + default: () => defProps.collapseItem.clickable + }, + // 是否显示内边框 + border: { + type: Boolean, + default: () => defProps.collapseItem.border + }, + // 标题的对齐方式 + align: { + type: String, + default: () => defProps.collapseItem.align + }, + // 唯一标识符 + name: { + type: [String, Number], + default: () => defProps.collapseItem.name + }, + // 标题左侧图片,可为绝对路径的图片或内置图标 + icon: { + type: String, + default: () => defProps.collapseItem.icon + }, + // 面板展开收起的过渡时间,单位ms + duration: { + type: Number, + default: () => defProps.collapseItem.duration + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue new file mode 100644 index 0000000..7fa4b69 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-collapse/props.js b/im-uniapp/uni_modules/uview-plus/components/u-collapse/props.js new file mode 100644 index 0000000..7712603 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-collapse/props.js @@ -0,0 +1,21 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 当前展开面板的name,非手风琴模式:[],手风琴模式:string | number + value: { + type: [String, Number, Array, null], + default: () => defProps.collapse.value + }, + // 是否手风琴模式 + accordion: { + type: Boolean, + default: () => defProps.collapse.accordion + }, + // 是否显示外边框 + border: { + type: Boolean, + default: () => defProps.collapse.border + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-collapse/u-collapse.vue b/im-uniapp/uni_modules/uview-plus/components/u-collapse/u-collapse.vue new file mode 100644 index 0000000..f7ec7f7 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-collapse/u-collapse.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-column-notice/props.js b/im-uniapp/uni_modules/uview-plus/components/u-column-notice/props.js new file mode 100644 index 0000000..ae1a335 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-column-notice/props.js @@ -0,0 +1,57 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 显示的内容,字符串 + text: { + type: [Array], + default: () => defProps.columnNotice.text + }, + // 是否显示左侧的音量图标 + icon: { + type: String, + default: () => defProps.columnNotice.icon + }, + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 + mode: { + type: String, + default: () => defProps.columnNotice.mode + }, + // 文字颜色,各图标也会使用文字颜色 + color: { + type: String, + default: () => defProps.columnNotice.color + }, + // 背景颜色 + bgColor: { + type: String, + default: () => defProps.columnNotice.bgColor + }, + // 字体大小,单位px + fontSize: { + type: [String, Number], + default: () => defProps.columnNotice.fontSize + }, + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 + speed: { + type: [String, Number], + default: () => defProps.columnNotice.speed + }, + // direction = row时,是否使用步进形式滚动 + step: { + type: Boolean, + default: () => defProps.columnNotice.step + }, + // 滚动一个周期的时间长,单位ms + duration: { + type: [String, Number], + default: () => defProps.columnNotice.duration + }, + // 是否禁止用手滑动切换 + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 + disableTouch: { + type: Boolean, + default: () => defProps.columnNotice.disableTouch + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue b/im-uniapp/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue new file mode 100644 index 0000000..5e2507f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-copy/u-copy.vue b/im-uniapp/uni_modules/uview-plus/components/u-copy/u-copy.vue new file mode 100644 index 0000000..7e63ded --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-copy/u-copy.vue @@ -0,0 +1,70 @@ + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-count-down/props.js b/im-uniapp/uni_modules/uview-plus/components/u-count-down/props.js new file mode 100644 index 0000000..6dbf1c2 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-count-down/props.js @@ -0,0 +1,26 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 倒计时时长,单位ms + time: { + type: [String, Number], + default: () => defProps.countDown.time + }, + // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 + format: { + type: String, + default: () => defProps.countDown.format + }, + // 是否自动开始倒计时 + autoStart: { + type: Boolean, + default: () => defProps.countDown.autoStart + }, + // 是否展示毫秒倒计时 + millisecond: { + type: Boolean, + default: () => defProps.countDown.millisecond + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-count-down/u-count-down.vue b/im-uniapp/uni_modules/uview-plus/components/u-count-down/u-count-down.vue new file mode 100644 index 0000000..5720757 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-count-down/u-count-down.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-count-down/utils.js b/im-uniapp/uni_modules/uview-plus/components/u-count-down/utils.js new file mode 100644 index 0000000..8c75005 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-count-down/utils.js @@ -0,0 +1,62 @@ +// 补0,如1 -> 01 +function padZero(num, targetLength = 2) { + let str = `${num}` + while (str.length < targetLength) { + str = `0${str}` + } + return str +} +const SECOND = 1000 +const MINUTE = 60 * SECOND +const HOUR = 60 * MINUTE +const DAY = 24 * HOUR +export function parseTimeData(time) { + const days = Math.floor(time / DAY) + const hours = Math.floor((time % DAY) / HOUR) + const minutes = Math.floor((time % HOUR) / MINUTE) + const seconds = Math.floor((time % MINUTE) / SECOND) + const milliseconds = Math.floor(time % SECOND) + return { + days, + hours, + minutes, + seconds, + milliseconds + } +} +export function parseFormat(format, timeData) { + let { + days, + hours, + minutes, + seconds, + milliseconds + } = timeData + // 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去 + if (format.indexOf('DD') === -1) { + hours += days * 24 + } else { + // 对天补0 + format = format.replace('DD', padZero(days)) + } + // 其他同理于DD的格式化处理方式 + if (format.indexOf('HH') === -1) { + minutes += hours * 60 + } else { + format = format.replace('HH', padZero(hours)) + } + if (format.indexOf('mm') === -1) { + seconds += minutes * 60 + } else { + format = format.replace('mm', padZero(minutes)) + } + if (format.indexOf('ss') === -1) { + milliseconds += seconds * 1000 + } else { + format = format.replace('ss', padZero(seconds)) + } + return format.replace('SSS', padZero(milliseconds, 3)) +} +export function isSameSecond(time1, time2) { + return Math.floor(time1 / 1000) === Math.floor(time2 / 1000) +} diff --git a/im-uniapp/uni_modules/uview-plus/components/u-count-to/props.js b/im-uniapp/uni_modules/uview-plus/components/u-count-to/props.js new file mode 100644 index 0000000..0b97c1f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-count-to/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 开始的数值,默认从0增长到某一个数 + startVal: { + type: [String, Number], + default: () => defProps.countTo.startVal + }, + // 要滚动的目标数值,必须 + endVal: { + type: [String, Number], + default: () => defProps.countTo.endVal + }, + // 滚动到目标数值的动画持续时间,单位为毫秒(ms) + duration: { + type: [String, Number], + default: () => defProps.countTo.duration + }, + // 设置数值后是否自动开始滚动 + autoplay: { + type: Boolean, + default: () => defProps.countTo.autoplay + }, + // 要显示的小数位数 + decimals: { + type: [String, Number], + default: () => defProps.countTo.decimals + }, + // 是否在即将到达目标数值的时候,使用缓慢滚动的效果 + useEasing: { + type: Boolean, + default: () => defProps.countTo.useEasing + }, + // 十进制分割 + decimal: { + type: [String, Number], + default: () => defProps.countTo.decimal + }, + // 字体颜色 + color: { + type: String, + default: () => defProps.countTo.color + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.countTo.fontSize + }, + // 是否加粗字体 + bold: { + type: Boolean, + default: () => defProps.countTo.bold + }, + // 千位分隔符,类似金额的分割(¥23,321.05中的",") + separator: { + type: String, + default: () => defProps.countTo.separator + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-count-to/u-count-to.vue b/im-uniapp/uni_modules/uview-plus/components/u-count-to/u-count-to.vue new file mode 100644 index 0000000..83ab13d --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-count-to/u-count-to.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/props.js b/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/props.js new file mode 100644 index 0000000..97c6e3a --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/props.js @@ -0,0 +1,145 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否显示input + hasInput: { + type: Boolean, + default: () => false + }, + placeholder: { + type: String, + default: () => '请选择' + }, + format: { + type: String, + default: () => '' + }, + // 是否打开组件 + show: { + type: Boolean, + default: () => defProps.datetimePicker.show + }, + // 弹出的方向,可选值为 top bottom right left center + popupMode: { + type: String, + default: () => defProps.picker.popupMode + }, + // 是否展示顶部的操作栏 + showToolbar: { + type: Boolean, + default: () => defProps.datetimePicker.showToolbar + }, + // #ifdef VUE2 + // 绑定值 + value: { + type: [String, Number], + default: () => defProps.datetimePicker.value + }, + // #endif + // #ifdef VUE3 + // 绑定值 + modelValue: { + type: [String, Number], + default: () => defProps.datetimePicker.value + }, + // #endif + // 顶部标题 + title: { + type: String, + default: () => defProps.datetimePicker.title + }, + // 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择 + mode: { + type: String, + default: () => defProps.datetimePicker.mode + }, + // 可选的最大时间 + maxDate: { + type: Number, + // 最大默认值为后10年 + default: () => defProps.datetimePicker.maxDate + }, + // 可选的最小时间 + minDate: { + type: Number, + // 最小默认值为前10年 + default: () => defProps.datetimePicker.minDate + }, + // 可选的最小小时,仅mode=time有效 + minHour: { + type: Number, + default: () => defProps.datetimePicker.minHour + }, + // 可选的最大小时,仅mode=time有效 + maxHour: { + type: Number, + default: () => defProps.datetimePicker.maxHour + }, + // 可选的最小分钟,仅mode=time有效 + minMinute: { + type: Number, + default: () => defProps.datetimePicker.minMinute + }, + // 可选的最大分钟,仅mode=time有效 + maxMinute: { + type: Number, + default: () => defProps.datetimePicker.maxMinute + }, + // 选项过滤函数 + filter: { + type: [Function, null], + default: () => defProps.datetimePicker.filter + }, + // 选项格式化函数 + formatter: { + type: [Function, null], + default: () => defProps.datetimePicker.formatter + }, + // 是否显示加载中状态 + loading: { + type: Boolean, + default: () => defProps.datetimePicker.loading + }, + // 各列中,单个选项的高度 + itemHeight: { + type: [String, Number], + default: () => defProps.datetimePicker.itemHeight + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: () => defProps.datetimePicker.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: () => defProps.datetimePicker.confirmText + }, + // 取消按钮的颜色 + cancelColor: { + type: String, + default: () => defProps.datetimePicker.cancelColor + }, + // 确认按钮的颜色 + confirmColor: { + type: String, + default: () => defProps.datetimePicker.confirmColor + }, + // 每列中可见选项的数量 + visibleItemCount: { + type: [String, Number], + default: () => defProps.datetimePicker.visibleItemCount + }, + // 是否允许点击遮罩关闭选择器 + closeOnClickOverlay: { + type: Boolean, + default: () => defProps.datetimePicker.closeOnClickOverlay + }, + // 各列的默认索引 + defaultIndex: { + type: Array, + default: () => defProps.datetimePicker.defaultIndex + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue b/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue new file mode 100644 index 0000000..ca33e86 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue @@ -0,0 +1,455 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-divider/props.js b/im-uniapp/uni_modules/uview-plus/components/u-divider/props.js new file mode 100644 index 0000000..ec3fd8f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-divider/props.js @@ -0,0 +1,46 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否虚线 + dashed: { + type: Boolean, + default: () => defProps.divider.dashed + }, + // 是否细线 + hairline: { + type: Boolean, + default: () => defProps.divider.hairline + }, + // 是否以点替代文字,优先于text字段起作用 + dot: { + type: Boolean, + default: () => defProps.divider.dot + }, + // 内容文本的位置,left-左边,center-中间,right-右边 + textPosition: { + type: String, + default: () => defProps.divider.textPosition + }, + // 文本内容 + text: { + type: [String, Number], + default: () => defProps.divider.text + }, + // 文本大小 + textSize: { + type: [String, Number], + default: () => defProps.divider.textSize + }, + // 文本颜色 + textColor: { + type: String, + default: () => defProps.divider.textColor + }, + // 线条颜色 + lineColor: { + type: String, + default: () => defProps.divider.lineColor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-divider/u-divider.vue b/im-uniapp/uni_modules/uview-plus/components/u-divider/u-divider.vue new file mode 100644 index 0000000..47dbf00 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-divider/u-divider.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/props.js new file mode 100644 index 0000000..e5e0265 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/props.js @@ -0,0 +1,47 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // #ifdef VUE3 + // 当前选中项的value值 + modelValue: { + type: [Number, String, Array], + default: '' + }, + // #endif + // #ifdef VUE2 + // 当前选中项的value值 + value: { + type: [Number, String, Array], + default: '' + }, + // #endif + // 菜单项标题 + title: { + type: [String, Number], + default: '' + }, + // 选项数据,如果传入了默认slot,此参数无效 + options: { + type: Array, + default() { + return [] + } + }, + // 是否禁用此菜单项 + disabled: { + type: Boolean, + default: false + }, + // 下拉弹窗的高度 + height: { + type: [Number, String], + default: 'auto' + }, + // 点击遮罩是否可以收起弹窗 + closeOnClickOverlay: { + type: Boolean, + default: true + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/u-dropdown-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/u-dropdown-item.vue new file mode 100644 index 0000000..ec15a06 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-dropdown-item/u-dropdown-item.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-dropdown/props.js b/im-uniapp/uni_modules/uview-plus/components/u-dropdown/props.js new file mode 100644 index 0000000..4614b96 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-dropdown/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 菜单标题和选项的激活态颜色 + activeColor: { + type: String, + default: '#2979ff' + }, + // 菜单标题和选项的未激活态颜色 + inactiveColor: { + type: String, + default: '#606266' + }, + // 点击遮罩是否关闭菜单 + closeOnClickMask: { + type: Boolean, + default: true + }, + // 点击当前激活项标题是否关闭菜单 + closeOnClickSelf: { + type: Boolean, + default: true + }, + // 过渡时间 + duration: { + type: [Number, String], + default: 300 + }, + // 标题菜单的高度 + height: { + type: [Number, String], + default: 40 + }, + // 是否显示下边框 + borderBottom: { + type: Boolean, + default: false + }, + // 标题的字体大小 + titleSize: { + type: [Number, String], + default: 14 + }, + // 下拉出来的内容部分的圆角值 + borderRadius: { + type: [Number, String], + default: 0 + }, + // 菜单右侧的icon图标 + menuIcon: { + type: String, + default: 'arrow-down' + }, + // 菜单右侧图标的大小 + menuIconSize: { + type: [Number, String], + default: 14 + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue b/im-uniapp/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue new file mode 100644 index 0000000..46479a3 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-empty/props.js b/im-uniapp/uni_modules/uview-plus/components/u-empty/props.js new file mode 100644 index 0000000..40edd11 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-empty/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 内置图标名称,或图片路径,建议绝对路径 + icon: { + type: String, + default: () => defProps.empty.icon + }, + // 提示文字 + text: { + type: String, + default: () => defProps.empty.text + }, + // 文字颜色 + textColor: { + type: String, + default: () => defProps.empty.textColor + }, + // 文字大小 + textSize: { + type: [String, Number], + default: () => defProps.empty.textSize + }, + // 图标的颜色 + iconColor: { + type: String, + default: () => defProps.empty.iconColor + }, + // 图标的大小 + iconSize: { + type: [String, Number], + default: () => defProps.empty.iconSize + }, + // 选择预置的图标类型 + mode: { + type: String, + default: () => defProps.empty.mode + }, + // 图标宽度,单位px + width: { + type: [String, Number], + default: () => defProps.empty.width + }, + // 图标高度,单位px + height: { + type: [String, Number], + default: () => defProps.empty.height + }, + // 是否显示组件 + show: { + type: Boolean, + default: () => defProps.empty.show + }, + // 组件距离上一个元素之间的距离,默认px单位 + marginTop: { + type: [String, Number], + default: () => defProps.empty.marginTop + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-empty/u-empty.vue b/im-uniapp/uni_modules/uview-plus/components/u-empty/u-empty.vue new file mode 100644 index 0000000..f83677f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-empty/u-empty.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-form-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-form-item/props.js new file mode 100644 index 0000000..9402d37 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-form-item/props.js @@ -0,0 +1,55 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // input的label提示语 + label: { + type: String, + default: () => defProps.formItem.label + }, + // 绑定的值 + prop: { + type: String, + default: () => defProps.formItem.prop + }, + // 绑定的规则 + rule: { + type: String, + default: () => defProps.formItem.rule + }, + // 是否显示表单域的下划线边框 + borderBottom: { + type: [String, Boolean], + default: () => defProps.formItem.borderBottom + }, + // label的位置,left-左边,top-上边 + labelPosition: { + type: String, + default: () => defProps.formItem.labelPosition + }, + // label的宽度,单位px + labelWidth: { + type: [String, Number], + default: () => defProps.formItem.labelWidth + }, + // 右侧图标 + rightIcon: { + type: String, + default: () => defProps.formItem.rightIcon + }, + // 左侧图标 + leftIcon: { + type: String, + default: () => defProps.formItem.leftIcon + }, + // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 + required: { + type: Boolean, + default: () => defProps.formItem.required + }, + leftIconStyle: { + type: [String, Object], + default: () => defProps.formItem.leftIconStyle, + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-form-item/u-form-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-form-item/u-form-item.vue new file mode 100644 index 0000000..1151814 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-form-item/u-form-item.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-form/props.js b/im-uniapp/uni_modules/uview-plus/components/u-form/props.js new file mode 100644 index 0000000..b80d623 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-form/props.js @@ -0,0 +1,47 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 当前form的需要验证字段的集合 + model: { + type: Object, + default: () => defProps.form.model + }, + // 验证规则 + rules: { + type: [Object, Function, Array], + default: () => defProps.form.rules + }, + // 有错误时的提示方式,message-提示信息,toast-进行toast提示 + // border-bottom-下边框呈现红色,none-无提示 + errorType: { + type: String, + default: () => defProps.form.errorType + }, + // 是否显示表单域的下划线边框 + borderBottom: { + type: Boolean, + default: () => defProps.form.borderBottom + }, + // label的位置,left-左边,top-上边 + labelPosition: { + type: String, + default: () => defProps.form.labelPosition + }, + // label的宽度,单位px + labelWidth: { + type: [String, Number], + default: () => defProps.form.labelWidth + }, + // lable字体的对齐方式 + labelAlign: { + type: String, + default: () => defProps.form.labelAlign + }, + // lable的样式,对象形式 + labelStyle: { + type: Object, + default: () => defProps.form.labelStyle + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-form/u-form.vue b/im-uniapp/uni_modules/uview-plus/components/u-form/u-form.vue new file mode 100644 index 0000000..373d1ff --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-form/u-form.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-gap/props.js b/im-uniapp/uni_modules/uview-plus/components/u-gap/props.js new file mode 100644 index 0000000..77b1d7c --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-gap/props.js @@ -0,0 +1,26 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 背景颜色(默认transparent) + bgColor: { + type: String, + default: () => defProps.gap.bgColor + }, + // 分割槽高度,单位px(默认30) + height: { + type: [String, Number], + default: () => defProps.gap.height + }, + // 与上一个组件的距离 + marginTop: { + type: [String, Number], + default: () => defProps.gap.marginTop + }, + // 与下一个组件的距离 + marginBottom: { + type: [String, Number], + default: () => defProps.gap.marginBottom + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-gap/u-gap.vue b/im-uniapp/uni_modules/uview-plus/components/u-gap/u-gap.vue new file mode 100644 index 0000000..36863a5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-gap/u-gap.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-grid-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-grid-item/props.js new file mode 100644 index 0000000..55f957c --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-grid-item/props.js @@ -0,0 +1,16 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 宫格的name + name: { + type: [String, Number, null], + default: () => defProps.gridItem.name + }, + // 背景颜色 + bgColor: { + type: String, + default: () => defProps.gridItem.bgColor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue new file mode 100644 index 0000000..89197aa --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-grid/props.js b/im-uniapp/uni_modules/uview-plus/components/u-grid/props.js new file mode 100644 index 0000000..3d99844 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-grid/props.js @@ -0,0 +1,21 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 分成几列 + col: { + type: [String, Number], + default: () => defProps.grid.col + }, + // 是否显示边框 + border: { + type: Boolean, + default: () => defProps.grid.border + }, + // 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 + align: { + type: String, + default: () => defProps.grid.align + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-grid/u-grid.vue b/im-uniapp/uni_modules/uview-plus/components/u-grid/u-grid.vue new file mode 100644 index 0000000..a650504 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-grid/u-grid.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-icon/icons.js b/im-uniapp/uni_modules/uview-plus/components/u-icon/icons.js new file mode 100644 index 0000000..f4d0fe2 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-icon/icons.js @@ -0,0 +1,214 @@ +export default { + 'uicon-level': '\ue693', + 'uicon-column-line': '\ue68e', + 'uicon-checkbox-mark': '\ue807', + 'uicon-folder': '\ue7f5', + 'uicon-movie': '\ue7f6', + 'uicon-star-fill': '\ue669', + 'uicon-star': '\ue65f', + 'uicon-phone-fill': '\ue64f', + 'uicon-phone': '\ue622', + 'uicon-apple-fill': '\ue881', + 'uicon-chrome-circle-fill': '\ue885', + 'uicon-backspace': '\ue67b', + 'uicon-attach': '\ue632', + 'uicon-cut': '\ue948', + 'uicon-empty-car': '\ue602', + 'uicon-empty-coupon': '\ue682', + 'uicon-empty-address': '\ue646', + 'uicon-empty-favor': '\ue67c', + 'uicon-empty-permission': '\ue686', + 'uicon-empty-news': '\ue687', + 'uicon-empty-search': '\ue664', + 'uicon-github-circle-fill': '\ue887', + 'uicon-rmb': '\ue608', + 'uicon-person-delete-fill': '\ue66a', + 'uicon-reload': '\ue788', + 'uicon-order': '\ue68f', + 'uicon-server-man': '\ue6bc', + 'uicon-search': '\ue62a', + 'uicon-fingerprint': '\ue955', + 'uicon-more-dot-fill': '\ue630', + 'uicon-scan': '\ue662', + 'uicon-share-square': '\ue60b', + 'uicon-map': '\ue61d', + 'uicon-map-fill': '\ue64e', + 'uicon-tags': '\ue629', + 'uicon-tags-fill': '\ue651', + 'uicon-bookmark-fill': '\ue63b', + 'uicon-bookmark': '\ue60a', + 'uicon-eye': '\ue613', + 'uicon-eye-fill': '\ue641', + 'uicon-mic': '\ue64a', + 'uicon-mic-off': '\ue649', + 'uicon-calendar': '\ue66e', + 'uicon-calendar-fill': '\ue634', + 'uicon-trash': '\ue623', + 'uicon-trash-fill': '\ue658', + 'uicon-play-left': '\ue66d', + 'uicon-play-right': '\ue610', + 'uicon-minus': '\ue618', + 'uicon-plus': '\ue62d', + 'uicon-info': '\ue653', + 'uicon-info-circle': '\ue7d2', + 'uicon-info-circle-fill': '\ue64b', + 'uicon-question': '\ue715', + 'uicon-error': '\ue6d3', + 'uicon-close': '\ue685', + 'uicon-checkmark': '\ue6a8', + 'uicon-android-circle-fill': '\ue67e', + 'uicon-android-fill': '\ue67d', + 'uicon-ie': '\ue87b', + 'uicon-IE-circle-fill': '\ue889', + 'uicon-google': '\ue87a', + 'uicon-google-circle-fill': '\ue88a', + 'uicon-setting-fill': '\ue872', + 'uicon-setting': '\ue61f', + 'uicon-minus-square-fill': '\ue855', + 'uicon-plus-square-fill': '\ue856', + 'uicon-heart': '\ue7df', + 'uicon-heart-fill': '\ue851', + 'uicon-camera': '\ue7d7', + 'uicon-camera-fill': '\ue870', + 'uicon-more-circle': '\ue63e', + 'uicon-more-circle-fill': '\ue645', + 'uicon-chat': '\ue620', + 'uicon-chat-fill': '\ue61e', + 'uicon-bag-fill': '\ue617', + 'uicon-bag': '\ue619', + 'uicon-error-circle-fill': '\ue62c', + 'uicon-error-circle': '\ue624', + 'uicon-close-circle': '\ue63f', + 'uicon-close-circle-fill': '\ue637', + 'uicon-checkmark-circle': '\ue63d', + 'uicon-checkmark-circle-fill': '\ue635', + 'uicon-question-circle-fill': '\ue666', + 'uicon-question-circle': '\ue625', + 'uicon-share': '\ue631', + 'uicon-share-fill': '\ue65e', + 'uicon-shopping-cart': '\ue621', + 'uicon-shopping-cart-fill': '\ue65d', + 'uicon-bell': '\ue609', + 'uicon-bell-fill': '\ue640', + 'uicon-list': '\ue650', + 'uicon-list-dot': '\ue616', + 'uicon-zhihu': '\ue6ba', + 'uicon-zhihu-circle-fill': '\ue709', + 'uicon-zhifubao': '\ue6b9', + 'uicon-zhifubao-circle-fill': '\ue6b8', + 'uicon-weixin-circle-fill': '\ue6b1', + 'uicon-weixin-fill': '\ue6b2', + 'uicon-twitter-circle-fill': '\ue6ab', + 'uicon-twitter': '\ue6aa', + 'uicon-taobao-circle-fill': '\ue6a7', + 'uicon-taobao': '\ue6a6', + 'uicon-weibo-circle-fill': '\ue6a5', + 'uicon-weibo': '\ue6a4', + 'uicon-qq-fill': '\ue6a1', + 'uicon-qq-circle-fill': '\ue6a0', + 'uicon-moments-circel-fill': '\ue69a', + 'uicon-moments': '\ue69b', + 'uicon-qzone': '\ue695', + 'uicon-qzone-circle-fill': '\ue696', + 'uicon-baidu-circle-fill': '\ue680', + 'uicon-baidu': '\ue681', + 'uicon-facebook-circle-fill': '\ue68a', + 'uicon-facebook': '\ue689', + 'uicon-car': '\ue60c', + 'uicon-car-fill': '\ue636', + 'uicon-warning-fill': '\ue64d', + 'uicon-warning': '\ue694', + 'uicon-clock-fill': '\ue638', + 'uicon-clock': '\ue60f', + 'uicon-edit-pen': '\ue612', + 'uicon-edit-pen-fill': '\ue66b', + 'uicon-email': '\ue611', + 'uicon-email-fill': '\ue642', + 'uicon-minus-circle': '\ue61b', + 'uicon-minus-circle-fill': '\ue652', + 'uicon-plus-circle': '\ue62e', + 'uicon-plus-circle-fill': '\ue661', + 'uicon-file-text': '\ue663', + 'uicon-file-text-fill': '\ue665', + 'uicon-pushpin': '\ue7e3', + 'uicon-pushpin-fill': '\ue86e', + 'uicon-grid': '\ue673', + 'uicon-grid-fill': '\ue678', + 'uicon-play-circle': '\ue647', + 'uicon-play-circle-fill': '\ue655', + 'uicon-pause-circle-fill': '\ue654', + 'uicon-pause': '\ue8fa', + 'uicon-pause-circle': '\ue643', + 'uicon-eye-off': '\ue648', + 'uicon-eye-off-outline': '\ue62b', + 'uicon-gift-fill': '\ue65c', + 'uicon-gift': '\ue65b', + 'uicon-rmb-circle-fill': '\ue657', + 'uicon-rmb-circle': '\ue677', + 'uicon-kefu-ermai': '\ue656', + 'uicon-server-fill': '\ue751', + 'uicon-coupon-fill': '\ue8c4', + 'uicon-coupon': '\ue8ae', + 'uicon-integral': '\ue704', + 'uicon-integral-fill': '\ue703', + 'uicon-home-fill': '\ue964', + 'uicon-home': '\ue965', + 'uicon-hourglass-half-fill': '\ue966', + 'uicon-hourglass': '\ue967', + 'uicon-account': '\ue628', + 'uicon-plus-people-fill': '\ue626', + 'uicon-minus-people-fill': '\ue615', + 'uicon-account-fill': '\ue614', + 'uicon-thumb-down-fill': '\ue726', + 'uicon-thumb-down': '\ue727', + 'uicon-thumb-up': '\ue733', + 'uicon-thumb-up-fill': '\ue72f', + 'uicon-lock-fill': '\ue979', + 'uicon-lock-open': '\ue973', + 'uicon-lock-opened-fill': '\ue974', + 'uicon-lock': '\ue97a', + 'uicon-red-packet-fill': '\ue690', + 'uicon-photo-fill': '\ue98b', + 'uicon-photo': '\ue98d', + 'uicon-volume-off-fill': '\ue659', + 'uicon-volume-off': '\ue644', + 'uicon-volume-fill': '\ue670', + 'uicon-volume': '\ue633', + 'uicon-red-packet': '\ue691', + 'uicon-download': '\ue63c', + 'uicon-arrow-up-fill': '\ue6b0', + 'uicon-arrow-down-fill': '\ue600', + 'uicon-play-left-fill': '\ue675', + 'uicon-play-right-fill': '\ue676', + 'uicon-rewind-left-fill': '\ue679', + 'uicon-rewind-right-fill': '\ue67a', + 'uicon-arrow-downward': '\ue604', + 'uicon-arrow-leftward': '\ue601', + 'uicon-arrow-rightward': '\ue603', + 'uicon-arrow-upward': '\ue607', + 'uicon-arrow-down': '\ue60d', + 'uicon-arrow-right': '\ue605', + 'uicon-arrow-left': '\ue60e', + 'uicon-arrow-up': '\ue606', + 'uicon-skip-back-left': '\ue674', + 'uicon-skip-forward-right': '\ue672', + 'uicon-rewind-right': '\ue66f', + 'uicon-rewind-left': '\ue671', + 'uicon-arrow-right-double': '\ue68d', + 'uicon-arrow-left-double': '\ue68c', + 'uicon-wifi-off': '\ue668', + 'uicon-wifi': '\ue667', + 'uicon-empty-data': '\ue62f', + 'uicon-empty-history': '\ue684', + 'uicon-empty-list': '\ue68b', + 'uicon-empty-page': '\ue627', + 'uicon-empty-order': '\ue639', + 'uicon-man': '\ue697', + 'uicon-woman': '\ue69c', + 'uicon-man-add': '\ue61c', + 'uicon-man-add-fill': '\ue64c', + 'uicon-man-delete': '\ue61a', + 'uicon-man-delete-fill': '\ue66a', + 'uicon-zh': '\ue70a', + 'uicon-en': '\ue692' +} diff --git a/im-uniapp/uni_modules/uview-plus/components/u-icon/props.js b/im-uniapp/uni_modules/uview-plus/components/u-icon/props.js new file mode 100644 index 0000000..d5bdfdb --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-icon/props.js @@ -0,0 +1,91 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 图标类名 + name: { + type: String, + default: () => defProps.icon.name + }, + // 图标颜色,可接受主题色 + color: { + type: String, + default: () => defProps.icon.color + }, + // 字体大小,单位px + size: { + type: [String, Number], + default: () => defProps.icon.size + }, + // 是否显示粗体 + bold: { + type: Boolean, + default: () => defProps.icon.bold + }, + // 点击图标的时候传递事件出去的index(用于区分点击了哪一个) + index: { + type: [String, Number], + default: () => defProps.icon.index + }, + // 触摸图标时的类名 + hoverClass: { + type: String, + default: () => defProps.icon.hoverClass + }, + // 自定义扩展前缀,方便用户扩展自己的图标库 + customPrefix: { + type: String, + default: () => defProps.icon.customPrefix + }, + // 图标右边或者下面的文字 + label: { + type: [String, Number], + default: () => defProps.icon.label + }, + // label的位置,只能右边或者下边 + labelPos: { + type: String, + default: () => defProps.icon.labelPos + }, + // label的大小 + labelSize: { + type: [String, Number], + default: () => defProps.icon.labelSize + }, + // label的颜色 + labelColor: { + type: String, + default: () => defProps.icon.labelColor + }, + // label与图标的距离 + space: { + type: [String, Number], + default: () => defProps.icon.space + }, + // 图片的mode + imgMode: { + type: String, + default: () => defProps.icon.imgMode + }, + // 用于显示图片小图标时,图片的宽度 + width: { + type: [String, Number], + default: () => defProps.icon.width + }, + // 用于显示图片小图标时,图片的高度 + height: { + type: [String, Number], + default: () => defProps.icon.height + }, + // 用于解决某些情况下,让图标垂直居中的用途 + top: { + type: [String, Number], + default: () => defProps.icon.top + }, + // 是否阻止事件传播 + stop: { + type: Boolean, + default: () => defProps.icon.stop + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-icon/u-icon.vue b/im-uniapp/uni_modules/uview-plus/components/u-icon/u-icon.vue new file mode 100644 index 0000000..ce3b52a --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-icon/u-icon.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-image/props.js b/im-uniapp/uni_modules/uview-plus/components/u-image/props.js new file mode 100644 index 0000000..71bc6db --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-image/props.js @@ -0,0 +1,86 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 图片地址 + src: { + type: String, + default: () => defProps.image.src + }, + // 裁剪模式 + mode: { + type: String, + default: () => defProps.image.mode + }, + // 宽度,单位任意 + width: { + type: [String, Number], + default: () => defProps.image.width + }, + // 高度,单位任意 + height: { + type: [String, Number], + default: () => defProps.image.height + }, + // 图片形状,circle-圆形,square-方形 + shape: { + type: String, + default: () => defProps.image.shape + }, + // 圆角,单位任意 + radius: { + type: [String, Number], + default: () => defProps.image.radius + }, + // 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序 + lazyLoad: { + type: Boolean, + default: () => defProps.image.lazyLoad + }, + // 开启长按图片显示识别微信小程序码菜单 + showMenuByLongpress: { + type: Boolean, + default: () => defProps.image.showMenuByLongpress + }, + // 加载中的图标,或者小图片 + loadingIcon: { + type: String, + default: () => defProps.image.loadingIcon + }, + // 加载失败的图标,或者小图片 + errorIcon: { + type: String, + default: () => defProps.image.errorIcon + }, + // 是否显示加载中的图标或者自定义的slot + showLoading: { + type: Boolean, + default: () => defProps.image.showLoading + }, + // 是否显示加载错误的图标或者自定义的slot + showError: { + type: Boolean, + default: () => defProps.image.showError + }, + // 是否需要淡入效果 + fade: { + type: Boolean, + default: () => defProps.image.fade + }, + // 只支持网络资源,只对微信小程序有效 + webp: { + type: Boolean, + default: () => defProps.image.webp + }, + // 过渡时间,单位ms + duration: { + type: [String, Number], + default: () => defProps.image.duration + }, + // 背景颜色,用于深色页面加载图片时,为了和背景色融合 + bgColor: { + type: String, + default: () => defProps.image.bgColor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-image/u-image.vue b/im-uniapp/uni_modules/uview-plus/components/u-image/u-image.vue new file mode 100644 index 0000000..63936f6 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-image/u-image.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/props.js b/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/props.js new file mode 100644 index 0000000..f07e407 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/props.js @@ -0,0 +1,31 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 列表锚点文本内容 + text: { + type: [String, Number], + default: () => defProps.indexAnchor.text + }, + // 列表锚点文字颜色 + color: { + type: String, + default: () => defProps.indexAnchor.color + }, + // 列表锚点文字大小,单位默认px + size: { + type: [String, Number], + default: () => defProps.indexAnchor.size + }, + // 列表锚点背景颜色 + bgColor: { + type: String, + default: () => defProps.indexAnchor.bgColor + }, + // 列表锚点高度,单位默认px + height: { + type: [String, Number], + default: () => defProps.indexAnchor.height + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/u-index-anchor.vue b/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/u-index-anchor.vue new file mode 100644 index 0000000..91c43e9 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-anchor/u-index-anchor.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-index-item/props.js new file mode 100644 index 0000000..3461f1e --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-item/props.js @@ -0,0 +1,8 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-item/u-index-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-index-item/u-index-item.vue new file mode 100644 index 0000000..13cc30f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-item/u-index-item.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-list/props.js b/im-uniapp/uni_modules/uview-plus/components/u-index-list/props.js new file mode 100644 index 0000000..9acd678 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-list/props.js @@ -0,0 +1,31 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 右边锚点非激活的颜色 + inactiveColor: { + type: String, + default: () => defProps.indexList.inactiveColor + }, + // 右边锚点激活的颜色 + activeColor: { + type: String, + default: () => defProps.indexList.activeColor + }, + // 索引字符列表,数组形式 + indexList: { + type: Array, + default: () => defProps.indexList.indexList + }, + // 是否开启锚点自动吸顶 + sticky: { + type: Boolean, + default: () => defProps.indexList.sticky + }, + // 自定义导航栏的高度 + customNavHeight: { + type: [String, Number], + default: () => defProps.indexList.customNavHeight + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-index-list/u-index-list.vue b/im-uniapp/uni_modules/uview-plus/components/u-index-list/u-index-list.vue new file mode 100644 index 0000000..c55eafb --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-index-list/u-index-list.vue @@ -0,0 +1,447 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-input/props.js b/im-uniapp/uni_modules/uview-plus/components/u-input/props.js new file mode 100644 index 0000000..e5e343e --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-input/props.js @@ -0,0 +1,198 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // #ifdef VUE3 + // 绑定的值 + modelValue: { + type: [String, Number], + default: () => defProps.input.value + }, + // #endif + // #ifdef VUE2 + // 绑定的值 + value: { + type: [String, Number], + default: () => defProps.input.value + }, + // #endif + // number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数 + // idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序 + // digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序 + // text-文本输入键盘 + type: { + type: String, + default: () => defProps.input.type + }, + // 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true, + // 兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 + fixed: { + type: Boolean, + default: () => defProps.input.fixed + }, + // 是否禁用输入框 + disabled: { + type: Boolean, + default: () => defProps.input.disabled + }, + // 禁用状态时的背景色 + disabledColor: { + type: String, + default: () => defProps.input.disabledColor + }, + // 是否显示清除控件 + clearable: { + type: Boolean, + default: () => defProps.input.clearable + }, + // 是否密码类型 + password: { + type: Boolean, + default: () => defProps.input.password + }, + // 最大输入长度,设置为 -1 的时候不限制最大长度 + maxlength: { + type: [String, Number], + default: () => defProps.input.maxlength + }, + // 输入框为空时的占位符 + placeholder: { + type: String, + default: () => defProps.input.placeholder + }, + // 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ + placeholderClass: { + type: String, + default: () => defProps.input.placeholderClass + }, + // 指定placeholder的样式 + placeholderStyle: { + type: [String, Object], + default: () => defProps.input.placeholderStyle + }, + // 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 + showWordLimit: { + type: Boolean, + default: () => defProps.input.showWordLimit + }, + // 设置右下角按钮的文字,有效值:send|search|next|go|done,兼容性详见uni-app文档 + // https://uniapp.dcloud.io/component/input + // https://uniapp.dcloud.io/component/textarea + confirmType: { + type: String, + default: () => defProps.input.confirmType + }, + // 点击键盘右下角按钮时是否保持键盘不收起,H5无效 + confirmHold: { + type: Boolean, + default: () => defProps.input.confirmHold + }, + // focus时,点击页面的时候不收起键盘,微信小程序有效 + holdKeyboard: { + type: Boolean, + default: () => defProps.input.holdKeyboard + }, + // 自动获取焦点 + // 在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 + focus: { + type: Boolean, + default: () => defProps.input.focus + }, + // 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 + autoBlur: { + type: Boolean, + default: () => defProps.input.autoBlur + }, + // 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 + disableDefaultPadding: { + type: Boolean, + default: () => defProps.input.disableDefaultPadding + }, + // 指定focus时光标的位置 + cursor: { + type: [String, Number], + default: () => defProps.input.cursor + }, + // 输入框聚焦时底部与键盘的距离 + cursorSpacing: { + type: [String, Number], + default: () => defProps.input.cursorSpacing + }, + // 光标起始位置,自动聚集时有效,需与selection-end搭配使用 + selectionStart: { + type: [String, Number], + default: () => defProps.input.selectionStart + }, + // 光标结束位置,自动聚集时有效,需与selection-start搭配使用 + selectionEnd: { + type: [String, Number], + default: () => defProps.input.selectionEnd + }, + // 键盘弹起时,是否自动上推页面 + adjustPosition: { + type: Boolean, + default: () => defProps.input.adjustPosition + }, + // 输入框内容对齐方式,可选值为:left|center|right + inputAlign: { + type: String, + default: () => defProps.input.inputAlign + }, + // 输入框字体的大小 + fontSize: { + type: [String, Number], + default: () => defProps.input.fontSize + }, + // 输入框字体颜色 + color: { + type: String, + default: () => defProps.input.color + }, + // 输入框前置图标 + prefixIcon: { + type: String, + default: () => defProps.input.prefixIcon + }, + // 前置图标样式,对象或字符串 + prefixIconStyle: { + type: [String, Object], + default: () => defProps.input.prefixIconStyle + }, + // 输入框后置图标 + suffixIcon: { + type: String, + default: () => defProps.input.suffixIcon + }, + // 后置图标样式,对象或字符串 + suffixIconStyle: { + type: [String, Object], + default: () => defProps.input.suffixIconStyle + }, + // 边框类型,surround-四周边框,bottom-底部边框,none-无边框 + border: { + type: String, + default: () => defProps.input.border + }, + // 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 + readonly: { + type: Boolean, + default: () => defProps.input.readonly + }, + // 输入框形状,circle-圆形,square-方形 + shape: { + type: String, + default: () => defProps.input.shape + }, + // 用于处理或者过滤输入框内容的方法 + formatter: { + type: [Function, null], + default: () => defProps.input.formatter + }, + // 是否忽略组件内对文本合成系统事件的处理 + ignoreCompositionEvent: { + type: Boolean, + default: true + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-input/u-input.vue b/im-uniapp/uni_modules/uview-plus/components/u-input/u-input.vue new file mode 100644 index 0000000..c4c18dc --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-input/u-input.vue @@ -0,0 +1,386 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-keyboard/props.js b/im-uniapp/uni_modules/uview-plus/components/u-keyboard/props.js new file mode 100644 index 0000000..8c5d8ae --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-keyboard/props.js @@ -0,0 +1,86 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘 + mode: { + type: String, + default: () => defProps.keyboard.mode + }, + // 是否显示键盘的"."符号 + dotDisabled: { + type: Boolean, + default: () => defProps.keyboard.dotDisabled + }, + // 是否显示顶部工具条 + tooltip: { + type: Boolean, + default: () => defProps.keyboard.tooltip + }, + // 是否显示工具条中间的提示 + showTips: { + type: Boolean, + default: () => defProps.keyboard.showTips + }, + // 工具条中间的提示文字 + tips: { + type: String, + default: () => defProps.keyboard.tips + }, + // 是否显示工具条左边的"取消"按钮 + showCancel: { + type: Boolean, + default: () => defProps.keyboard.showCancel + }, + // 是否显示工具条右边的"完成"按钮 + showConfirm: { + type: Boolean, + default: () => defProps.keyboard.showConfirm + }, + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: () => defProps.keyboard.random + }, + // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 + safeAreaInsetBottom: { + type: Boolean, + default: () => defProps.keyboard.safeAreaInsetBottom + }, + // 是否允许通过点击遮罩关闭键盘 + closeOnClickOverlay: { + type: Boolean, + default: () => defProps.keyboard.closeOnClickOverlay + }, + // 控制键盘的弹出与收起 + show: { + type: Boolean, + default: () => defProps.keyboard.show + }, + // 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩 + overlay: { + type: Boolean, + default: () => defProps.keyboard.overlay + }, + // z-index值 + zIndex: { + type: [String, Number], + default: () => defProps.keyboard.zIndex + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: () => defProps.keyboard.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: () => defProps.keyboard.confirmText + }, + // 输入一个中文后,是否自动切换到英文 + autoChange: { + type: Boolean, + default: () => defProps.keyboard.autoChange + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue b/im-uniapp/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue new file mode 100644 index 0000000..868f0c5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue b/im-uniapp/uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue new file mode 100644 index 0000000..7618781 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-lazy-load/u-lazy-load.vue @@ -0,0 +1,251 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/uni_modules/uview-plus/components/u-line-progress/props.js b/im-uniapp/uni_modules/uview-plus/components/u-line-progress/props.js new file mode 100644 index 0000000..3e03e61 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-line-progress/props.js @@ -0,0 +1,30 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 激活部分的颜色 + activeColor: { + type: String, + default: () => defProps.lineProgress.activeColor + }, + inactiveColor: { + type: String, + default: () => defProps.lineProgress.color + }, + // 进度百分比,数值 + percentage: { + type: [String, Number], + default: () => defProps.lineProgress.inactiveColor + }, + // 是否在进度条内部显示百分比的值 + showText: { + type: Boolean, + default: () => defProps.lineProgress.showText + }, + // 进度条的高度,单位px + height: { + type: [String, Number], + default: () => defProps.lineProgress.height + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue b/im-uniapp/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue new file mode 100644 index 0000000..577f877 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-line/props.js b/im-uniapp/uni_modules/uview-plus/components/u-line/props.js new file mode 100644 index 0000000..6428d3f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-line/props.js @@ -0,0 +1,35 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + color: { + type: String, + default: () => defProps.line.color + }, + // 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 + length: { + type: [String, Number], + default: () => defProps.line.length + }, + // 线条方向,col-竖向,row-横向 + direction: { + type: String, + default: () => defProps.line.direction + }, + // 是否显示细边框 + hairline: { + type: Boolean, + default: () => defProps.line.hairline + }, + // 线条与上下左右元素的间距,字符串形式,如"30px"、"20px 30px" + margin: { + type: [String, Number], + default: () => defProps.line.margin + }, + // 是否虚线,true-虚线,false-实线 + dashed: { + type: Boolean, + default: () => defProps.line.dashed + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-line/u-line.vue b/im-uniapp/uni_modules/uview-plus/components/u-line/u-line.vue new file mode 100644 index 0000000..4acc0a0 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-line/u-line.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-link/props.js b/im-uniapp/uni_modules/uview-plus/components/u-link/props.js new file mode 100644 index 0000000..d96d824 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-link/props.js @@ -0,0 +1,41 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 文字颜色 + color: { + type: String, + default: () => defProps.link.color + }, + // 字体大小,单位px + fontSize: { + type: [String, Number], + default: () => defProps.link.fontSize + }, + // 是否显示下划线 + underLine: { + type: Boolean, + default: () => defProps.link.underLine + }, + // 要跳转的链接 + href: { + type: String, + default: () => defProps.link.href + }, + // 小程序中复制到粘贴板的提示语 + mpTips: { + type: String, + default: () => defProps.link.mpTips + }, + // 下划线颜色 + lineColor: { + type: String, + default: () => defProps.link.lineColor + }, + // 超链接的问题,不使用slot形式传入,是因为nvue下无法修改颜色 + text: { + type: String, + default: () => defProps.link.text + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-link/u-link.vue b/im-uniapp/uni_modules/uview-plus/components/u-link/u-link.vue new file mode 100644 index 0000000..5103893 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-link/u-link.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-list-item/props.js b/im-uniapp/uni_modules/uview-plus/components/u-list-item/props.js new file mode 100644 index 0000000..edaa4dc --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-list-item/props.js @@ -0,0 +1,11 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 用于滚动到指定item + anchor: { + type: [String, Number], + default: () => defProps.listItem.anchor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-list-item/u-list-item.vue b/im-uniapp/uni_modules/uview-plus/components/u-list-item/u-list-item.vue new file mode 100644 index 0000000..ae30b08 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-list-item/u-list-item.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-list/props.js b/im-uniapp/uni_modules/uview-plus/components/u-list/props.js new file mode 100644 index 0000000..9fca829 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-list/props.js @@ -0,0 +1,101 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 控制是否出现滚动条,仅nvue有效 + showScrollbar: { + type: Boolean, + default: () => defProps.list.showScrollbar + }, + // 距底部多少时触发scrolltolower事件 + lowerThreshold: { + type: [String, Number], + default: () => defProps.list.lowerThreshold + }, + // 距顶部多少时触发scrolltoupper事件,非nvue有效 + upperThreshold: { + type: [String, Number], + default: () => defProps.list.upperThreshold + }, + // 设置竖向滚动条位置 + scrollTop: { + type: [String, Number], + default: () => defProps.list.scrollTop + }, + // 控制 onscroll 事件触发的频率,仅nvue有效 + offsetAccuracy: { + type: [String, Number], + default: () => defProps.list.offsetAccuracy + }, + // 启用 flexbox 布局。开启后,当前节点声明了display: flex就会成为flex container,并作用于其孩子节点,仅微信小程序有效 + enableFlex: { + type: Boolean, + default: () => defProps.list.enableFlex + }, + // 是否按分页模式显示List,默认值false + pagingEnabled: { + type: Boolean, + default: () => defProps.list.pagingEnabled + }, + // 是否允许List滚动 + scrollable: { + type: Boolean, + default: () => defProps.list.scrollable + }, + // 值应为某子元素id(id不能以数字开头) + scrollIntoView: { + type: String, + default: () => defProps.list.scrollIntoView + }, + // 在设置滚动条位置时使用动画过渡 + scrollWithAnimation: { + type: Boolean, + default: () => defProps.list.scrollWithAnimation + }, + // iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只对微信小程序有效 + enableBackToTop: { + type: Boolean, + default: () => defProps.list.enableBackToTop + }, + // 列表的高度 + height: { + type: [String, Number], + default: () => defProps.list.height + }, + // 列表宽度 + width: { + type: [String, Number], + default: () => defProps.list.width + }, + // 列表前后预渲染的屏数,1代表一个屏幕的高度,1.5代表1个半屏幕高度 + preLoadScreen: { + type: [String, Number], + default: () => defProps.list.preLoadScreen + }, + // 开启自定义下拉刷新 + refresherEnabled: { + type: Boolean, + default: () => false + }, + // 设置自定义下拉刷新阈值 + refresherThreshold: { + type: Number, + default: () => 45 + }, + // 设置自定义下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式 + refresherDefaultStyle: { + type: String, + default: () => 'black' + }, + // 设置自定义下拉刷新区域背景颜色 + refresherBackground: { + type: String, + default: () => '#FFF' + }, + // 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发 + refresherTriggered: { + type: Boolean, + default: () => false + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-list/u-list.vue b/im-uniapp/uni_modules/uview-plus/components/u-list/u-list.vue new file mode 100644 index 0000000..1473b3e --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-list/u-list.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/props.js b/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/props.js new file mode 100644 index 0000000..6a6abf9 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/props.js @@ -0,0 +1,61 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否显示组件 + show: { + type: Boolean, + default: () => defProps.loadingIcon.show + }, + // 颜色 + color: { + type: String, + default: () => defProps.loadingIcon.color + }, + // 提示文字颜色 + textColor: { + type: String, + default: () => defProps.loadingIcon.textColor + }, + // 文字和图标是否垂直排列 + vertical: { + type: Boolean, + default: () => defProps.loadingIcon.vertical + }, + // 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形 + mode: { + type: String, + default: () => defProps.loadingIcon.mode + }, + // 图标大小,单位默认px + size: { + type: [String, Number], + default: () => defProps.loadingIcon.size + }, + // 文字大小 + textSize: { + type: [String, Number], + default: () => defProps.loadingIcon.textSize + }, + // 文字内容 + text: { + type: [String, Number], + default: () => defProps.loadingIcon.text + }, + // 动画模式 + timingFunction: { + type: String, + default: () => defProps.loadingIcon.timingFunction + }, + // 动画执行周期时间 + duration: { + type: [String, Number], + default: () => defProps.loadingIcon.duration + }, + // mode=circle时的暗边颜色 + inactiveColor: { + type: String, + default: () => defProps.loadingIcon.inactiveColor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue b/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue new file mode 100644 index 0000000..f00f777 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loading-page/props.js b/im-uniapp/uni_modules/uview-plus/components/u-loading-page/props.js new file mode 100644 index 0000000..3a0ab3f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loading-page/props.js @@ -0,0 +1,52 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 提示内容 + loadingText: { + type: [String, Number], + default: () => defProps.loadingPage.loadingText + }, + // 文字上方用于替换loading动画的图片 + image: { + type: String, + default: () => defProps.loadingPage.image + }, + // 加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形 + loadingMode: { + type: String, + default: () => defProps.loadingPage.loadingMode + }, + // 是否加载中 + loading: { + type: Boolean, + default: () => defProps.loadingPage.loading + }, + // 背景色 + bgColor: { + type: String, + default: () => defProps.loadingPage.bgColor + }, + // 文字颜色 + color: { + type: String, + default: () => defProps.loadingPage.color + }, + // 文字大小 + fontSize: { + type: [String, Number], + default: () => defProps.loadingPage.fontSize + }, + // 图标大小 + iconSize: { + type: [String, Number], + default: () => defProps.loadingPage.fontSize + }, + // 加载中图标的颜色,只能rgb或者十六进制颜色值 + loadingColor: { + type: String, + default: () => defProps.loadingPage.loadingColor + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue b/im-uniapp/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue new file mode 100644 index 0000000..302304d --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loadmore/props.js b/im-uniapp/uni_modules/uview-plus/components/u-loadmore/props.js new file mode 100644 index 0000000..fb78e39 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loadmore/props.js @@ -0,0 +1,96 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态 + status: { + type: String, + default: () => defProps.loadmore.status + }, + // 组件背景色 + bgColor: { + type: String, + default: () => defProps.loadmore.bgColor + }, + // 是否显示加载中的图标 + icon: { + type: Boolean, + default: () => defProps.loadmore.icon + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.loadmore.fontSize + }, + // 图标大小 + iconSize: { + type: [String, Number], + default: () => defProps.loadmore.iconSize + }, + // 字体颜色 + color: { + type: String, + default: () => defProps.loadmore.color + }, + // 加载中状态的图标,spinner-花朵状图标,circle-圆圈状,semicircle-半圆 + loadingIcon: { + type: String, + default: () => defProps.loadmore.loadingIcon + }, + // 加载前的提示语 + loadmoreText: { + type: String, + default: () => defProps.loadmore.loadmoreText + }, + // 加载中提示语 + loadingText: { + type: String, + default: () => defProps.loadmore.loadingText + }, + // 没有更多的提示语 + nomoreText: { + type: String, + default: () => defProps.loadmore.nomoreText + }, + // 在“没有更多”状态下,是否显示粗点 + isDot: { + type: Boolean, + default: () => defProps.loadmore.isDot + }, + // 加载中图标的颜色 + iconColor: { + type: String, + default: () => defProps.loadmore.iconColor + }, + // 上边距 + marginTop: { + type: [String, Number], + default: () => defProps.loadmore.marginTop + }, + // 下边距 + marginBottom: { + type: [String, Number], + default: () => defProps.loadmore.marginBottom + }, + // 高度,单位px + height: { + type: [String, Number], + default: () => defProps.loadmore.height + }, + // 是否显示左边分割线 + line: { + type: Boolean, + default: () => defProps.loadmore.line + }, + // 线条颜色 + lineColor: { + type: String, + default: () => defProps.loadmore.lineColor + }, + // 是否虚线,true-虚线,false-实线 + dashed: { + type: Boolean, + default: () => defProps.loadmore.dashed + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue b/im-uniapp/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue new file mode 100644 index 0000000..62c99c0 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-message-input/u-message-input.vue b/im-uniapp/uni_modules/uview-plus/components/u-message-input/u-message-input.vue new file mode 100644 index 0000000..0da5f1c --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-message-input/u-message-input.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-modal/props.js b/im-uniapp/uni_modules/uview-plus/components/u-modal/props.js new file mode 100644 index 0000000..22dbdfd --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-modal/props.js @@ -0,0 +1,86 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否展示modal + show: { + type: Boolean, + default: () => defProps.modal.show + }, + // 标题 + title: { + type: [String], + default: () => defProps.modal.title + }, + // 弹窗内容 + content: { + type: String, + default: () => defProps.modal.content + }, + // 确认文案 + confirmText: { + type: String, + default: () => defProps.modal.confirmText + }, + // 取消文案 + cancelText: { + type: String, + default: () => defProps.modal.cancelText + }, + // 是否显示确认按钮 + showConfirmButton: { + type: Boolean, + default: () => defProps.modal.showConfirmButton + }, + // 是否显示取消按钮 + showCancelButton: { + type: Boolean, + default: () => defProps.modal.showCancelButton + }, + // 确认按钮颜色 + confirmColor: { + type: String, + default: () => defProps.modal.confirmColor + }, + // 取消文字颜色 + cancelColor: { + type: String, + default: () => defProps.modal.cancelColor + }, + // 对调确认和取消的位置 + buttonReverse: { + type: Boolean, + default: () => defProps.modal.buttonReverse + }, + // 是否开启缩放效果 + zoom: { + type: Boolean, + default: () => defProps.modal.zoom + }, + // 是否异步关闭,只对确定按钮有效 + asyncClose: { + type: Boolean, + default: () => defProps.modal.asyncClose + }, + // 是否允许点击遮罩关闭modal + closeOnClickOverlay: { + type: Boolean, + default: () => defProps.modal.closeOnClickOverlay + }, + // 给一个负的margin-top,往上偏移,避免和键盘重合的情况 + negativeTop: { + type: [String, Number], + default: () => defProps.modal.negativeTop + }, + // modal宽度,不支持百分比,可以数值,px,rpx单位 + width: { + type: [String, Number], + default: () => defProps.modal.width + }, + // 确认按钮的样式,circle-圆形,square-方形,如设置,将不会显示取消按钮 + confirmButtonShape: { + type: String, + default: () => defProps.modal.confirmButtonShape + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-modal/u-modal.vue b/im-uniapp/uni_modules/uview-plus/components/u-modal/u-modal.vue new file mode 100644 index 0000000..f9ab38f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-modal/u-modal.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-navbar/props.js b/im-uniapp/uni_modules/uview-plus/components/u-navbar/props.js new file mode 100644 index 0000000..2f20b02 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-navbar/props.js @@ -0,0 +1,87 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' + +export const props = defineMixin({ + props: { + // 是否开启顶部安全区适配 + safeAreaInsetTop: { + type: Boolean, + default: () => defProps.navbar.safeAreaInsetTop + }, + // 固定在顶部时,是否生成一个等高元素,以防止塌陷 + placeholder: { + type: Boolean, + default: () => defProps.navbar.placeholder + }, + // 是否固定在顶部 + fixed: { + type: Boolean, + default: () => defProps.navbar.fixed + }, + // 是否显示下边框 + border: { + type: Boolean, + default: () => defProps.navbar.border + }, + // 左边的图标 + leftIcon: { + type: String, + default: () => defProps.navbar.leftIcon + }, + // 左边的提示文字 + leftText: { + type: String, + default: () => defProps.navbar.leftText + }, + // 左右的提示文字 + rightText: { + type: String, + default: () => defProps.navbar.rightText + }, + // 右边的图标 + rightIcon: { + type: String, + default: () => defProps.navbar.rightIcon + }, + // 标题 + title: { + type: [String, Number], + default: () => defProps.navbar.title + }, + // 背景颜色 + bgColor: { + type: String, + default: () => defProps.navbar.bgColor + }, + // 标题的宽度 + titleWidth: { + type: [String, Number], + default: () => defProps.navbar.titleWidth + }, + // 导航栏高度 + height: { + type: [String, Number], + default: () => defProps.navbar.height + }, + // 左侧返回图标的大小 + leftIconSize: { + type: [String, Number], + default: () => defProps.navbar.leftIconSize + }, + // 左侧返回图标的颜色 + leftIconColor: { + type: String, + default: () => defProps.navbar.leftIconColor + }, + // 点击左侧区域(返回图标),是否自动返回上一页 + autoBack: { + type: Boolean, + default: () => defProps.navbar.autoBack + }, + // 标题的样式,对象或字符串 + titleStyle: { + type: [String, Object], + default: () => defProps.navbar.titleStyle + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-navbar/u-navbar.vue b/im-uniapp/uni_modules/uview-plus/components/u-navbar/u-navbar.vue new file mode 100644 index 0000000..6d6cdba --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-navbar/u-navbar.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-no-network/props.js b/im-uniapp/uni_modules/uview-plus/components/u-no-network/props.js new file mode 100644 index 0000000..1091cf5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-no-network/props.js @@ -0,0 +1,21 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 页面文字提示 + tips: { + type: String, + default: () => defProps.noNetwork.tips + }, + // 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖 + zIndex: { + type: [String, Number], + default: () => defProps.noNetwork.zIndex + }, + // image 没有网络的图片提示 + image: { + type: String, + default: () => defProps.noNetwork.image + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-no-network/u-no-network.vue b/im-uniapp/uni_modules/uview-plus/components/u-no-network/u-no-network.vue new file mode 100644 index 0000000..54539a5 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-no-network/u-no-network.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/props.js b/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/props.js new file mode 100644 index 0000000..cc2a53f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/props.js @@ -0,0 +1,72 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 显示的内容,数组 + text: { + type: [Array, String], + default: () => defProps.noticeBar.text + }, + // 通告滚动模式,row-横向滚动,column-竖向滚动 + direction: { + type: String, + default: () => defProps.noticeBar.direction + }, + // direction = row时,是否使用步进形式滚动 + step: { + type: Boolean, + default: () => defProps.noticeBar.step + }, + // 是否显示左侧的音量图标 + icon: { + type: String, + default: () => defProps.noticeBar.icon + }, + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 + mode: { + type: String, + default: () => defProps.noticeBar.mode + }, + // 文字颜色,各图标也会使用文字颜色 + color: { + type: String, + default: () => defProps.noticeBar.color + }, + // 背景颜色 + bgColor: { + type: String, + default: () => defProps.noticeBar.bgColor + }, + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 + speed: { + type: [String, Number], + default: () => defProps.noticeBar.speed + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.noticeBar.fontSize + }, + // 滚动一个周期的时间长,单位ms + duration: { + type: [String, Number], + default: () => defProps.noticeBar.duration + }, + // 是否禁止用手滑动切换 + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 + disableTouch: { + type: Boolean, + default: () => defProps.noticeBar.disableTouch + }, + // 跳转的页面路径 + url: { + type: String, + default: () => defProps.noticeBar.url + }, + // 页面跳转的类型 + linkType: { + type: String, + default: () => defProps.noticeBar.linkType + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue b/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue new file mode 100644 index 0000000..548d07f --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue @@ -0,0 +1,105 @@ + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-notify/props.js b/im-uniapp/uni_modules/uview-plus/components/u-notify/props.js new file mode 100644 index 0000000..fdb01b4 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-notify/props.js @@ -0,0 +1,51 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 到顶部的距离 + top: { + type: [String, Number], + default: () => defProps.notify.top + }, + // 是否展示组件 + // show: { + // type: Boolean, + // default: () => defProps.notify.show + // }, + // type主题,primary,success,warning,error + type: { + type: String, + default: () => defProps.notify.type + }, + // 字体颜色 + color: { + type: String, + default: () => defProps.notify.color + }, + // 背景颜色 + bgColor: { + type: String, + default: () => defProps.notify.bgColor + }, + // 展示的文字内容 + message: { + type: String, + default: () => defProps.notify.message + }, + // 展示时长,为0时不消失,单位ms + duration: { + type: [String, Number], + default: () => defProps.notify.duration + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: () => defProps.notify.fontSize + }, + // 是否留出顶部安全距离(状态栏高度) + safeAreaInsetTop: { + type: Boolean, + default: () => defProps.notify.safeAreaInsetTop + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-notify/u-notify.vue b/im-uniapp/uni_modules/uview-plus/components/u-notify/u-notify.vue new file mode 100644 index 0000000..adab25a --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-notify/u-notify.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-number-box/props.js b/im-uniapp/uni_modules/uview-plus/components/u-number-box/props.js new file mode 100644 index 0000000..1c5564e --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-number-box/props.js @@ -0,0 +1,120 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 步进器标识符,在change回调返回 + name: { + type: [String, Number], + default: () => defProps.numberBox.name + }, + // #ifdef VUE2 + // 用于双向绑定的值,初始化时设置设为默认min值(最小值) + value: { + type: [String, Number], + default: () => defProps.numberBox.value + }, + // #endif + // #ifdef VUE3 + // 用于双向绑定的值,初始化时设置设为默认min值(最小值) + modelValue: { + type: [String, Number], + default: () => defProps.numberBox.value + }, + // #endif + // 最小值 + min: { + type: [String, Number], + default: () => defProps.numberBox.min + }, + // 最大值 + max: { + type: [String, Number], + default: () => defProps.numberBox.max + }, + // 加减的步长,可为小数 + step: { + type: [String, Number], + default: () => defProps.numberBox.step + }, + // 是否只允许输入整数 + integer: { + type: Boolean, + default: () => defProps.numberBox.integer + }, + // 是否禁用,包括输入框,加减按钮 + disabled: { + type: Boolean, + default: () => defProps.numberBox.disabled + }, + // 是否禁用输入框 + disabledInput: { + type: Boolean, + default: () => defProps.numberBox.disabledInput + }, + // 是否开启异步变更,开启后需要手动控制输入值 + asyncChange: { + type: Boolean, + default: () => defProps.numberBox.asyncChange + }, + // 输入框宽度,单位为px + inputWidth: { + type: [String, Number], + default: () => defProps.numberBox.inputWidth + }, + // 是否显示减少按钮 + showMinus: { + type: Boolean, + default: () => defProps.numberBox.showMinus + }, + // 是否显示增加按钮 + showPlus: { + type: Boolean, + default: () => defProps.numberBox.showPlus + }, + // 显示的小数位数 + decimalLength: { + type: [String, Number, null], + default: () => defProps.numberBox.decimalLength + }, + // 是否开启长按加减手势 + longPress: { + type: Boolean, + default: () => defProps.numberBox.longPress + }, + // 输入框文字和加减按钮图标的颜色 + color: { + type: String, + default: () => defProps.numberBox.color + }, + // 按钮大小,宽高等于此值,单位px,输入框高度和此值保持一致 + buttonSize: { + type: [String, Number], + default: () => defProps.numberBox.buttonSize + }, + // 输入框和按钮的背景颜色 + bgColor: { + type: String, + default: () => defProps.numberBox.bgColor + }, + // 指定光标于键盘的距离,避免键盘遮挡输入框,单位px + cursorSpacing: { + type: [String, Number], + default: () => defProps.numberBox.cursorSpacing + }, + // 是否禁用增加按钮 + disablePlus: { + type: Boolean, + default: () => defProps.numberBox.disablePlus + }, + // 是否禁用减少按钮 + disableMinus: { + type: Boolean, + default: () => defProps.numberBox.disableMinus + }, + // 加减按钮图标的样式 + iconStyle: { + type: [Object, String], + default: () => defProps.numberBox.iconStyle + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-number-box/u-number-box.vue b/im-uniapp/uni_modules/uview-plus/components/u-number-box/u-number-box.vue new file mode 100644 index 0000000..2371bbc --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-number-box/u-number-box.vue @@ -0,0 +1,465 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/props.js b/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/props.js new file mode 100644 index 0000000..d853858 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/props.js @@ -0,0 +1,21 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 键盘的类型,number-数字键盘,card-身份证键盘 + mode: { + type: String, + default: () => defProps.numberKeyboard.value + }, + // 是否显示键盘的"."符号 + dotDisabled: { + type: Boolean, + default: () => defProps.numberKeyboard.dotDisabled + }, + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: () => defProps.numberKeyboard.random + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/u-number-keyboard.vue b/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/u-number-keyboard.vue new file mode 100644 index 0000000..a978fd0 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-number-keyboard/u-number-keyboard.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-overlay/props.js b/im-uniapp/uni_modules/uview-plus/components/u-overlay/props.js new file mode 100644 index 0000000..39f16f3 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-overlay/props.js @@ -0,0 +1,26 @@ +import { defineMixin } from '../../libs/vue' +import defProps from '../../libs/config/props.js' +export const props = defineMixin({ + props: { + // 是否显示遮罩 + show: { + type: Boolean, + default: () => defProps.overlay.show + }, + // 层级z-index + zIndex: { + type: [String, Number], + default: () => defProps.overlay.zIndex + }, + // 遮罩的过渡时间,单位为ms + duration: { + type: [String, Number], + default: () => defProps.overlay.duration + }, + // 不透明度值,当做rgba的第四个参数 + opacity: { + type: [String, Number], + default: () => defProps.overlay.opacity + } + } +}) diff --git a/im-uniapp/uni_modules/uview-plus/components/u-overlay/u-overlay.vue b/im-uniapp/uni_modules/uview-plus/components/u-overlay/u-overlay.vue new file mode 100644 index 0000000..d804750 --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-overlay/u-overlay.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/im-uniapp/uni_modules/uview-plus/components/u-parse/node/node.vue b/im-uniapp/uni_modules/uview-plus/components/u-parse/node/node.vue new file mode 100644 index 0000000..06035de --- /dev/null +++ b/im-uniapp/uni_modules/uview-plus/components/u-parse/node/node.vue @@ -0,0 +1,576 @@ +