├── view.PNG ├── im-chat ├── static │ └── img │ │ ├── homeHL.png │ │ └── customerHL.png ├── main.js ├── pages.json ├── App.vue ├── store │ └── index.js ├── components │ ├── chatinput.vue │ └── messageshow.vue ├── manifest.json ├── pages │ └── index │ │ └── index.vue └── common │ └── icon.css ├── .gitignore └── README.md /view.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felony/uniapp-chat/HEAD/view.PNG -------------------------------------------------------------------------------- /im-chat/static/img/homeHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felony/uniapp-chat/HEAD/im-chat/static/img/homeHL.png -------------------------------------------------------------------------------- /im-chat/static/img/customerHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felony/uniapp-chat/HEAD/im-chat/static/img/customerHL.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################## 2 | ## Folders ## 3 | ############################## 4 | im-chat/unpackage -------------------------------------------------------------------------------- /im-chat/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | import store from './store' 5 | 6 | Vue.config.productionTip = false 7 | 8 | Vue.prototype.$store = store 9 | 10 | App.mpType = 'app' 11 | 12 | const app = new Vue({ 13 | store, 14 | ...App 15 | }) 16 | app.$mount() 17 | -------------------------------------------------------------------------------- /im-chat/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "im-chat" 7 | } 8 | } 9 | ], 10 | "globalStyle": { 11 | "navigationBarTextStyle": "white", 12 | "navigationBarTitleText": "im-chat", 13 | "navigationBarBackgroundColor": "#1482d1", 14 | "backgroundColor": "#1482d1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /im-chat/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /im-chat/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | user: { 9 | home: { 10 | id: 1, 11 | name: 'tax', 12 | img: 'static/img/homeHL.png' 13 | }, 14 | customer: { 15 | id: 2, 16 | name: 'customer', 17 | img: 'static/img/customerHL.png' 18 | } 19 | } 20 | }, 21 | updated:function(){ 22 | console.log('message update:'+ this.scrollTop); 23 | } 24 | }); 25 | 26 | export default store 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uniapp-chat 2 | 在mui中有chat界面的例子,升级到uni-app后,没有类似的模板,因此模仿写了一个。遇到了一些坑,在此一一记录下来。当然,由于是新手,可能有些坑可以避开。 3 | --- 4 | ## scroll-view高度的设置 5 | 输入内容后,必然要在对话界面的底部显示内容,但是在uni-app下不知道如何能操作DOM来显示和定位,有说需要通过uni.pageScrollTo的方式,但是这个页面刷新的太厉害,输入框都刷新了,没法使用。所以只能使用scroll-view组件。但是通过scroll-view使用竖向滚动时,需要给 一个固定高度。为了适配屏幕的大小,则需要通过计算来确定scroll-view的高度。 6 | 7 | ``` 8 | 9 | 10 | ``` 11 | > style.contentViewHeight 需要在加载前通过计算获得 12 | 13 | ``` 14 | created: function () { 15 | const res = uni.getSystemInfoSync(); 16 | this.style.pageHeight = res.windowHeight; 17 | this.style.contentViewHeight = res.windowHeight - uni.getSystemInfoSync().screenWidth / 750 * (100); //像素 18 | } 19 | ``` 20 | > 由于给出的是像素高度,所以需要换算一下 res.windowHeight - uni.getSystemInfoSync().screenWidth / 750 * (100); 其中100是底部输入框的像素高度 21 | 22 | ## scroll-top的使用 23 | 每次发送内容后,需要滚动到底部,可以通过把最后一个元素id赋值给scroll-into-view的方式来实现,但是效果也不是很理想,所以采用了scroll-top的方式。 24 | ``` 25 | var that = this; 26 | var query = uni.createSelectorQuery(); 27 | query.selectAll('.m-item').boundingClientRect(); 28 | query.select('#scrollview').boundingClientRect(); 29 | query.exec(function (res) { 30 | that.style.mitemHeight = 0; 31 | res[0].forEach(function (rect) { 32 | that.style.mitemHeight = that.style.mitemHeight + rect.height + 20;}); 33 | 34 | if (that.style.mitemHeight > that.style.contentViewHeight) { 35 | that.scrollTop = that.style.mitemHeight - that.style.contentViewHeight; 36 | } 37 | }); 38 | ``` 39 | > 方法就是先获取所有内部子元素的高度,然后用子元素的高度和-显示高度,就得到了scroll-top的滚动位置。 40 | 41 | ## 其他 42 | 1. uni-app的img组件地址有点问题,小程序版本能用绝对地址,但是app版本就需要使用相对路径。 43 | 2. 虽然现在每次发送信息后,能滚动到底部,但是输入的时候,由于弹出键盘,就可能覆盖了,没法看到最后一条信息。 -------------------------------------------------------------------------------- /im-chat/components/chatinput.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 54 | 55 | 103 | -------------------------------------------------------------------------------- /im-chat/components/messageshow.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 32 | 33 | 103 | -------------------------------------------------------------------------------- /im-chat/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "im-chat", 3 | "appid" : "__UNI__39DE137", 4 | "description": "", 5 | "versionName": "1.0.0", 6 | "versionCode": "100", 7 | "app-plus": { /* 5+App特有相关 */ 8 | "modules": { /* 模块配置 */ 9 | 10 | }, 11 | "distribute": { /* 应用发布信息 */ 12 | "android": { /* android打包配置 */ 13 | "permissions": ["", 14 | "", 15 | "", 16 | "", 17 | "", 18 | "", 19 | "", 20 | "", 21 | "", 22 | "", 23 | "", 24 | "", 25 | "", 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "" 35 | ] 36 | }, 37 | "ios": { /* ios打包配置 */ 38 | 39 | }, 40 | "sdkConfigs": { /* SDK配置 */ 41 | 42 | } 43 | } 44 | }, 45 | "quickapp": { /* 快应用特有相关 */ 46 | 47 | }, 48 | "mp-weixin": { /* 小程序特有相关 */ 49 | "appid": "" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /im-chat/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 92 | 106 | 107 | 129 | -------------------------------------------------------------------------------- /im-chat/common/icon.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: uniicons; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('https://img-cdn-qiniu.dcloud.net.cn/fonts/uni.ttf') format('truetype'); 6 | } 7 | 8 | .uni-icon { 9 | font-family: uniicons; 10 | font-size: 48px; 11 | font-weight: normal; 12 | font-style: normal; 13 | line-height: 1; 14 | display: inline-block; 15 | text-decoration: none; 16 | -webkit-font-smoothing: antialiased; 17 | } 18 | 19 | .uni-icon.uni-active { 20 | color: #007aff; 21 | } 22 | 23 | .uni-icon-contact:before { 24 | content: '\e100'; 25 | } 26 | 27 | .uni-icon-person:before { 28 | content: '\e101'; 29 | } 30 | 31 | .uni-icon-personadd:before { 32 | content: '\e102'; 33 | } 34 | 35 | .uni-icon-contact-filled:before { 36 | content: '\e130'; 37 | } 38 | 39 | .uni-icon-person-filled:before { 40 | content: '\e131'; 41 | } 42 | 43 | .uni-icon-personadd-filled:before { 44 | content: '\e132'; 45 | } 46 | 47 | .uni-icon-phone:before { 48 | content: '\e200'; 49 | } 50 | 51 | .uni-icon-email:before { 52 | content: '\e201'; 53 | } 54 | 55 | .uni-icon-chatbubble:before { 56 | content: '\e202'; 57 | } 58 | 59 | .uni-icon-chatboxes:before { 60 | content: '\e203'; 61 | } 62 | 63 | .uni-icon-phone-filled:before { 64 | content: '\e230'; 65 | } 66 | 67 | .uni-icon-email-filled:before { 68 | content: '\e231'; 69 | } 70 | 71 | .uni-icon-chatbubble-filled:before { 72 | content: '\e232'; 73 | } 74 | 75 | .uni-icon-chatboxes-filled:before { 76 | content: '\e233'; 77 | } 78 | 79 | .uni-icon-weibo:before { 80 | content: '\e260'; 81 | } 82 | 83 | .uni-icon-weixin:before { 84 | content: '\e261'; 85 | } 86 | 87 | .uni-icon-pengyouquan:before { 88 | content: '\e262'; 89 | } 90 | 91 | .uni-icon-chat:before { 92 | content: '\e263'; 93 | } 94 | 95 | .uni-icon-qq:before { 96 | content: '\e264'; 97 | } 98 | 99 | .uni-icon-videocam:before { 100 | content: '\e300'; 101 | } 102 | 103 | .uni-icon-camera:before { 104 | content: '\e301'; 105 | } 106 | 107 | .uni-icon-mic:before { 108 | content: '\e302'; 109 | } 110 | 111 | .uni-icon-location:before { 112 | content: '\e303'; 113 | } 114 | 115 | .uni-icon-mic-filled:before, 116 | .uni-icon-speech:before { 117 | content: '\e332'; 118 | } 119 | 120 | .uni-icon-location-filled:before { 121 | content: '\e333'; 122 | } 123 | 124 | .uni-icon-micoff:before { 125 | content: '\e360'; 126 | } 127 | 128 | .uni-icon-image:before { 129 | content: '\e363'; 130 | } 131 | 132 | .uni-icon-map:before { 133 | content: '\e364'; 134 | } 135 | 136 | .uni-icon-compose:before { 137 | content: '\e400'; 138 | } 139 | 140 | .uni-icon-trash:before { 141 | content: '\e401'; 142 | } 143 | 144 | .uni-icon-upload:before { 145 | content: '\e402'; 146 | } 147 | 148 | .uni-icon-download:before { 149 | content: '\e403'; 150 | } 151 | 152 | .uni-icon-close:before { 153 | content: '\e404'; 154 | } 155 | 156 | .uni-icon-redo:before { 157 | content: '\e405'; 158 | } 159 | 160 | .uni-icon-undo:before { 161 | content: '\e406'; 162 | } 163 | 164 | .uni-icon-refresh:before { 165 | content: '\e407'; 166 | } 167 | 168 | .uni-icon-star:before { 169 | content: '\e408'; 170 | } 171 | 172 | .uni-icon-plus:before { 173 | content: '\e409'; 174 | } 175 | 176 | .uni-icon-minus:before { 177 | content: '\e410'; 178 | } 179 | 180 | .uni-icon-circle:before, 181 | .uni-icon-checkbox:before { 182 | content: '\e411'; 183 | } 184 | 185 | .uni-icon-close-filled:before, 186 | .uni-icon-clear:before { 187 | content: '\e434'; 188 | } 189 | 190 | .uni-icon-refresh-filled:before { 191 | content: '\e437'; 192 | } 193 | 194 | .uni-icon-star-filled:before { 195 | content: '\e438'; 196 | } 197 | 198 | .uni-icon-plus-filled:before { 199 | content: '\e439'; 200 | } 201 | 202 | .uni-icon-minus-filled:before { 203 | content: '\e440'; 204 | } 205 | 206 | .uni-icon-circle-filled:before { 207 | content: '\e441'; 208 | } 209 | 210 | .uni-icon-checkbox-filled:before { 211 | content: '\e442'; 212 | } 213 | 214 | .uni-icon-closeempty:before { 215 | content: '\e460'; 216 | } 217 | 218 | .uni-icon-refreshempty:before { 219 | content: '\e461'; 220 | } 221 | 222 | .uni-icon-reload:before { 223 | content: '\e462'; 224 | } 225 | 226 | .uni-icon-starhalf:before { 227 | content: '\e463'; 228 | } 229 | 230 | .uni-icon-spinner:before { 231 | content: '\e464'; 232 | } 233 | 234 | .uni-icon-spinner-cycle:before { 235 | content: '\e465'; 236 | } 237 | 238 | .uni-icon-search:before { 239 | content: '\e466'; 240 | } 241 | 242 | .uni-icon-plusempty:before { 243 | content: '\e468'; 244 | } 245 | 246 | .uni-icon-forward:before { 247 | content: '\e470'; 248 | } 249 | 250 | .uni-icon-back:before, 251 | .uni-icon-left-nav:before { 252 | content: '\e471'; 253 | } 254 | 255 | .uni-icon-checkmarkempty:before { 256 | content: '\e472'; 257 | } 258 | 259 | .uni-icon-home:before { 260 | content: '\e500'; 261 | } 262 | 263 | .uni-icon-navigate:before { 264 | content: '\e501'; 265 | } 266 | 267 | .uni-icon-gear:before { 268 | content: '\e502'; 269 | } 270 | 271 | .uni-icon-paperplane:before { 272 | content: '\e503'; 273 | } 274 | 275 | .uni-icon-info:before { 276 | content: '\e504'; 277 | } 278 | 279 | .uni-icon-help:before { 280 | content: '\e505'; 281 | } 282 | 283 | .uni-icon-locked:before { 284 | content: '\e506'; 285 | } 286 | 287 | .uni-icon-more:before { 288 | content: '\e507'; 289 | } 290 | 291 | .uni-icon-flag:before { 292 | content: '\e508'; 293 | } 294 | 295 | .uni-icon-home-filled:before { 296 | content: '\e530'; 297 | } 298 | 299 | .uni-icon-gear-filled:before { 300 | content: '\e532'; 301 | } 302 | 303 | .uni-icon-info-filled:before { 304 | content: '\e534'; 305 | } 306 | 307 | .uni-icon-help-filled:before { 308 | content: '\e535'; 309 | } 310 | 311 | .uni-icon-more-filled:before { 312 | content: '\e537'; 313 | } 314 | 315 | .uni-icon-settings:before { 316 | content: '\e560'; 317 | } 318 | 319 | .uni-icon-list:before { 320 | content: '\e562'; 321 | } 322 | 323 | .uni-icon-bars:before { 324 | content: '\e563'; 325 | } 326 | 327 | .uni-icon-loop:before { 328 | content: '\e565'; 329 | } 330 | 331 | .uni-icon-paperclip:before { 332 | content: '\e567'; 333 | } 334 | 335 | .uni-icon-eye:before { 336 | content: '\e568'; 337 | } 338 | 339 | .uni-icon-arrowup:before { 340 | content: '\e580'; 341 | } 342 | 343 | .uni-icon-arrowdown:before { 344 | content: '\e581'; 345 | } 346 | 347 | .uni-icon-arrowleft:before { 348 | content: '\e582'; 349 | } 350 | 351 | .uni-icon-arrowright:before { 352 | content: '\e583'; 353 | } 354 | 355 | .uni-icon-arrowthinup:before { 356 | content: '\e584'; 357 | } 358 | 359 | .uni-icon-arrowthindown:before { 360 | content: '\e585'; 361 | } 362 | 363 | .uni-icon-arrowthinleft:before { 364 | content: '\e586'; 365 | } 366 | 367 | .uni-icon-arrowthinright:before { 368 | content: '\e587'; 369 | } 370 | 371 | .uni-icon-pulldown:before { 372 | content: '\e588'; 373 | } 374 | --------------------------------------------------------------------------------