index.vue 24 KB

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