├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── README.md ├── api └── data.json ├── build ├── build.js ├── check-versions.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── src ├── App.vue ├── assets │ ├── alipay.jpg │ ├── avatar.jpg │ ├── avatar0.jpg │ ├── avatar1.jpg │ ├── avatar2.jpg │ ├── avatar3.jpg │ ├── avatar4.jpg │ ├── avatar5.jpg │ ├── avatar6.jpg │ ├── avatar7.jpg │ ├── bg0.jpg │ ├── bg1.jpg │ ├── bg2.jpg │ ├── bg3.jpg │ ├── bg4.jpg │ ├── photo_0.jpg │ ├── photo_1.jpg │ ├── qr.jpg │ ├── tag.png │ ├── theme_dark.jpg │ ├── theme_default.jpg │ ├── theme_gold.jpg │ └── theme_pink.jpg ├── components │ ├── chat_area.vue │ ├── chat_avatar_panel.vue │ ├── chat_card.vue │ ├── chat_cell.vue │ ├── chat_container.vue │ ├── chat_dialog.vue │ ├── chat_group.vue │ ├── chat_header.vue │ ├── chat_menu.vue │ ├── chat_pop_bubble.vue │ └── chat_table.vue ├── main.js ├── pages │ ├── chat.vue │ ├── index.vue │ ├── overview.vue │ ├── page_transition.vue │ ├── pay.vue │ └── theme.vue ├── router │ └── index.js └── styles │ ├── base │ ├── animate.scss │ ├── minxin.scss │ ├── reset.scss │ └── value.scss │ └── common.scss └── static └── .gitkeep /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "postcss-import": {}, 7 | "autoprefixer": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatUI 2 | 3 | # Install 4 | 5 | ``` 6 | npm install vue-chat-ui --save 7 | ``` 8 | 9 | # Code Organization 10 | ``` 11 | ├── build 12 | ├── config 13 | ├── preview // project preview image 14 | ├── src // the source files 15 | │ ├── assets // static image resource 16 | │ ├── components 17 | │ │ ├── chat_area.vue // provide chatting view 18 | │ │ ├── chat_cell.vue // a cell include contact simple data 19 | │   │   ├── chat_dialog.vue     // pop dialog 20 | │   │   ├── chat_group.vue   // chat-cell group 21 | │ │ ├── chat_header.vue // top header 22 | │ │ └── chat_pop_bubble.vue // small bubble clickable 23 | │ ├── pages 24 | │   │   ├── chat.vue             // chat-each-other view 25 | │ │ ├── index.vue // contact list 26 | │ │ ├── overview.vue // contact profile view 27 | │ │ ├── page_transition.vue // page transition animate 28 | │ │ ├── pay.vue // pay for VIP 29 | │ │ └── theme.vue // change theme 30 | │ ├── router 31 | │ │ └── index.js // vue router setting 32 | │ ├── scripts // scripts resource 33 | │ ├── styles // css resource 34 | │ ├── App.vue // page entry file 35 | │ └── main.js // program entry file, load components 36 | ├── .babelrc 37 | ├── .gitigore 38 | ├── data.json // analog data of back end 39 | ├── index.html // entry html 40 | ├── LICENSE 41 | ├── package-lock.json 42 | ├── package.json 43 | └── README.md 44 | ``` 45 | 46 | # Components 47 | ## ChatArea 48 | 49 | >Provide the conversation view and the message sender of each chatting. 50 | >### Menu props 51 | >| props | Introductions | type | default | 52 | >| :------------ | :------------ | :------------ | :------------ | 53 | >| height | Chatting area height. | Number | 300 | 54 | >| contactAvatar | URL of other avartar, display on the left. | String | - | 55 | >| ownerAvatar | URL of own avartar, display on the right. | String | - | 56 | >| oriData | Chat message, once at start time. | Array | [] | 57 | >### Menu events 58 | >| events | Introductions | return | 59 | >| :------------ | :------------ | :------------ | 60 | >| on-avatar-click | Triggered when a avatar is clicked. | Which avatar was clicked (0: own, 1: other). | 61 | >| load-more | Drag to top and get more data. | A function callback contains success status and data. | 62 | >| on-msg-send | Triggered when the message to be sent is ready and the sending button is clicked. | The message of sending. | 63 | > 64 | >When you received a new message, use `this.$bus.emit('new-chat-data', message)`with `vue-bus` on parent to provide the new message to this component. 65 | 66 | ## ChatCell 67 | 68 | > A cell include contact avatar, nickname, last massage and time. 69 | >### Menu props 70 | >| props | Introductions | type | default | 71 | >| :------------ | :------------ | :------------ | :------------ | 72 | >| avatar | Avartar URL. | String | '' | 73 | >| nickname | Nickname or display name. | String | '' | 74 | >| msg | The last message. | String | '' | 75 | >| time | The sent time of last message. | String | '' | 76 | >| circle-avatar | Display the avatar in circle or not. | Boolean | false | 77 | 78 | ## ChatGroup 79 | > The contacts list container, each info including other's avatar, the last message of the chat, the last time. Consists of the `chat-cell` component. 80 | >### Menu props 81 | >| props | Introductions | type | default | 82 | >| :------------ | :------------ | :------------ | :------------ | 83 | >| data | Contacts list data. | Array| [] | 84 | >### Menu events 85 | >| events | Introductions | return | 86 | >| :------------ | :------------ | :------------ | 87 | >| on-cell-click | A item is clicked. | click index, content. | 88 | 89 | ## ChatDialog 90 | > Pop a dialog in custom. 91 | >### Menu props 92 | >| props | Introductions | type | default | 93 | >| :------------ | :------------ | :------------ | :------------ | 94 | >| type | Dialog type (text dialog or input dialog). | Number | 0 | 95 | >| show | Dialog display. | Boolean | false | 96 | >| title | Dialog title. | String | false | 97 | >| content | Dialog content. | String | '' | 98 | >| positive-btn | Display text on positive button. | String | 'Confirm'| 99 | >| negative-btn | Display text on negative button. | String | 'Cancel'| 100 | >### Menu events 101 | >| events | Introductions | return | 102 | >| :------------ | :------------ | :------------ | 103 | >| positive-btn-click | When the positive button is pressed. | content | 104 | >| negative-btn-click | When the nagative button is pressed. | - | 105 | 106 | ## ChatHeader 107 | >The top header. 108 | >### Menu props 109 | >| props | Introductions | type | default | 110 | >| :------------ | :------------ | :------------ | :------------ | 111 | >| height | The height occupied by the header. | Number | 50 | 112 | >| back | Display the back option. | Boolean | true | 113 | >| title | The content of the header. | String | '' | 114 | >| fixed | The position of the header use fixed or not. | Boolean | false | 115 | -------------------------------------------------------------------------------- /api/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "contacts": [ 3 | { 4 | "contacts_id": 10001, 5 | "displayName": "Lancy", 6 | "avatar": "avatar1.jpg", 7 | "msg": "right", 8 | "time": 1515231611 9 | }, 10 | { 11 | "contacts_id": 10002, 12 | "displayName": "Jack", 13 | "avatar": "avatar2.jpg", 14 | "msg": "I'll take tonight", 15 | "time": 1515224552 16 | }, 17 | { 18 | "contacts_id": 10003, 19 | "displayName": "Bro", 20 | "avatar": "avatar3.jpg", 21 | "msg": "Hi!", 22 | "time": 1515223263 23 | }, 24 | { 25 | "contacts_id": 10004, 26 | "displayName": "Carrot", 27 | "avatar": "avatar4.jpg", 28 | "msg": "Now we are friends, let's chat.", 29 | "time": 1515219552 30 | }, 31 | { 32 | "contacts_id": 10005, 33 | "displayName": "Onion", 34 | "avatar": "avatar5.jpg", 35 | "msg": "Now we are friends, let's chat.", 36 | "time": 1515218641 37 | }, 38 | { 39 | "contacts_id": 10006, 40 | "displayName": "Pepper", 41 | "avatar": "avatar6.jpg", 42 | "msg": "Now we are friends, let's chat.", 43 | "time": 1515198757 44 | }, 45 | { 46 | "contacts_id": 10007, 47 | "displayName": "Corn", 48 | "avatar": "avatar7.jpg", 49 | "msg": "Now we are friends, let's chat.", 50 | "time": 1515168000 51 | }, 52 | { 53 | "contacts_id": 10008, 54 | "displayName": "Corn", 55 | "avatar": "avatar7.jpg", 56 | "msg": "Now we are friends, let's chat.", 57 | "time": 1515168000 58 | }, 59 | { 60 | "contacts_id": 10009, 61 | "displayName": "Corn", 62 | "avatar": "avatar7.jpg", 63 | "msg": "Now we are friends, let's chat.", 64 | "time": 1515168000 65 | }, 66 | { 67 | "contacts_id": 10010, 68 | "displayName": "Corn", 69 | "avatar": "avatar7.jpg", 70 | "msg": "Now we are friends, let's chat.", 71 | "time": 1515168000 72 | }, 73 | { 74 | "contacts_id": 10011, 75 | "displayName": "Corn", 76 | "avatar": "avatar7.jpg", 77 | "msg": "Now we are friends, let's chat.", 78 | "time": 1515168000 79 | }, 80 | { 81 | "contacts_id": 10012, 82 | "displayName": "Corn", 83 | "avatar": "avatar7.jpg", 84 | "msg": "Now we are friends, let's chat.", 85 | "time": 1515168000 86 | } 87 | ], 88 | "userProfile": [ 89 | { 90 | "accountId": "10001", 91 | "nickname": "Peanut", 92 | "alias": "Lancy", 93 | "avatarImg": "./src/assets/avatar1.jpg", 94 | "gender": "Female", 95 | "age": "17", 96 | "region": "Mars", 97 | "tel": "55052", 98 | "tag": "Family" 99 | }, 100 | { 101 | "accountId": "10002", 102 | "nickname": "Potato", 103 | "alias": "Jack", 104 | "avatarImg": "./src/assets/avatar2.jpg", 105 | "gender": "Male", 106 | "age": "", 107 | "region": "Mars", 108 | "tel": "55053", 109 | "tag": "Classmate" 110 | }, 111 | { 112 | "accountId": "10003", 113 | "nickname": "Tomato", 114 | "alias": "Bro", 115 | "avatarImg": "./src/assets/avatar3.jpg", 116 | "gender": "Male", 117 | "age": "10", 118 | "region": "Mars", 119 | "tel": "55053", 120 | "tag": "Family" 121 | }, 122 | { 123 | "accountId": "10004", 124 | "nickname": "Carrot", 125 | "alias": "", 126 | "avatarImg": "./src/assets/avatar4.jpg", 127 | "gender": "Female", 128 | "age": "", 129 | "region": "Mars", 130 | "tel": "55053", 131 | "tag": "Family" 132 | }, 133 | { 134 | "accountId": "10005", 135 | "nickname": "Onion", 136 | "alias": "", 137 | "avatarImg": "./src/assets/avatar5.jpg", 138 | "gender": "Female", 139 | "age": "", 140 | "region": "Mars", 141 | "tel": "55053", 142 | "tag": "Classmate" 143 | }, 144 | { 145 | "accountId": "10006", 146 | "nickname": "Pepper", 147 | "alias": "", 148 | "avatarImg": "./src/assets/avatar6.jpg", 149 | "gender": "Male", 150 | "age": "", 151 | "region": "Mars", 152 | "tel": "55053", 153 | "tag": "Colleague" 154 | }, 155 | { 156 | "accountId": "10007", 157 | "nickname": "Corn", 158 | "alias": "", 159 | "avatarImg": "./src/assets/avatar7.jpg", 160 | "gender": "Female", 161 | "age": "", 162 | "region": "Mars", 163 | "tel": "55053", 164 | "tag": "Family" 165 | }, 166 | { 167 | "accountId": "10008", 168 | "nickname": "Corn", 169 | "alias": "", 170 | "avatarImg": "./src/assets/avatar8.jpg", 171 | "gender": "", 172 | "age": "", 173 | "region": "Mars", 174 | "tel": "55053", 175 | "tag": "Family" 176 | }, 177 | { 178 | "accountId": "10009", 179 | "nickname": "Corn", 180 | "alias": "", 181 | "avatarImg": "./src/assets/avatar8.jpg", 182 | "gender": "", 183 | "age": "", 184 | "region": "Mars", 185 | "tel": "55053", 186 | "tag": "Family" 187 | } 188 | ], 189 | "chatData_10001": 190 | { 191 | "ownerAvatar": "avatar0.jpg", 192 | "contactAvatar": "avatar1.jpg", 193 | "chatData":[ 194 | { 195 | "direction": 1, 196 | "type": "picture", 197 | "content": "./src/assets/photo_0.jpg", 198 | "time": 1515231466 199 | }, 200 | { 201 | "direction": 1, 202 | "type": "picture", 203 | "content": "./src/assets/photo_1.jpg", 204 | "time": 1515231468 205 | }, 206 | { 207 | "direction": 0, 208 | "type": "text", 209 | "content": "looks nice", 210 | "time": 1515231592 211 | }, 212 | { 213 | "direction": 1, 214 | "type": "text", 215 | "content": "right", 216 | "time": 1515231611 217 | } 218 | ] 219 | }, 220 | "chatData_10002": 221 | { 222 | "ownerAvatar": "avatar0.jpg", 223 | "contactAvatar": "avatar2.jpg", 224 | "chatData": 225 | [ 226 | { 227 | "direction": 1, 228 | "type": "text", 229 | "content": "Your left coat in my house", 230 | "time": 1515218080 231 | }, 232 | { 233 | "direction": 0, 234 | "type": "text", 235 | "content": "I'll take tonight", 236 | "time": 1515224552 237 | } 238 | ] 239 | }, 240 | "chatData_10003": 241 | { 242 | "ownerAvatar": "avatar0.jpg", 243 | "contactAvatar": "avatar3.jpg", 244 | "chatData": 245 | [ 246 | { 247 | "direction": 1, 248 | "type": "text", 249 | "content": "Now we are friends, let's chat.", 250 | "time": 1515222060 251 | }, 252 | { 253 | "direction": 0, 254 | "type": "text", 255 | "content": "Hi!", 256 | "time": 1515219552 257 | } 258 | ] 259 | }, 260 | "chatData_10004": 261 | { 262 | "ownerAvatar": "avatar0.jpg", 263 | "contactAvatar": "avatar4.jpg", 264 | "chatData": 265 | [ 266 | { 267 | "direction": 1, 268 | "type": "text", 269 | "content": "Now we are friends, let's chat.", 270 | "time": 1515223263 271 | } 272 | ] 273 | }, 274 | "chatData_10005": 275 | { 276 | "ownerAvatar": "avatar0.jpg", 277 | "contactAvatar": "avatar5.jpg", 278 | "chatData": 279 | [ 280 | { 281 | "direction": 1, 282 | "type": "text", 283 | "content": "Now we are friends, let's chat.", 284 | "time": 1515218641 285 | } 286 | ] 287 | }, 288 | "chatData_10006": 289 | { 290 | "ownerAvatar": "avatar0.jpg", 291 | "contactAvatar": "avatar6.jpg", 292 | "chatData": 293 | [ 294 | { 295 | "direction": 1, 296 | "type": "text", 297 | "content": "Now we are friends, let's chat.", 298 | "time": 1515198757 299 | } 300 | ] 301 | }, 302 | "chatData_10007": 303 | { 304 | "ownerAvatar": "avatar0.jpg", 305 | "contactAvatar": "avatar7.jpg", 306 | "chatData": 307 | [ 308 | { 309 | "direction": 1, 310 | "type": "text", 311 | "content": "Now we are friends, let's chat.", 312 | "time": 1515168000 313 | } 314 | ] 315 | }, 316 | "chatData_10008": 317 | { 318 | "ownerAvatar": "avatar0.jpg", 319 | "contactAvatar": "avatar7.jpg", 320 | "chatData": 321 | [ 322 | { 323 | "direction": 1, 324 | "type": "text", 325 | "content": "Now we are friends, let's chat.", 326 | "time": 1515168000 327 | } 328 | ] 329 | }, 330 | "chatData_10009": 331 | { 332 | "ownerAvatar": "avatar0.jpg", 333 | "contactAvatar": "avatar7.jpg", 334 | "chatData": 335 | [ 336 | { 337 | "direction": 1, 338 | "type": "text", 339 | "content": "Now we are friends, let's chat.", 340 | "time": 1515168000 341 | } 342 | ] 343 | }, 344 | "chatData_10010": 345 | { 346 | "ownerAvatar": "avatar0.jpg", 347 | "contactAvatar": "avatar7.jpg", 348 | "chatData": 349 | [ 350 | { 351 | "direction": 1, 352 | "type": "text", 353 | "content": "Now we are friends, let's chat.", 354 | "time": 1515168000 355 | } 356 | ] 357 | }, 358 | "chatData_10010": 359 | { 360 | "ownerAvatar": "avatar0.jpg", 361 | "contactAvatar": "avatar7.jpg", 362 | "chatData": 363 | [ 364 | { 365 | "direction": 1, 366 | "type": "text", 367 | "content": "Now we are friends, let's chat.", 368 | "time": 1515168000 369 | } 370 | ] 371 | }, 372 | "chatData_10011": 373 | { 374 | "ownerAvatar": "avatar0.jpg", 375 | "contactAvatar": "avatar7.jpg", 376 | "chatData": 377 | [ 378 | { 379 | "direction": 1, 380 | "type": "text", 381 | "content": "Now we are friends, let's chat.", 382 | "time": 1515168000 383 | } 384 | ] 385 | }, 386 | "chatData_10012": 387 | { 388 | "ownerAvatar": "avatar0.jpg", 389 | "contactAvatar": "avatar7.jpg", 390 | "chatData": 391 | [ 392 | { 393 | "direction": 1, 394 | "type": "text", 395 | "content": "Now we are friends, let's chat.", 396 | "time": 1515168000 397 | } 398 | ] 399 | }, 400 | "chatData_10001_more": { 401 | "chatDataMore": [ 402 | { 403 | "direction": 0, 404 | "type": "text", 405 | "content": "May I invite you to dinner together?", 406 | "time": 1515231400 407 | }, 408 | { 409 | "direction": 1, 410 | "type": "text", 411 | "content": "I have had", 412 | "time": 1515231422 413 | } 414 | ] 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // if you are using ts-loader, setting this to true will make tyescript errors show up during build 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const glob = require('globby') 4 | const utils = require('./utils') 5 | const merge = require('webpack-merge') 6 | const config = require('../config') 7 | const vueLoaderConfig = require('./vue-loader.conf') 8 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 9 | 10 | function resolve (dir) { 11 | return path.join(__dirname, '..', dir) 12 | } 13 | 14 | const CSS_PATH = { 15 | css: { 16 | pattern: ['../src/styles/**/[^_]*.scss', '../src/styles/**/*.scss', '../src/styles/*.scss'], 17 | // dst: path.resolve(__dirname, 'static/build/webpack'), 18 | dst: config.build.assetsLib, 19 | } 20 | } 21 | 22 | function getCSSEntries(config) { 23 | let fileList = glob.sync(config.pattern) 24 | return fileList.reduce(function (previous, current) { 25 | let filePath = path.parse(path.relative(config.src, current)) 26 | let withoutSuffix = path.join(filePath.dir, filePath.name) 27 | previous[withoutSuffix] = path.resolve(__dirname, current) 28 | return previous 29 | }, {}) 30 | } 31 | const wpconfig = { 32 | devtool: 'cheap-module-eval-source-map', 33 | context: path.resolve(__dirname, '../'), 34 | entry: getCSSEntries(CSS_PATH.css), 35 | output: { 36 | path: CSS_PATH.css.dst, 37 | filename: 'style.css', 38 | publicPath: process.env.NODE_ENV === 'production' 39 | ? config.build.assetsPublicPath 40 | : config.dev.assetsPublicPath 41 | }, 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.scss$/, 46 | use: ExtractTextPlugin.extract({ 47 | use: ['style-loader', 'css-loader', 'sass-loader'] 48 | }) 49 | } 50 | ] 51 | }, 52 | resolve: { 53 | extensions: ['.scss'] 54 | }, 55 | plugins: [ 56 | new ExtractTextPlugin('/lib/style.css'), 57 | ] 58 | } 59 | 60 | module.exports = merge(wpconfig, { 61 | context: path.resolve(__dirname, '../'), 62 | entry: { 63 | app: './src/main.js' 64 | }, 65 | output: { 66 | path: config.build.assetsRoot, 67 | filename: '[name].js', 68 | publicPath: process.env.NODE_ENV === 'production' 69 | ? config.build.assetsPublicPath 70 | : config.dev.assetsPublicPath 71 | }, 72 | resolve: { 73 | extensions: ['.js', '.vue', '.json'], 74 | alias: { 75 | 'vue$': 'vue/dist/vue.esm.js', 76 | '@': resolve('src'), 77 | } 78 | }, 79 | module: { 80 | rules: [ 81 | { 82 | test: /\.vue$/, 83 | loader: 'vue-loader', 84 | options: vueLoaderConfig 85 | }, 86 | { 87 | test: /\.js$/, 88 | loader: 'babel-loader', 89 | include: [resolve('src'), resolve('test')] 90 | }, 91 | { 92 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 93 | loader: 'url-loader', 94 | options: { 95 | limit: 10000, 96 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 97 | } 98 | }, 99 | { 100 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 101 | loader: 'url-loader', 102 | options: { 103 | limit: 10000, 104 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 105 | } 106 | }, 107 | { 108 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 109 | loader: 'url-loader', 110 | options: { 111 | limit: 10000, 112 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 113 | } 114 | }, 115 | 116 | ] 117 | }, 118 | node: { 119 | // prevent webpack from injecting useless setImmediate polyfill because Vue 120 | // source contains it (although only uses it if it's native). 121 | setImmediate: false, 122 | // prevent webpack from injecting mocks to Node native modules 123 | // that does not make sense for the client 124 | dgram: 'empty', 125 | fs: 'empty', 126 | net: 'empty', 127 | tls: 'empty', 128 | child_process: 'empty' 129 | } 130 | }) -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | const portfinder = require('portfinder') 10 | 11 | const HOST = process.env.HOST 12 | const PORT = process.env.PORT && Number(process.env.PORT) 13 | 14 | const express = require('express'); 15 | const app = express(); 16 | const appData = require('../api/data.json'); 17 | const apiRoutes = express.Router(); 18 | app.use('/api', apiRoutes); 19 | 20 | const devWebpackConfig = merge(baseWebpackConfig, { 21 | module: { 22 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 23 | }, 24 | // cheap-module-eval-source-map is faster for development 25 | devtool: config.dev.devtool, 26 | 27 | // these devServer options should be customized in /config/index.js 28 | devServer: { 29 | clientLogLevel: 'warning', 30 | historyApiFallback: true, 31 | hot: true, 32 | compress: true, 33 | host: HOST || config.dev.host, 34 | port: PORT || config.dev.port, 35 | open: config.dev.autoOpenBrowser, 36 | overlay: config.dev.errorOverlay 37 | ? { warnings: false, errors: true } 38 | : false, 39 | publicPath: config.dev.assetsPublicPath, 40 | proxy: config.dev.proxyTable, 41 | quiet: true, // necessary for FriendlyErrorsPlugin 42 | watchOptions: { 43 | poll: config.dev.poll, 44 | }, 45 | before(app) { 46 | app.get('/api/contacts', (req, res) => { 47 | res.json({ 48 | errno: 0, 49 | data: appData.contacts 50 | }); 51 | }), 52 | app.get('/api/user_overview/10001', (req, res) => { 53 | res.json({ 54 | errno: 0, 55 | data: appData.userProfile[0] 56 | }); 57 | }), 58 | app.get('/api/user_overview/10002', (req, res) => { 59 | res.json({ 60 | errno: 0, 61 | data: appData.userProfile[1] 62 | }); 63 | }), 64 | app.get('/api/user_overview/10003', (req, res) => { 65 | res.json({ 66 | errno: 0, 67 | data: appData.userProfile[2] 68 | }); 69 | }), 70 | app.get('/api/user_overview/10004', (req, res) => { 71 | res.json({ 72 | errno: 0, 73 | data: appData.userProfile[3] 74 | }); 75 | }), 76 | app.get('/api/user_overview/10005', (req, res) => { 77 | res.json({ 78 | errno: 0, 79 | data: appData.userProfile[4] 80 | }); 81 | }), 82 | app.get('/api/user_overview/10006', (req, res) => { 83 | res.json({ 84 | errno: 0, 85 | data: appData.userProfile[5] 86 | }); 87 | }), 88 | app.get('/api/user_overview/10007', (req, res) => { 89 | res.json({ 90 | errno: 0, 91 | data: appData.userProfile[6] 92 | }); 93 | }), 94 | app.get('/api/user_overview/10008', (req, res) => { 95 | res.json({ 96 | errno: 0, 97 | data: appData.userProfile[7] 98 | }); 99 | }), 100 | app.get('/api/user_overview/10009', (req, res) => { 101 | res.json({ 102 | errno: 0, 103 | data: appData.userProfile[8] 104 | }); 105 | }), 106 | app.get('/api/chat_data/10001', (req, res) => { 107 | res.json({ 108 | errno: 0, 109 | data: appData.chatData_10001 110 | }); 111 | }), 112 | app.get('/api/chat_data/10002', (req, res) => { 113 | res.json({ 114 | errno: 0, 115 | data: appData.chatData_10002 116 | }); 117 | }), 118 | app.get('/api/chat_data/10003', (req, res) => { 119 | res.json({ 120 | errno: 0, 121 | data: appData.chatData_10003 122 | }); 123 | }), 124 | app.get('/api/chat_data/10004', (req, res) => { 125 | res.json({ 126 | errno: 0, 127 | data: appData.chatData_10004 128 | }); 129 | }), 130 | app.get('/api/chat_data/10005', (req, res) => { 131 | res.json({ 132 | errno: 0, 133 | data: appData.chatData_10005 134 | }); 135 | }), 136 | app.get('/api/chat_data/10006', (req, res) => { 137 | res.json({ 138 | errno: 0, 139 | data: appData.chatData_10006 140 | }); 141 | }), 142 | app.get('/api/chat_data/10007', (req, res) => { 143 | res.json({ 144 | errno: 0, 145 | data: appData.chatData_10007 146 | }); 147 | }), 148 | app.get('/api/chat_data/10008', (req, res) => { 149 | res.json({ 150 | errno: 0, 151 | data: appData.chatData_10008 152 | }); 153 | }), 154 | app.get('/api/chat_data/10009', (req, res) => { 155 | res.json({ 156 | errno: 0, 157 | data: appData.chatData_10009 158 | }); 159 | }), 160 | app.get('/api/chat_data/10001/1515231466', (req, res) => { 161 | res.json({ 162 | errno: 0, 163 | data: appData.chatData_10001_more 164 | }); 165 | }) 166 | } 167 | }, 168 | plugins: [ 169 | new webpack.DefinePlugin({ 170 | 'process.env': require('../config/dev.env') 171 | }), 172 | new webpack.HotModuleReplacementPlugin(), 173 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 174 | new webpack.NoEmitOnErrorsPlugin(), 175 | // https://github.com/ampedandwired/html-webpack-plugin 176 | new HtmlWebpackPlugin({ 177 | filename: 'index.html', 178 | template: 'index.html', 179 | inject: true 180 | }), 181 | ] 182 | }) 183 | 184 | module.exports = new Promise((resolve, reject) => { 185 | portfinder.basePort = process.env.PORT || config.dev.port 186 | portfinder.getPort((err, port) => { 187 | if (err) { 188 | reject(err) 189 | } else { 190 | // publish the new Port, necessary for e2e tests 191 | process.env.PORT = port 192 | // add port to devServer config 193 | devWebpackConfig.devServer.port = port 194 | 195 | // Add FriendlyErrorsPlugin 196 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 197 | compilationSuccessInfo: { 198 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 199 | }, 200 | onErrors: config.dev.notifyOnErrors 201 | ? utils.createNotifierCallback() 202 | : undefined 203 | })) 204 | 205 | resolve(devWebpackConfig) 206 | } 207 | }) 208 | }); 209 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('scripts/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('scripts/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('styles/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vender modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.7 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | 24 | /** 25 | * Source Maps 26 | */ 27 | 28 | // https://webpack.js.org/configuration/devtool/#development 29 | devtool: 'eval-source-map', 30 | 31 | // If you have problems debugging vue-files in devtools, 32 | // set this to false - it *may* help 33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 | cacheBusting: true, 35 | 36 | // CSS Sourcemaps off by default because relative paths are "buggy" 37 | // with this option, according to the CSS-Loader README 38 | // (https://github.com/webpack/css-loader#sourcemaps) 39 | // In our experience, they generally work as expected, 40 | // just be aware of this issue when enabling this option. 41 | cssSourceMap: false, 42 | }, 43 | build: { 44 | // Template for index.html 45 | index: path.resolve(__dirname, '../dist/index.html'), 46 | 47 | // Paths 48 | assetsLib: path.resolve(__dirname, '../lib'), 49 | assetsRoot: path.resolve(__dirname, '../dist'), 50 | assetsSubDirectory: 'static', 51 | assetsPublicPath: '/', 52 | 53 | /** 54 | * Source Maps 55 | */ 56 | 57 | productionSourceMap: true, 58 | // https://webpack.js.org/configuration/devtool/#production 59 | devtool: '#source-map', 60 | 61 | // Gzip off by default as many popular static hosts such as 62 | // Surge or Netlify already gzip all static assets for you. 63 | // Before setting to `true`, make sure to: 64 | // npm install --save-dev compression-webpack-plugin 65 | productionGzip: false, 66 | productionGzipExtensions: ['js', 'css'], 67 | 68 | // Run the build command with an extra argument to 69 | // View the bundle analyzer report after build finishes: 70 | // `npm run build --report` 71 | // Set to `true` or `false` to always turn it on or off 72 | bundleAnalyzerReport: process.env.npm_config_report 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | new 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import ChatAvatarPanel from './src/components/chat_avatar_panel.vue'; 2 | import ChatCell from './src/components/chat_cell.vue'; 3 | import ChatContainer from './src/components/chat_container.vue'; 4 | import ChatDialog from './src/components/chat_dialog.vue'; 5 | import ChatGroup from './src/components/chat_group.vue'; 6 | import ChatHeader from './src/components/chat_header.vue'; 7 | import ChatMenu from './src/components/chat_menu.vue'; 8 | import ChatTable from './src/components/chat_table.vue'; 9 | export { 10 | ChatAvatarPanel, 11 | ChatCell, 12 | ChatContainer, 13 | ChatDialog, 14 | ChatGroup, 15 | ChatHeader, 16 | ChatMenu, 17 | ChatTable 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat_ui", 3 | "main": "index.js", 4 | "description": "a simple chat ui in mobile with vue", 5 | "version": "0.1.0", 6 | "author": "stuffish", 7 | "engines" : { 8 | "node": ">=3.0.0", 9 | "npm": ">=5.3.0" 10 | }, 11 | "scripts": { 12 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 13 | "start": "npm run dev", 14 | "build": "node build/build.js", 15 | "build-style": "node build/build-style.js" 16 | }, 17 | "dependencies": { 18 | "css-loader": "^0.28.7", 19 | "html-webpack-plugin": "^2.30.1", 20 | "node-sass": "^4.5.3", 21 | "sass-loader": "^6.0.6", 22 | "vue": "^2.5.2", 23 | "vue-bus": "^1.0.0", 24 | "vue-resource": "^1.3.4", 25 | "vue-router": "^3.0.1", 26 | "vuex": "^3.0.1", 27 | "webpack": "^3.10.0", 28 | "webpack-merge": "^4.1.1" 29 | }, 30 | "devDependencies": { 31 | "autoprefixer": "^7.1.2", 32 | "babel-core": "^6.22.1", 33 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 34 | "babel-loader": "^7.1.1", 35 | "babel-plugin-syntax-jsx": "^6.18.0", 36 | "babel-plugin-transform-runtime": "^6.22.0", 37 | "babel-plugin-transform-vue-jsx": "^3.5.0", 38 | "babel-preset-env": "^1.3.2", 39 | "babel-preset-stage-2": "^6.22.0", 40 | "chalk": "^2.0.1", 41 | "copy-webpack-plugin": "^4.0.1", 42 | "css-loader": "^0.28.0", 43 | "extract-text-webpack-plugin": "^3.0.0", 44 | "file-loader": "^1.1.4", 45 | "friendly-errors-webpack-plugin": "^1.6.1", 46 | "html-webpack-plugin": "^2.30.1", 47 | "node-notifier": "^5.1.2", 48 | "optimize-css-assets-webpack-plugin": "^3.2.0", 49 | "ora": "^1.2.0", 50 | "portfinder": "^1.0.13", 51 | "postcss-import": "^11.0.0", 52 | "postcss-loader": "^2.0.8", 53 | "rimraf": "^2.6.0", 54 | "sass-map": "^0.3.4", 55 | "semver": "^5.3.0", 56 | "shelljs": "^0.7.6", 57 | "uglifyjs-webpack-plugin": "^1.1.1", 58 | "url-loader": "^0.5.8", 59 | "vue-loader": "^13.3.0", 60 | "vue-style-loader": "^3.0.1", 61 | "vue-template-compiler": "^2.5.2", 62 | "webpack": "^3.6.0", 63 | "webpack-bundle-analyzer": "^2.9.0", 64 | "webpack-dev-server": "^2.9.1", 65 | "webpack-merge": "^4.1.0" 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/stuffish/ChatUI.git" 70 | }, 71 | "keywords": [ 72 | "stuffish" 73 | ], 74 | "license": "MIT", 75 | "bugs": { 76 | "url": "https://github.com/stuffish/ChatUI/issues" 77 | }, 78 | "homepage": "https://github.com/stuffish/ChatUI#readme" 79 | } 80 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 33 | -------------------------------------------------------------------------------- /src/assets/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/alipay.jpg -------------------------------------------------------------------------------- /src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar.jpg -------------------------------------------------------------------------------- /src/assets/avatar0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar0.jpg -------------------------------------------------------------------------------- /src/assets/avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar1.jpg -------------------------------------------------------------------------------- /src/assets/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar2.jpg -------------------------------------------------------------------------------- /src/assets/avatar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar3.jpg -------------------------------------------------------------------------------- /src/assets/avatar4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar4.jpg -------------------------------------------------------------------------------- /src/assets/avatar5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar5.jpg -------------------------------------------------------------------------------- /src/assets/avatar6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar6.jpg -------------------------------------------------------------------------------- /src/assets/avatar7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/avatar7.jpg -------------------------------------------------------------------------------- /src/assets/bg0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/bg0.jpg -------------------------------------------------------------------------------- /src/assets/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/bg1.jpg -------------------------------------------------------------------------------- /src/assets/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/bg2.jpg -------------------------------------------------------------------------------- /src/assets/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/bg3.jpg -------------------------------------------------------------------------------- /src/assets/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/bg4.jpg -------------------------------------------------------------------------------- /src/assets/photo_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/photo_0.jpg -------------------------------------------------------------------------------- /src/assets/photo_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/photo_1.jpg -------------------------------------------------------------------------------- /src/assets/qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/qr.jpg -------------------------------------------------------------------------------- /src/assets/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/tag.png -------------------------------------------------------------------------------- /src/assets/theme_dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/theme_dark.jpg -------------------------------------------------------------------------------- /src/assets/theme_default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/theme_default.jpg -------------------------------------------------------------------------------- /src/assets/theme_gold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/theme_gold.jpg -------------------------------------------------------------------------------- /src/assets/theme_pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/assets/theme_pink.jpg -------------------------------------------------------------------------------- /src/components/chat_area.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 184 | 363 | -------------------------------------------------------------------------------- /src/components/chat_avatar_panel.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | 16 | -------------------------------------------------------------------------------- /src/components/chat_card.vue: -------------------------------------------------------------------------------- 1 | 17 | 44 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/components/chat_cell.vue: -------------------------------------------------------------------------------- 1 | 19 | 50 | 97 | -------------------------------------------------------------------------------- /src/components/chat_container.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 134 | 247 | -------------------------------------------------------------------------------- /src/components/chat_dialog.vue: -------------------------------------------------------------------------------- 1 | 24 | 74 | 142 | -------------------------------------------------------------------------------- /src/components/chat_group.vue: -------------------------------------------------------------------------------- 1 | 14 | 62 | 71 | -------------------------------------------------------------------------------- /src/components/chat_header.vue: -------------------------------------------------------------------------------- 1 | 15 | 42 | 79 | -------------------------------------------------------------------------------- /src/components/chat_menu.vue: -------------------------------------------------------------------------------- 1 | 12 | 36 | 60 | -------------------------------------------------------------------------------- /src/components/chat_pop_bubble.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | -------------------------------------------------------------------------------- /src/components/chat_table.vue: -------------------------------------------------------------------------------- 1 | 9 | 32 | 60 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import VueResource from 'vue-resource' 4 | import VueBus from 'vue-bus'; 5 | 6 | import App from './App.vue' 7 | import router from './router' 8 | 9 | Vue.use(VueResource); 10 | Vue.use(Vuex); 11 | Vue.use(VueBus); 12 | 13 | const store = new Vuex.Store({ 14 | state: { 15 | theme: localStorage.getItem('theme')||'default', 16 | themes: [ 17 | { 18 | id: 0, 19 | name: 'default', 20 | description: 'default', 21 | img: require('@/assets/theme_default.jpg'), 22 | isvip: false 23 | }, 24 | { 25 | id: 1, 26 | name: 'pink', 27 | description: 'pink', 28 | img: require('@/assets/theme_pink.jpg'), 29 | isvip: false 30 | }, 31 | { 32 | id: 2, 33 | name: 'dark', 34 | description: 'dark', 35 | img: require('@/assets/theme_dark.jpg'), 36 | isvip: false 37 | }, 38 | { 39 | id: 3, 40 | name: 'gold', 41 | description: 'gold', 42 | img: require('@/assets/theme_gold.jpg'), 43 | isvip: true 44 | } 45 | ], 46 | data: {} 47 | }, 48 | mutations: { 49 | setTheme(state, tm) { 50 | state.theme = tm; 51 | } 52 | } 53 | }); 54 | var script = document.createElement("script"); 55 | script.type = "text/javascript"; 56 | script.src = "//at.alicdn.com/t/font_463713_eqy42vs5i1gojemi.js"; 57 | document.getElementsByTagName("head")[0].appendChild(script); 58 | 59 | new Vue({ 60 | el: '#app', 61 | router, 62 | store, 63 | template: '', 64 | components: { App } 65 | }) 66 | -------------------------------------------------------------------------------- /src/pages/chat.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 132 | 143 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 55 | 76 | -------------------------------------------------------------------------------- /src/pages/overview.vue: -------------------------------------------------------------------------------- 1 | 30 | 102 | 166 | -------------------------------------------------------------------------------- /src/pages/page_transition.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /src/pages/pay.vue: -------------------------------------------------------------------------------- 1 | 12 | 24 | 74 | -------------------------------------------------------------------------------- /src/pages/theme.vue: -------------------------------------------------------------------------------- 1 | 29 | 66 | 78 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import PageTransition from '@/pages/page_transition.vue' 4 | import Index from '@/pages/index.vue' 5 | import Chat from '@/pages/chat.vue' 6 | import Overview from '@/pages/overview.vue' 7 | import Theme from '@/pages/theme.vue' 8 | import Pay from '@/pages/pay.vue' 9 | 10 | Router.prototype.goBack = function () { 11 | this.isBack = true 12 | window.history.go(-1) 13 | } 14 | 15 | Vue.use(Router) 16 | const router = new Router({ 17 | routes: [ 18 | { 19 | path: '/', 20 | name: 'PageTransition', 21 | component: PageTransition, 22 | children: [{ 23 | path: '', 24 | component: Index 25 | }, { 26 | path: '/chat', 27 | name: 'Chat', 28 | component: Chat 29 | }, { 30 | path: '/overview', 31 | name: 'Overview', 32 | component: Overview 33 | }, { 34 | path: '/theme', 35 | name: 'Theme', 36 | component: Theme 37 | }, { 38 | path: '/pay', 39 | name: 'Pay', 40 | component: Pay 41 | }] 42 | } 43 | ] 44 | }) 45 | 46 | export default router 47 | -------------------------------------------------------------------------------- /src/styles/base/animate.scss: -------------------------------------------------------------------------------- 1 | .up-slide-enter-active, .up-slide-leave-active { 2 | transition: all.5s ease 3 | } 4 | .up-slide-enter, .up-slide-leave-active { 5 | transform: translateY(-100%); 6 | } 7 | 8 | .fade-enter-active, .fade-leave-active { 9 | transition: .5s ease; 10 | } 11 | .fade-enter, .fade-leave-active { 12 | opacity: 0; 13 | } 14 | .up-appear-enter-active, .up-appear-leave-active { 15 | transition: .5s; 16 | } 17 | .up-appear-enter, .up-appear-leave-active { 18 | margin-top: -10px; 19 | opacity: 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/styles/base/minxin.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/src/styles/base/minxin.scss -------------------------------------------------------------------------------- /src/styles/base/reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | font-family: "微软雅黑"; 26 | -webkit-tap-highlight-color:transparent; 27 | } 28 | /* HTML5 display-role reset for older browsers */ 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | display: block; 32 | } 33 | body { 34 | line-height: 1; 35 | } 36 | ol, ul { 37 | list-style: none; 38 | } 39 | blockquote, q { 40 | quotes: none; 41 | } 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { 44 | content: ''; 45 | content: none; 46 | } 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; 50 | } 51 | a{ 52 | text-decoration: none; 53 | &:hover { 54 | color: inherit; 55 | } 56 | &:-webkit-any-link { 57 | color: inherit; 58 | } 59 | } 60 | input,button,select,textarea{outline:none;} 61 | :focus { 62 | outline:0; 63 | } 64 | -------------------------------------------------------------------------------- /src/styles/base/value.scss: -------------------------------------------------------------------------------- 1 | $theme_color: ( 2 | default: ( 3 | color_background: #f1f1f1, 4 | color_cell_bg_active: #ebeeef, 5 | color_tit_bg: #4ea9d4, 6 | color_tit_gradient_bg: ( 7 | webkit: -webkit-linear-gradient(-70deg, #3e8ad9 40%, #73c3bc 95%), 8 | moz: -moz-linear-gradient(-70deg, #3e8ad9 40%, #73c3bc 95%), 9 | o: -o-linear-gradient(-70deg, #3e8ad9 40%, #73c3bc 95%) 10 | ), 11 | color_bubble_own: #fff, 12 | color_bubble_own_bg: #4ea9d4, 13 | color_bubble_own_border: #4ea9d4, 14 | color_bubble_other: rgba(#000, .8), 15 | color_bubble_other_bg: #fff, 16 | color_bubble_other_border: #fff, 17 | color_pop_bubble: #43698a, 18 | color_pop_bubble_active: #335675, 19 | color_pop_bubble_bg: #fff, 20 | color_pop_bubble_bg_active: #c6dce8, 21 | color_dialog_bg: #fff, 22 | color_dialog_tit_bg: #4ea9d4, 23 | color_dialog_input_border: #4ea9d4, 24 | color_persend_bg: #eaeaea, 25 | color_persend_input_border: #ddd, 26 | color_persend_input_border_active: #4ea9d4, 27 | color_presend_btn_bg: #4ea9d4, 28 | color_presend_btn_bg_disable: #c8c8c8, 29 | color_overview_displayname: #fff, 30 | shadow_pop_bubble: 0 0 7px -3px #445883, 31 | shadow_overview_avatar: 0 3px 15px -4px #555, 32 | ), 33 | pink: ( 34 | color_background: #f1f1f1, 35 | color_cell_bg_active: #f7e0e0, 36 | color_tit_bg: #ea7575, 37 | color_tit_gradient_bg: (), 38 | color_bubble_own: #fff, 39 | color_bubble_own_bg: #ea7575, 40 | color_bubble_own_border: #ea7575, 41 | color_bubble_other: rgba(#000, .8), 42 | color_bubble_other_bg: #f9f4bf, 43 | color_bubble_other_border: #d3d3d3, 44 | color_pop_bubble: #ea7575, 45 | color_pop_bubble_active: #335675, 46 | color_pop_bubble_bg: #fff, 47 | color_pop_bubble_bg_active: #c6dce8, 48 | color_dialog_bg: #fff, 49 | color_dialog_tit_bg: #ea7575, 50 | color_dialog_input_border: #ea7575, 51 | color_persend_bg: #eaeaea, 52 | color_persend_border: #dcdbdb, 53 | color_persend_input_border: #ddd, 54 | color_persend_input_border_active: #ea7575, 55 | color_presend_btn_bg: #ea7575, 56 | color_presend_btn_bg_disable: #c8c8c8, 57 | color_overview_bg: #fdf8f8, 58 | color_overview_tit: #ea7575, 59 | color_overview_displayname: #b54141, 60 | color_overview_cell_bg_active: #fffef0, 61 | shadow_pop_bubble: 0 0 7px -3px #445883, 62 | shadow_overview_avatar: none, 63 | ), 64 | dark: ( 65 | color: #999, 66 | color_background: #313538, 67 | color_bubble_own: rgba(#fff, .6), 68 | color_bubble_own_bg: #444, 69 | color_bubble_own_border: #444, 70 | color_bubble_other: rgba(#fff, .6), 71 | color_bubble_other_bg: #444, 72 | color_bubble_other_border: #444, 73 | color_cell_bg_active: #3a3f45, 74 | color_cell_line: #292c33, 75 | color_cell_sub_text: #616161, 76 | color_dialog_bg: #313538, 77 | color_dialog_btn: #aaa, 78 | color_dialog_btn_bg: #292929, 79 | color_dialog_btn_bg_active: #666, 80 | color_dialog_btn_line: #444, 81 | color_dialog_tit: #aaa, 82 | color_dialog_tit_bg: #292929, 83 | color_dialog_input: rgba(#fff, .6), 84 | color_dialog_input_border: rgba(#fff, .15), 85 | color_pop_bubble: rgba(#fff, .6), 86 | color_pop_bubble_active: rgba(#fff, .6), 87 | color_pop_bubble_bg: #565b5f, 88 | color_pop_bubble_bg_active: #4c4c4c, 89 | color_persend_bg: #47484a, 90 | color_persend_input: #292929, 91 | color_persend_input_bg: #666, 92 | color_persend_input_border: #424242, 93 | color_persend_input_active: #999, 94 | color_presend_btn: #b7b7b7, 95 | color_presend_btn_bg: #5d5e61, 96 | color_presend_btn_bg_disable: #5d5e61, 97 | color_presend_btn_bg_active: #76777b, 98 | color_tit: #ddd, 99 | color_tit_bg: #292929, 100 | color_tit_gradient_bg: (), 101 | color_overview_bg: #313538, 102 | color_overview_cell: rgba(#fff, 0.6), 103 | color_overview_cell_bg: #47484a, 104 | color_overview_cell_bg_active: #3c3c3e, 105 | color_overview_cell_line: rgba(#fff, .15), 106 | color_overview_displayname: rgba(#fff, .8), 107 | color_overview_tit: rgba(#fff, .8), 108 | shadow_pop_bubble: 0 0 7px -3px #445883, 109 | shadow_overview_avatar: none, 110 | ), 111 | gold: ( 112 | color: #000, 113 | color_background: #e6e6e8, 114 | color_bubble_own: #f1cb26, 115 | color_bubble_own_bg: #444, 116 | color_bubble_own_border: #444, 117 | color_bubble_other: #000, 118 | color_bubble_other_bg: #fff, 119 | color_bubble_other_border: #444, 120 | color_cell_bg_active: rgba(#555, .1), 121 | color_cell_line: #656565, 122 | color_cell_sub_text: #868276, 123 | color_dialog_bg: #fff, 124 | // color_dialog_btn: #000, 125 | // color_dialog_btn_bg: #000, 126 | // color_dialog_btn_bg_active: #000, 127 | // color_dialog_btn_line: #000, 128 | color_dialog_tit: #f1cb26, 129 | color_dialog_tit_bg: #444, 130 | // color_dialog_input: rgba(#000, .6), 131 | // color_dialog_input_border: rgba(#000, .15), 132 | color_pop_bubble: #f1cb26, 133 | color_pop_bubble_active: #f1cb26, 134 | color_pop_bubble_bg: #444, 135 | color_pop_bubble_bg_active: #5a5a5a, 136 | color_persend_bg: #eaeaea, 137 | color_persend_input: '', 138 | color_persend_input_bg: '', 139 | color_persend_input_border: #ddd, 140 | color_persend_input_border_active: #444, 141 | color_persend_input_active: '', 142 | color_presend_btn: #f1cb26, 143 | color_presend_btn_disable: rgba(#fff, .8), 144 | color_presend_btn_bg: #444, 145 | color_presend_btn_bg_disable: #717171, 146 | color_presend_btn_bg_active: #666, 147 | color_tit: #f1cb26, 148 | color_tit_bg: #444, 149 | color_tit_gradient_bg: (), 150 | // color_overview_bg: #000, 151 | // color_overview_cell: rgba(#000, 0.6), 152 | // color_overview_cell_bg: #000, 153 | // color_overview_cell_bg_active: #000, 154 | // color_overview_cell_line: rgba(#000, .15), 155 | color_overview_displayname: #f1cb26, 156 | color_overview_tit: #f1cb26, 157 | shadow_pop_bubble: 0 0 0px 1px #444, 158 | shadow_overview_avatar: none, 159 | ) 160 | ); -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | @import "./base/value.scss"; 2 | @each $theme, $value in $theme_color { 3 | .theme-#{$theme} { 4 | color: map-get($value, color); 5 | background: map-get($value, color_background); 6 | .chat-header { 7 | color: map-get($value, color_tit); 8 | background-color: map-get($value, color_tit_bg); 9 | @each $g in map-values(map-get($value, color_tit_gradient_bg)) { 10 | background-image: $g; 11 | } 12 | } 13 | .chat-cell { 14 | .msg, .time { 15 | color: map-get($value, color_cell_sub_text); 16 | } 17 | &:nth-child(n+2):before { 18 | background: map-get($value, color_cell_line);; 19 | } 20 | &:active { 21 | background: map-get($value, color_cell_bg_active); 22 | } 23 | } 24 | .chat-dialog { 25 | background: map-get($value, color_dialog_bg); 26 | .title { 27 | color: map-get($value, color_dialog_tit); 28 | background: map-get($value, color_dialog_tit_bg); 29 | } 30 | .content { 31 | input { 32 | color: map-get($value, color_dialog_input); 33 | border-bottom-color: map-get($value, color_dialog_input_border); 34 | } 35 | } 36 | .btns button { 37 | background: map-get($value, color_dialog_btn_bg); 38 | color: map-get($value, color_dialog_btn); 39 | & + button { 40 | border-left-color: map-get($value, color_dialog_btn_line); 41 | } 42 | &:active { 43 | background: map-get($value, color_dialog_btn_bg_active); 44 | } 45 | } 46 | } 47 | .chat-area { 48 | .content .msg-content { 49 | .msg-wrap { 50 | color: map-get($value, color_bubble_other); 51 | background: map-get($value, color_bubble_other_bg); 52 | border-color: map-get($value, color_bubble_other_border); 53 | @if $theme == pink or $theme == dark or $theme == gold { 54 | box-shadow: none; 55 | } 56 | &:before { 57 | border-right-color: map-get($value, color_bubble_other_bg); 58 | } 59 | &:after { 60 | border-right-color: map-get($value, color_bubble_other_border); 61 | } 62 | } 63 | .own .msg-wrap { 64 | color: map-get($value, color_bubble_own); 65 | background: map-get($value, color_bubble_own_bg); 66 | &:before { 67 | border-left-color: map-get($value, color_bubble_own_bg); 68 | } 69 | &:after { 70 | border-left-color: map-get($value, color_bubble_own_border); 71 | } 72 | } 73 | } 74 | .pre-bottom { 75 | background: map-get($value, color_persend_bg); 76 | border-top-color: map-get($value, color_persend_border); 77 | input { 78 | color: map-get($value, color_persend_input); 79 | background: map-get($value, color_persend_input_bg); 80 | border-color: map-get($value, color_persend_input_border); 81 | &:focus { 82 | background: map-get($value, color_persend_input_active); 83 | border-color: map-get($value, color_persend_input_border_active); 84 | } 85 | } 86 | button { 87 | background: map-get($value, color_presend_btn_bg); 88 | @if $theme == dark or $theme == gold { 89 | &:active:after { 90 | opacity: 0; 91 | } 92 | } 93 | i { 94 | color: map-get($value, color_presend_btn); 95 | } 96 | &.disable { 97 | background: map-get($value, color_presend_btn_bg_disable)!important; 98 | i { 99 | color: map-get($value, color_presend_btn_disable)!important; 100 | } 101 | } 102 | &:active { 103 | background: map-get($value, color_presend_btn_bg_active); 104 | } 105 | } 106 | } 107 | } 108 | .pop-bubble { 109 | background: map-get($value, color_pop_bubble_bg); 110 | box-shadow: map-get($value, shadow_pop_bubble); 111 | .iconfont { 112 | color: map-get($value, color_pop_bubble); 113 | } 114 | &:active { 115 | background: map-get($value, color_pop_bubble_bg_active); 116 | .iconfont { 117 | color: map-get($value, color_pop_bubble_active); 118 | } 119 | } 120 | } 121 | .overview { 122 | background: map-get($value, color_overview_bg); 123 | .top { 124 | .title { 125 | color: map-get($value, color_overview_tit); 126 | } 127 | .avatar { 128 | box-shadow: map-get($value, shadow_overview_avatar); 129 | } 130 | .display-name { 131 | color: map-get($value, color_overview_displayname); 132 | } 133 | } 134 | .top .title .menus { 135 | color: map-get($value, color_overview_cell); 136 | background: map-get($value, color_overview_cell_bg); 137 | li { 138 | &:active { 139 | background: map-get($value, color_overview_cell_bg_active); 140 | } 141 | + li { 142 | border-top-color: map-get($value, color_overview_cell_line); 143 | } 144 | } 145 | } 146 | table { 147 | color: map-get($value, color_overview_cell); 148 | background: map-get($value, color_overview_cell_bg); 149 | tr:active { 150 | background: map-get($value, color_overview_cell_bg_active); 151 | } 152 | tr:nth-child(n+2) { 153 | border-color: map-get($value, color_overview_cell_line); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | .theme-dark { 160 | .theme { 161 | background: #313538; 162 | .theme-select .card { 163 | background: #292b2d; 164 | box-shadow: 0 0 4px -1px #fff; 165 | } 166 | } 167 | .pay { 168 | background: #313538; 169 | .logo:before, .logo:after { 170 | background:#666; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuffish/ChatUI/6a671921ba72e15c66e5a9c94758ddb58fd1b4bb/static/.gitkeep --------------------------------------------------------------------------------