index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. <script>
  2. import { useScopedSlot, fastDone, generateUUID } from "utils";
  3. import { isFunction, isString, isEmpty } from "utils/validate";
  4. import {
  5. DEFAULT_MENUS,
  6. DEFAULT_MENU_LASTMESSAGES,
  7. DEFAULT_MENU_CONTACTS
  8. } from "utils/constant";
  9. import lastContentRender from "../lastContentRender";
  10. import MemoryCache from "utils/cache/memory";
  11. const CacheContactContainer = new MemoryCache();
  12. const CacheMenuContainer = new MemoryCache();
  13. const CacheMessageLoaded = new MemoryCache();
  14. import {
  15. //constraintContactMessages,
  16. constraintContact
  17. //constraintMessage
  18. } from "utils/constraint";
  19. const messages = {};
  20. const emojiMap = {};
  21. let renderDrawerContent = () => {};
  22. export default {
  23. name: "LemonImui",
  24. provide() {
  25. return {
  26. IMUI: this
  27. };
  28. },
  29. props: {
  30. /**
  31. * 消息时间格式化规则
  32. */
  33. messageTimeFormat: Function,
  34. /**
  35. * 联系人最新消息时间格式化规则
  36. */
  37. contactTimeFormat: Function,
  38. /**
  39. * 初始化时是否隐藏抽屉
  40. */
  41. hideDrawer: {
  42. type: Boolean,
  43. default: true
  44. },
  45. /**
  46. * 初始化时是否隐藏导航按钮上的头像
  47. */
  48. hideMenuAvatar: Boolean,
  49. user: {
  50. type: Object,
  51. default: () => {
  52. return {};
  53. }
  54. }
  55. },
  56. data() {
  57. return {
  58. drawerVisible: !this.hideDrawer,
  59. currentContactId: "",
  60. activeSidebar: DEFAULT_MENU_LASTMESSAGES,
  61. contacts: [],
  62. menus: []
  63. };
  64. },
  65. render() {
  66. return this._renderWrapper([
  67. this._renderMenu(),
  68. this._renderSidebarMessage(),
  69. this._renderSidebarContact(),
  70. this._renderContainer(),
  71. this._renderDrawer()
  72. ]);
  73. },
  74. created() {
  75. this.initMenus();
  76. },
  77. async mounted() {
  78. await this.$nextTick();
  79. },
  80. computed: {
  81. currentMessages() {
  82. return messages[this.currentContactId] || [];
  83. },
  84. currentContact() {
  85. return this.contacts.find(item => item.id == this.currentContactId) || {};
  86. },
  87. currentMenu() {
  88. return this.menus.find(item => item.name == this.activeSidebar) || {};
  89. },
  90. currentIsDefSidebar() {
  91. return DEFAULT_MENUS.includes(this.activeSidebar);
  92. },
  93. lastMessages() {
  94. const data = this.contacts.filter(item => !isEmpty(item.lastContent));
  95. data.sort((a1, a2) => {
  96. return a2.lastSendTime - a1.lastSendTime;
  97. });
  98. return data;
  99. }
  100. },
  101. watch: {
  102. activeSidebar() {}
  103. },
  104. methods: {
  105. _menuIsContacts() {
  106. return this.activeSidebar == DEFAULT_MENU_CONTACTS;
  107. },
  108. _menuIsMessages() {
  109. return this.activeSidebar == DEFAULT_MENU_LASTMESSAGES;
  110. },
  111. _createMessage(message) {
  112. return {
  113. ...{
  114. id: generateUUID(),
  115. type: "text",
  116. status: "going",
  117. sendTime: new Date().getTime(),
  118. toContactId: this.currentContactId,
  119. fromUser: {
  120. ...this.user
  121. }
  122. },
  123. ...message
  124. };
  125. // const message = {
  126. // id: "123",
  127. // status: "succeed",
  128. // type: "image",
  129. // sendTime: 12312312312,
  130. // content: "asdas",
  131. // fromContactId: "123",
  132. // fromUser: { id: "123", displayName: "123", avatar: "123",}
  133. // }
  134. },
  135. // _setDefMessages(id) {
  136. // //this.messages[id] = this.messages[id] || [];
  137. // if (!messages[id]) {
  138. // this.$set(messages, id, []);
  139. // }
  140. // },
  141. appendMessage(message, contactId = this.currentContactId) {
  142. this._addMessage(message, contactId, 1);
  143. this.messageViewToBottom();
  144. },
  145. _emitSend(message, next, file) {
  146. this.$emit(
  147. "send",
  148. message,
  149. (replaceMessage = { status: "succeed" }) => {
  150. next();
  151. message = Object.assign(message, replaceMessage);
  152. this.forceUpdateMessage(message.id);
  153. },
  154. file
  155. );
  156. },
  157. _handleSend(text) {
  158. const message = this._createMessage({ content: text });
  159. this.appendMessage(message);
  160. this._emitSend(message, () => {
  161. this.updateContact(message.toContactId, {
  162. lastContent: lastContentRender[message.type].call(this, message),
  163. lastSendTime: message.sendTime
  164. });
  165. });
  166. },
  167. _handleUpload(file) {
  168. const imageTypes = ["image/gif", "image/jpeg", "image/png"];
  169. let joinMessage;
  170. if (imageTypes.includes(file.type)) {
  171. joinMessage = {
  172. type: "image",
  173. content: URL.createObjectURL(file)
  174. };
  175. } else {
  176. joinMessage = {
  177. type: "file",
  178. fileSize: file.size,
  179. fileName: file.name,
  180. content: ""
  181. };
  182. }
  183. const message = this._createMessage(joinMessage);
  184. this.appendMessage(message);
  185. this._emitSend(
  186. message,
  187. () => {
  188. this.updateContact(message.toContactId, {
  189. lastContent: lastContentRender[message.type].call(this, message),
  190. lastSendTime: message.sendTime
  191. });
  192. },
  193. file
  194. );
  195. },
  196. _handleReachTop(next) {
  197. // const messages = {
  198. // id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
  199. // type: "text",
  200. // status: "succeed",
  201. // sendTime: 1564926674646,
  202. // fromContactId: "superadmin",
  203. // fromUser: {
  204. // id: "hehe",
  205. // displayName: "I KNOEW",
  206. // avatar:
  207. // "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
  208. // },
  209. // content: "测试消息哦..."
  210. // };
  211. this.$emit(
  212. "pull-messages",
  213. this.currentContact,
  214. (messages, isEnd = false) => {
  215. this._addMessage(
  216. Array(10).fill(messages[1]),
  217. this.currentContactId,
  218. 0
  219. );
  220. CacheMessageLoaded.set(this.currentContactId, isEnd);
  221. next(isEnd);
  222. }
  223. );
  224. // setTimeout(() => {
  225. // CacheMessageLoaded.set(this.currentContactId, isEnd);
  226. // }, 2000);
  227. },
  228. clearCacheContainer(name) {
  229. CacheContactContainer.remove(name);
  230. CacheMenuContainer.remove(name);
  231. },
  232. _renderWrapper(children) {
  233. return (
  234. <div
  235. class={[
  236. "lemon-wrapper",
  237. this.drawerVisible && "lemon-wrapper--drawer-show"
  238. ]}
  239. >
  240. {children}
  241. </div>
  242. );
  243. },
  244. _renderMenu() {
  245. const menuItem = this._renderMenuItem();
  246. return (
  247. <div class="lemon-menu">
  248. {this.hideMenuAvatar == false && (
  249. <lemon-avatar
  250. on-click={e => {
  251. console.log("menu avatar click");
  252. }}
  253. class="lemon-menu__avatar"
  254. src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=400062461,2874561526&fm=26&gp=0.jpg"
  255. />
  256. )}
  257. {menuItem.top}
  258. {this.$slots.menu}
  259. <div class="lemon-menu__bottom">
  260. {this.$slots["menu-bottom"]}
  261. {menuItem.bottom}
  262. </div>
  263. </div>
  264. );
  265. },
  266. _renderMenuAvatar() {
  267. return;
  268. },
  269. _renderMenuItem() {
  270. const top = [];
  271. const bottom = [];
  272. this.menus.forEach(item => {
  273. const { name, title, unread, render, click } = item;
  274. const node = (
  275. <div
  276. class={[
  277. "lemon-menu__item",
  278. { "lemon-menu__item--active": this.activeSidebar == name }
  279. ]}
  280. on-click={() => {
  281. fastDone(click, () => {
  282. if (name) this.changeMenu(name);
  283. });
  284. }}
  285. title={title}
  286. >
  287. <lemon-badge count={unread}>{render(item)}</lemon-badge>
  288. </div>
  289. );
  290. item.isBottom === true ? bottom.push(node) : top.push(node);
  291. });
  292. return {
  293. top,
  294. bottom
  295. };
  296. },
  297. _renderSidebarMessage() {
  298. return this._renderSidebar(
  299. this.lastMessages.map(contact => {
  300. return this._renderContact(
  301. {
  302. contact,
  303. timeFormat: this.contactTimeFormat
  304. },
  305. () => this.changeContact(contact.id)
  306. );
  307. }),
  308. DEFAULT_MENU_LASTMESSAGES
  309. );
  310. },
  311. _renderContact(props, onClick) {
  312. const {
  313. click: customClick,
  314. renderContainer,
  315. id: contactId
  316. } = props.contact;
  317. const click = () => {
  318. fastDone(customClick, () => {
  319. onClick();
  320. this._customContainerReady(
  321. renderContainer,
  322. CacheContactContainer,
  323. contactId
  324. );
  325. });
  326. };
  327. return (
  328. <lemon-contact
  329. class={{
  330. "lemon-contact--active": this.currentContactId == props.contact.id
  331. }}
  332. props={props}
  333. on-click={click}
  334. />
  335. );
  336. },
  337. _renderSidebarContact() {
  338. let prevIndex;
  339. return this._renderSidebar(
  340. this.contacts.map(contact => {
  341. contact.index = contact.index.replace(/\[[0-9]*\]/, "");
  342. const node = [
  343. contact.index !== prevIndex && (
  344. <p class="lemon-sidebar__label">{contact.index}</p>
  345. ),
  346. this._renderContact(
  347. {
  348. contact: contact,
  349. simple: true
  350. },
  351. () => this.changeContact(contact.id)
  352. )
  353. ];
  354. prevIndex = contact.index;
  355. return node;
  356. }),
  357. DEFAULT_MENU_CONTACTS
  358. );
  359. },
  360. _renderSidebar(children, name) {
  361. return (
  362. <div class="lemon-sidebar" v-show={this.activeSidebar == name}>
  363. {children}
  364. </div>
  365. );
  366. },
  367. _renderDrawer() {
  368. return this._menuIsMessages() && this.currentContactId ? (
  369. <div class="lemon-drawer">
  370. {renderDrawerContent()}
  371. {useScopedSlot(this.$scopedSlots.drawer, "", this.currentContact)}
  372. </div>
  373. ) : (
  374. ""
  375. );
  376. },
  377. _isContactContainerCache(name) {
  378. return name.startsWith("contact#");
  379. },
  380. _renderContainer() {
  381. const nodes = [];
  382. const cls = "lemon-container";
  383. const curact = this.currentContact;
  384. let defIsShow = true;
  385. for (const name in CacheContactContainer.get()) {
  386. const show = curact.id == name && this.currentIsDefSidebar;
  387. defIsShow = !show;
  388. nodes.push(
  389. <div class={cls} v-show={show}>
  390. {CacheContactContainer.get(name)}
  391. </div>
  392. );
  393. }
  394. for (const name in CacheMenuContainer.get()) {
  395. nodes.push(
  396. <div
  397. class={cls}
  398. v-show={this.activeSidebar == name && !this.currentIsDefSidebar}
  399. >
  400. {CacheMenuContainer.get(name)}
  401. </div>
  402. );
  403. }
  404. nodes.push(
  405. <div
  406. class={cls}
  407. v-show={this._menuIsMessages() && defIsShow && curact.id}
  408. >
  409. <div class="lemon-container__title">
  410. <div class="lemon-container__displayname">
  411. {useScopedSlot(
  412. this.$scopedSlots["contact-title"],
  413. curact.displayName,
  414. curact
  415. )}
  416. </div>
  417. </div>
  418. <lemon-messages
  419. ref="messages"
  420. time-format={this.messageTimeFormat}
  421. reverse-user-id={this.user.id}
  422. on-reach-top={this._handleReachTop}
  423. messages={this.currentMessages}
  424. />
  425. <lemon-editor
  426. ref="editor"
  427. onSend={this._handleSend}
  428. onUpload={this._handleUpload}
  429. />
  430. </div>
  431. );
  432. nodes.push(
  433. <div class={cls} v-show={!curact.id}>
  434. {this.$slots.cover}
  435. </div>
  436. );
  437. nodes.push(
  438. <div
  439. class={cls}
  440. v-show={this._menuIsContacts() && defIsShow && curact.id}
  441. >
  442. {useScopedSlot(
  443. this.$scopedSlots["contact-info"],
  444. <div class="lemon-contact-info">
  445. <lemon-avatar src={curact.avatar} size={90} />
  446. <h4>{curact.displayName}</h4>
  447. <lemon-button
  448. on-click={() => {
  449. this.changeContact(curact.id, DEFAULT_MENU_LASTMESSAGES);
  450. }}
  451. >
  452. {" "}
  453. 发送消息{" "}
  454. </lemon-button>
  455. </div>,
  456. curact
  457. )}
  458. </div>
  459. );
  460. return nodes;
  461. },
  462. _addContact(data, t) {
  463. const type = {
  464. 0: "unshift",
  465. 1: "push"
  466. }[t];
  467. constraintContact(data);
  468. //this.contacts[type](cloneDeep(data));
  469. this.contacts[type](data);
  470. },
  471. _addMessage(data, contactId, t) {
  472. const type = {
  473. 0: "unshift",
  474. 1: "push"
  475. }[t];
  476. if (!Array.isArray(data)) data = [data];
  477. messages[contactId] = messages[contactId] || [];
  478. messages[contactId][type](...data);
  479. //console.log(messages[contactId]);
  480. this.forceUpdateMessage();
  481. },
  482. /**
  483. * 设置最新消息DOM
  484. * @param {String} messageType 消息类型
  485. * @param {Function} render 返回消息 vnode
  486. */
  487. setLastContentRender(messageType, render) {
  488. lastContentRender[messageType] = render;
  489. },
  490. /**
  491. * 将字符串内的 EmojiItem.name 替换为 img
  492. * @param {String} str 被替换的字符串
  493. * @return {String} 替换后的字符串
  494. */
  495. replaceEmojiName(str) {
  496. return str.replace(/\[!(\w+)\]/gi, (str, match) => {
  497. const file = match;
  498. return emojiMap[file]
  499. ? `<img src="${emojiMap[file]}" />`
  500. : `[!${match}]`;
  501. });
  502. },
  503. /**
  504. * 将当前聊天窗口滚动到底部
  505. */
  506. messageViewToBottom() {
  507. this.$refs.messages.scrollToBottom();
  508. },
  509. /**
  510. * 改变聊天对象
  511. * @param contactId 联系人 id
  512. */
  513. changeContact(contactId, menuName) {
  514. if (this.currentContactId == contactId) {
  515. this.currentContactId = undefined;
  516. }
  517. if (menuName) {
  518. this.changeMenu(menuName);
  519. }
  520. this.currentContactId = contactId;
  521. this.$emit("change-contact", this.currentContact);
  522. if (isFunction(this.currentContact.renderContainer)) {
  523. return;
  524. }
  525. if (this._menuIsMessages()) {
  526. if (!CacheMessageLoaded.has(contactId)) {
  527. this.$refs.messages.resetLoadState();
  528. }
  529. if (!messages[contactId]) {
  530. this.$emit(
  531. "pull-messages",
  532. this.currentContact,
  533. (messages, isEnd) => {
  534. this._addMessage(messages, contactId, 0);
  535. this.messageViewToBottom();
  536. }
  537. );
  538. } else {
  539. this.messageViewToBottom();
  540. }
  541. }
  542. },
  543. /**
  544. * 删除一条聊天消息
  545. * @param messageId 消息 id
  546. * @param contactId 联系人 id
  547. */
  548. removeMessage(messageId, contactId) {
  549. const index = this.findMessageIndexById(messageId, contactId);
  550. if (index !== -1) {
  551. messages[contactId].splice(index, 1);
  552. this.forceUpdateMessage();
  553. }
  554. },
  555. /**
  556. * 修改聊天一条聊天消息
  557. * @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
  558. * @param contactId 联系人 id
  559. */
  560. updateMessage(messageId, contactId, data) {
  561. const index = this.findMessageIndexById(messageId, contactId);
  562. if (index !== -1) {
  563. messages[contactId][index] = {
  564. ...messages[contactId][index],
  565. ...data
  566. };
  567. this.forceUpdateMessage(messageId);
  568. }
  569. },
  570. /**
  571. * 手动更新对话消息
  572. * @param {String} messageId 消息ID,如果为空则更新当前聊天窗口的所有消息
  573. */
  574. forceUpdateMessage(messageId) {
  575. if (!messageId) {
  576. this.$refs.messages.$forceUpdate();
  577. } else {
  578. const components = this.$refs.messages.$refs.message;
  579. if (components) {
  580. const messageComponent = components.find(
  581. com => com.$attrs.message.id == messageId
  582. );
  583. if (messageComponent) messageComponent.$forceUpdate();
  584. }
  585. }
  586. },
  587. _customContainerReady(render, cacheDrive, key) {
  588. if (isFunction(render) && !cacheDrive.has(key)) {
  589. cacheDrive.set(key, render.call(this));
  590. }
  591. },
  592. /**
  593. * 切换左侧按钮
  594. * @param {String} name 按钮 name
  595. */
  596. changeMenu(name) {
  597. this.$emit("change-menu", name);
  598. this.activeSidebar = name;
  599. const { renderContainer } = this.currentMenu;
  600. this._customContainerReady(renderContainer, CacheMenuContainer, name);
  601. },
  602. /**
  603. * 初始化编辑框的 Emoji 表情列表,是 Lemon-editor.initEmoji 的代理方法
  604. * @param {Array<Emoji,EmojiItem>} data emoji 数据
  605. * Emoji = {label: 表情,children: [{name: wx,title: 微笑,src: url}]} 分组
  606. * EmojiItem = {name: wx,title: 微笑,src: url} 无分组
  607. */
  608. initEmoji(data) {
  609. this.$refs.editor.initEmoji(data);
  610. if (data[0].label) {
  611. data = data.flatMap(item => item.children);
  612. }
  613. data.forEach(({ name, src }) => (emojiMap[name] = src));
  614. },
  615. /**
  616. * 初始化左侧按钮
  617. * @param {Array<Menu>} data 按钮数据
  618. */
  619. initMenus(data) {
  620. const defaultMenus = [
  621. {
  622. name: DEFAULT_MENU_LASTMESSAGES,
  623. title: "聊天",
  624. unread: 0,
  625. click: null,
  626. render: menu => {
  627. return <i class="lemon-icon-message" />;
  628. },
  629. isBottom: false
  630. },
  631. {
  632. name: DEFAULT_MENU_CONTACTS,
  633. title: "通讯录",
  634. unread: 0,
  635. click: null,
  636. render: menu => {
  637. return <i class="lemon-icon-addressbook" />;
  638. },
  639. isBottom: false
  640. }
  641. ];
  642. let menus = [];
  643. if (Array.isArray(data)) {
  644. const indexMap = {
  645. lastMessages: 0,
  646. contacts: 1
  647. };
  648. const indexKeys = Object.keys(indexMap);
  649. menus = data.map(item => {
  650. if (indexKeys.includes(item.name)) {
  651. return {
  652. ...defaultMenus[indexMap[item.name]],
  653. ...item,
  654. ...{ renderContainer: null }
  655. };
  656. }
  657. return item;
  658. });
  659. } else {
  660. menus = defaultMenus;
  661. }
  662. this.menus = menus;
  663. },
  664. /**
  665. * 初始化联系人数据
  666. * @param {Array<Contact>} data 联系人列表
  667. */
  668. initContacts(data) {
  669. this.contacts.push(...data);
  670. this.sortContacts();
  671. },
  672. /**
  673. * 使用 联系人的 index 值进行排序
  674. */
  675. sortContacts() {
  676. this.contacts.sort((a, b) => {
  677. return a.index.localeCompare(b.index);
  678. });
  679. },
  680. /**
  681. * 修改联系人数据
  682. * @param {Contact} data 修改的数据,根据 data.id 查找联系人并覆盖传入的值
  683. */
  684. updateContact(contactId, data) {
  685. delete data.id;
  686. delete data.toContactId;
  687. const index = this.findContactIndexById(contactId);
  688. if (index !== -1) {
  689. const { unread } = data;
  690. if (isString(unread)) {
  691. if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
  692. data.unread =
  693. parseInt(unread) + parseInt(this.contacts[index].unread);
  694. }
  695. }
  696. this.$set(this.contacts, index, {
  697. ...this.contacts[index],
  698. ...data
  699. });
  700. }
  701. },
  702. /**
  703. * 根据 id 查找联系人的索引
  704. * @param contactId 联系人 id
  705. * @return {Number} 联系人索引,未找到返回 -1
  706. */
  707. findContactIndexById(contactId) {
  708. return this.contacts.findIndex(item => item.id == contactId);
  709. },
  710. findMessageIndexById(messageId, contactId) {
  711. const msg = messages[contactId];
  712. if (isEmpty(msg)) {
  713. return -1;
  714. }
  715. return msg.findIndex(item => item.id == messageId);
  716. },
  717. findMessageById(messageId, contactId) {
  718. const index = this.findMessageIndexById(messageId, contactId);
  719. if (index !== -1) return messages[contactId][index];
  720. },
  721. /**
  722. * 返回所有联系人
  723. * @return {Array<Contact>}
  724. */
  725. getContacts() {
  726. return this.contacts;
  727. },
  728. /**
  729. * 返回所有消息
  730. * @return {Object<Contact.id,Message>}
  731. */
  732. getMessages() {
  733. return messages;
  734. },
  735. // appendContact(data) {
  736. // this._addContact(data, 0);
  737. // },
  738. // prependContact(data) {
  739. // this._addContact(data, 1);
  740. // },
  741. // addContactMessage(data) {
  742. // this._addContact(data, 0);
  743. // },
  744. // prependContactMessage(data) {
  745. // this._addContact(data, 1);
  746. // },
  747. // appendMessage(data) {},
  748. // prependMessage(data) {},
  749. // removeContact(contactId) {},
  750. // removeContactMessage(contactId) {},
  751. // removeContactAll(contactId) {},
  752. /**
  753. * 将自定义的HTML显示在主窗口内
  754. */
  755. openrenderContainer(vnode) {
  756. //renderContainerQueue[this.activeSidebar] = vnode;
  757. //this.$slots._renderContainer = vnode;
  758. },
  759. changeDrawer(render) {
  760. this.drawerVisible = !this.drawerVisible;
  761. if (this.drawerVisible == true) this.openDrawer(render);
  762. },
  763. openDrawer(render) {
  764. renderDrawerContent = render || new Function();
  765. this.drawerVisible = true;
  766. },
  767. closeDrawer() {
  768. this.drawerVisible = false;
  769. }
  770. }
  771. };
  772. </script>
  773. <style lang="stylus">
  774. wrapper-width = 850px
  775. drawer-width = 200px
  776. bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
  777. @import '~styles/utils/index'
  778. +b(lemon-wrapper)
  779. width wrapper-width
  780. height 580px
  781. display flex
  782. font-size 14px
  783. border-radius 5px
  784. //mask-image radial-gradient(circle, white 100%, black 100%)
  785. background #efefef
  786. transition all .4s bezier
  787. border-radius 4px
  788. p
  789. margin 0
  790. img
  791. vertical-align middle
  792. border-style none
  793. +b(lemon-menu)
  794. flex-column()
  795. align-items center
  796. width 60px
  797. background #1d232a
  798. padding 15px 0
  799. position relative
  800. user-select none
  801. +e(bottom)
  802. flex-column()
  803. position absolute
  804. bottom 0
  805. +e(avatar)
  806. margin-bottom 20px
  807. cursor pointer
  808. +e(item)
  809. color #999
  810. cursor pointer
  811. padding 14px 10px
  812. max-width 100%
  813. +m(active)
  814. color #0fd547
  815. &:hover:not(.lemon-menu__item--active)
  816. color #eee
  817. word-break()
  818. > *
  819. font-size 24px
  820. .ant-badge-count
  821. display inline-block
  822. padding 0 4px
  823. height 18px
  824. line-height 16px
  825. min-width 18px
  826. .ant-badge-count
  827. .ant-badge-dot
  828. box-shadow 0 0 0 1px #1d232a
  829. +b(lemon-sidebar)
  830. width 250px
  831. background #efefef
  832. overflow-y auto
  833. scrollbar-light()
  834. +e(label)
  835. padding 6px 14px 6px 14px
  836. color #666
  837. font-size 12px
  838. margin 0
  839. +b(lemon-contact--active)
  840. background #d9d9d9
  841. +b(lemon-container)
  842. flex 1
  843. flex-column()
  844. background #f4f4f4
  845. word-break()
  846. position relative
  847. z-index 2
  848. +e(title)
  849. padding 15px 15px
  850. +e(displayname)
  851. font-size 16px
  852. +b(lemon-messages)
  853. flex 1
  854. height auto
  855. +b(lemon-drawer)
  856. position absolute
  857. top 0
  858. right 0
  859. overflow hidden
  860. width drawer-width
  861. background #f4f4f4
  862. transition width .4s bezier
  863. height 100%
  864. z-index 1
  865. //border-left 1px solid #e9e9e9
  866. box-sizing border-box
  867. +b(lemon-wrapper)
  868. +m(drawer-show)
  869. +b(lemon-drawer)
  870. right -200px
  871. +b(lemon-contact-info)
  872. flex-column()
  873. justify-content center
  874. align-items center
  875. height 100%
  876. h4
  877. font-size 16px
  878. font-weight normal
  879. margin 10px 0 20px 0
  880. user-select none
  881. </style>