index.vue 24 KB

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