index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
  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: this.lastContentRender(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: this.lastContentRender(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. lastContentRender(message) {
  491. return lastContentRender[message.type].call(this, message);
  492. },
  493. /**
  494. * 将字符串内的 EmojiItem.name 替换为 img
  495. * @param {String} str 被替换的字符串
  496. * @return {String} 替换后的字符串
  497. */
  498. replaceEmojiName(str) {
  499. return str.replace(/\[!(\w+)\]/gi, (str, match) => {
  500. const file = match;
  501. return emojiMap[file]
  502. ? `<img src="${emojiMap[file]}" />`
  503. : `[!${match}]`;
  504. });
  505. },
  506. /**
  507. * 将当前聊天窗口滚动到底部
  508. */
  509. messageViewToBottom() {
  510. this.$refs.messages.scrollToBottom();
  511. },
  512. /**
  513. * 改变聊天对象
  514. * @param contactId 联系人 id
  515. */
  516. changeContact(contactId, menuName) {
  517. if (this.currentContactId == contactId) {
  518. this.currentContactId = undefined;
  519. }
  520. if (menuName) {
  521. this.changeMenu(menuName);
  522. }
  523. this.currentContactId = contactId;
  524. this.$emit("change-contact", this.currentContact);
  525. if (isFunction(this.currentContact.renderContainer)) {
  526. return;
  527. }
  528. if (this._menuIsMessages()) {
  529. if (!CacheMessageLoaded.has(contactId)) {
  530. this.$refs.messages.resetLoadState();
  531. }
  532. if (!messages[contactId]) {
  533. this.$emit(
  534. "pull-messages",
  535. this.currentContact,
  536. (messages, isEnd) => {
  537. this._addMessage(messages, contactId, 0);
  538. this.messageViewToBottom();
  539. }
  540. );
  541. } else {
  542. this.messageViewToBottom();
  543. }
  544. }
  545. },
  546. /**
  547. * 删除一条聊天消息
  548. * @param messageId 消息 id
  549. * @param contactId 联系人 id
  550. */
  551. removeMessage(messageId, contactId) {
  552. const index = this.findMessageIndexById(messageId, contactId);
  553. if (index !== -1) {
  554. messages[contactId].splice(index, 1);
  555. this.forceUpdateMessage();
  556. }
  557. },
  558. /**
  559. * 修改聊天一条聊天消息
  560. * @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
  561. * @param contactId 联系人 id
  562. */
  563. updateMessage(messageId, contactId, data) {
  564. const index = this.findMessageIndexById(messageId, contactId);
  565. if (index !== -1) {
  566. messages[contactId][index] = {
  567. ...messages[contactId][index],
  568. ...data
  569. };
  570. this.forceUpdateMessage(messageId);
  571. }
  572. },
  573. /**
  574. * 手动更新对话消息
  575. * @param {String} messageId 消息ID,如果为空则更新当前聊天窗口的所有消息
  576. */
  577. forceUpdateMessage(messageId) {
  578. if (!messageId) {
  579. this.$refs.messages.$forceUpdate();
  580. } else {
  581. const components = this.$refs.messages.$refs.message;
  582. if (components) {
  583. const messageComponent = components.find(
  584. com => com.$attrs.message.id == messageId
  585. );
  586. if (messageComponent) messageComponent.$forceUpdate();
  587. }
  588. }
  589. },
  590. _customContainerReady(render, cacheDrive, key) {
  591. if (isFunction(render) && !cacheDrive.has(key)) {
  592. cacheDrive.set(key, render.call(this));
  593. }
  594. },
  595. /**
  596. * 切换左侧按钮
  597. * @param {String} name 按钮 name
  598. */
  599. changeMenu(name) {
  600. this.$emit("change-menu", name);
  601. this.activeSidebar = name;
  602. const { renderContainer } = this.currentMenu;
  603. this._customContainerReady(renderContainer, CacheMenuContainer, name);
  604. },
  605. /**
  606. * 初始化编辑框的 Emoji 表情列表,是 Lemon-editor.initEmoji 的代理方法
  607. * @param {Array<Emoji,EmojiItem>} data emoji 数据
  608. * Emoji = {label: 表情,children: [{name: wx,title: 微笑,src: url}]} 分组
  609. * EmojiItem = {name: wx,title: 微笑,src: url} 无分组
  610. */
  611. initEmoji(data) {
  612. this.$refs.editor.initEmoji(data);
  613. if (data[0].label) {
  614. data = data.flatMap(item => item.children);
  615. }
  616. data.forEach(({ name, src }) => (emojiMap[name] = src));
  617. },
  618. /**
  619. * 初始化左侧按钮
  620. * @param {Array<Menu>} data 按钮数据
  621. */
  622. initMenus(data) {
  623. const defaultMenus = [
  624. {
  625. name: DEFAULT_MENU_LASTMESSAGES,
  626. title: "聊天",
  627. unread: 0,
  628. click: null,
  629. render: menu => {
  630. return <i class="lemon-icon-message" />;
  631. },
  632. isBottom: false
  633. },
  634. {
  635. name: DEFAULT_MENU_CONTACTS,
  636. title: "通讯录",
  637. unread: 0,
  638. click: null,
  639. render: menu => {
  640. return <i class="lemon-icon-addressbook" />;
  641. },
  642. isBottom: false
  643. }
  644. ];
  645. let menus = [];
  646. if (Array.isArray(data)) {
  647. const indexMap = {
  648. lastMessages: 0,
  649. contacts: 1
  650. };
  651. const indexKeys = Object.keys(indexMap);
  652. menus = data.map(item => {
  653. if (indexKeys.includes(item.name)) {
  654. return {
  655. ...defaultMenus[indexMap[item.name]],
  656. ...item,
  657. ...{ renderContainer: null }
  658. };
  659. }
  660. return item;
  661. });
  662. } else {
  663. menus = defaultMenus;
  664. }
  665. this.menus = menus;
  666. },
  667. /**
  668. * 初始化联系人数据
  669. * @param {Array<Contact>} data 联系人列表
  670. */
  671. initContacts(data) {
  672. this.contacts.push(...data);
  673. this.sortContacts();
  674. },
  675. /**
  676. * 使用 联系人的 index 值进行排序
  677. */
  678. sortContacts() {
  679. this.contacts.sort((a, b) => {
  680. return a.index.localeCompare(b.index);
  681. });
  682. },
  683. /**
  684. * 修改联系人数据
  685. * @param {Contact} data 修改的数据,根据 data.id 查找联系人并覆盖传入的值
  686. */
  687. updateContact(contactId, data) {
  688. delete data.id;
  689. delete data.toContactId;
  690. const index = this.findContactIndexById(contactId);
  691. if (index !== -1) {
  692. const { unread } = data;
  693. if (isString(unread)) {
  694. if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
  695. data.unread =
  696. parseInt(unread) + parseInt(this.contacts[index].unread);
  697. }
  698. }
  699. this.$set(this.contacts, index, {
  700. ...this.contacts[index],
  701. ...data
  702. });
  703. }
  704. },
  705. /**
  706. * 根据 id 查找联系人的索引
  707. * @param contactId 联系人 id
  708. * @return {Number} 联系人索引,未找到返回 -1
  709. */
  710. findContactIndexById(contactId) {
  711. return this.contacts.findIndex(item => item.id == contactId);
  712. },
  713. findMessageIndexById(messageId, contactId) {
  714. const msg = messages[contactId];
  715. if (isEmpty(msg)) {
  716. return -1;
  717. }
  718. return msg.findIndex(item => item.id == messageId);
  719. },
  720. findMessageById(messageId, contactId) {
  721. const index = this.findMessageIndexById(messageId, contactId);
  722. if (index !== -1) return messages[contactId][index];
  723. },
  724. /**
  725. * 返回所有联系人
  726. * @return {Array<Contact>}
  727. */
  728. getContacts() {
  729. return this.contacts;
  730. },
  731. /**
  732. * 返回所有消息
  733. * @return {Object<Contact.id,Message>}
  734. */
  735. getMessages() {
  736. return messages;
  737. },
  738. // appendContact(data) {
  739. // this._addContact(data, 0);
  740. // },
  741. // prependContact(data) {
  742. // this._addContact(data, 1);
  743. // },
  744. // addContactMessage(data) {
  745. // this._addContact(data, 0);
  746. // },
  747. // prependContactMessage(data) {
  748. // this._addContact(data, 1);
  749. // },
  750. // appendMessage(data) {},
  751. // prependMessage(data) {},
  752. // removeContact(contactId) {},
  753. // removeContactMessage(contactId) {},
  754. // removeContactAll(contactId) {},
  755. /**
  756. * 将自定义的HTML显示在主窗口内
  757. */
  758. openrenderContainer(vnode) {
  759. //renderContainerQueue[this.activeSidebar] = vnode;
  760. //this.$slots._renderContainer = vnode;
  761. },
  762. changeDrawer(render) {
  763. this.drawerVisible = !this.drawerVisible;
  764. if (this.drawerVisible == true) this.openDrawer(render);
  765. },
  766. openDrawer(render) {
  767. renderDrawerContent = render || new Function();
  768. this.drawerVisible = true;
  769. },
  770. closeDrawer() {
  771. this.drawerVisible = false;
  772. }
  773. }
  774. };
  775. </script>
  776. <style lang="stylus">
  777. wrapper-width = 850px
  778. drawer-width = 200px
  779. bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
  780. @import '~styles/utils/index'
  781. +b(lemon-wrapper)
  782. width wrapper-width
  783. height 580px
  784. display flex
  785. font-size 14px
  786. //mask-image radial-gradient(circle, white 100%, black 100%)
  787. background #efefef
  788. transition all .4s bezier
  789. position relative
  790. p
  791. margin 0
  792. img
  793. vertical-align middle
  794. border-style none
  795. +b(lemon-menu)
  796. flex-column()
  797. align-items center
  798. width 60px
  799. background #1d232a
  800. padding 15px 0
  801. position relative
  802. user-select none
  803. +e(bottom)
  804. flex-column()
  805. position absolute
  806. bottom 0
  807. +e(avatar)
  808. margin-bottom 20px
  809. cursor pointer
  810. +e(item)
  811. color #999
  812. cursor pointer
  813. padding 14px 10px
  814. max-width 100%
  815. +m(active)
  816. color #0fd547
  817. &:hover:not(.lemon-menu__item--active)
  818. color #eee
  819. word-break()
  820. > *
  821. font-size 24px
  822. .ant-badge-count
  823. display inline-block
  824. padding 0 4px
  825. height 18px
  826. line-height 16px
  827. min-width 18px
  828. .ant-badge-count
  829. .ant-badge-dot
  830. box-shadow 0 0 0 1px #1d232a
  831. +b(lemon-sidebar)
  832. width 250px
  833. background #efefef
  834. overflow-y auto
  835. scrollbar-light()
  836. +e(label)
  837. padding 6px 14px 6px 14px
  838. color #666
  839. font-size 12px
  840. margin 0
  841. +b(lemon-contact--active)
  842. background #d9d9d9
  843. +b(lemon-container)
  844. flex 1
  845. flex-column()
  846. background #f4f4f4
  847. word-break()
  848. position relative
  849. z-index 2
  850. +e(title)
  851. padding 15px 15px
  852. +e(displayname)
  853. font-size 16px
  854. +b(lemon-messages)
  855. flex 1
  856. height auto
  857. +b(lemon-drawer)
  858. position absolute
  859. top 0
  860. right 0
  861. overflow hidden
  862. background #f4f4f4
  863. transition width .4s bezier
  864. z-index 1
  865. width drawer-width
  866. height 100%
  867. box-sizing border-box
  868. //border-left 1px solid #e9e9e9
  869. +b(lemon-wrapper)
  870. +m(drawer-show)
  871. +b(lemon-drawer)
  872. right -200px
  873. +b(lemon-contact-info)
  874. flex-column()
  875. justify-content center
  876. align-items center
  877. height 100%
  878. h4
  879. font-size 16px
  880. font-weight normal
  881. margin 10px 0 20px 0
  882. user-select none
  883. </style>