index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. const SHARE_TO = (() => {
  2. const ShareType = {
  3. CREATE: 'create',
  4. UPDATE: 'update',
  5. CANCEL: 'cancel',
  6. };
  7. const { SharePermissionChangeType: PermissionType, PageTarget } = commonConstants;
  8. // 当前分享的项目ID
  9. let curProjectID;
  10. // 当前项目的已分享列表
  11. let curSharedUsers = [];
  12. // 清除缓存
  13. function clearCache() {
  14. curProjectID = null;
  15. curSharedUsers = [];
  16. }
  17. // 最近联系人显示数量
  18. const rencentCount = 5;
  19. // 获取初始数据:1.最近分享人 2.联系人 3.已分享人
  20. async function getInitalData(projectID) {
  21. return await ajaxPost('/pm/api/getInitialShareData', { user_id: userID, count: rencentCount, projectID }, false);
  22. }
  23. // 获取头像视图html
  24. function getAvatarHTML(mobile, realName) {
  25. // 手机最后一位
  26. const lastMobileNumer = mobile.substr(-1);
  27. // 显示名称为真实名称后两位
  28. const nickName = realName.substr(-2);
  29. return `<span class="avatar bg-${lastMobileNumer}">${nickName}</span>`;
  30. }
  31. /**
  32. * 获取用户列表视图html:最近分享和联系人用
  33. * @param {Array} users - 排序过的用户数据
  34. * @param {Boolean} showAlphabet - 是否显示字母表分类
  35. * @return {HTMLString}
  36. * */
  37. function getUserHTML(users, showAlphabet) {
  38. let curLetter = '';
  39. return users.reduce((html, user) => {
  40. const mobile = user.mobile || '';
  41. const realName = user.real_name || '';
  42. const company = user.company || '';
  43. if (showAlphabet) {
  44. // 名字首个字符对应拼音
  45. const letter = pinyinUtil.getFirstLetter(realName).substr(0, 1);
  46. if (letter !== curLetter) {
  47. curLetter = letter;
  48. html += `<li class="letter">${letter}</li>`;
  49. }
  50. }
  51. const avatarHtml = getAvatarHTML(mobile, realName);
  52. return html +
  53. `<li>
  54. ${avatarHtml}
  55. <div class="book-body">
  56. <h5 class="mt-0" title="${company}">${realName}</h5>
  57. <span>${mobile}</span>
  58. </div>
  59. </li>`;
  60. }, '');
  61. }
  62. // 初始化最近分享视图
  63. function initRecentView(recentUsers) {
  64. const recentShareHTML = getUserHTML(recentUsers, false);
  65. $('#recent-share').html(recentShareHTML);
  66. // 点击最近分享列表自动添加该用户添加到搜索框中
  67. $('#recent-share li').click(function () {
  68. const mobile = $(this).find('div span')[0].textContent;
  69. $('#share-phone').val(mobile);
  70. handleSearch()
  71. })
  72. }
  73. // 初始化联系人视图
  74. function initContactsView(contacts) {
  75. // 联系人按拼英首字母降序排序
  76. contacts.sort((a, b) => {
  77. const realNameA = a.real_name || '';
  78. const realNameB = b.real_name || '';
  79. return realNameA.localeCompare(realNameB, 'zh-Hans-CN', { sensitivity: 'accent' })
  80. });
  81. const contactsHTML = getUserHTML(contacts, true);
  82. $('#contacts').html(contactsHTML);
  83. // 点击联系人自动添加该联系人添加到搜索框中
  84. $('#contacts li:not(.letter)').click(function () {
  85. const mobile = $(this).find('div span')[0].textContent;
  86. $('#share-phone').val(mobile);
  87. $('#contacts-menue').removeClass('show');
  88. handleSearch()
  89. });
  90. }
  91. // 初始化已分享视图
  92. function initSharedView(sharedUsers) {
  93. const html = sharedUsers.reduce((html, user, index) => {
  94. const mobile = user.mobile || '';
  95. const realName = user.real_name || '';
  96. const company = user.company || '';
  97. const avatarHTML = getAvatarHTML(mobile, realName);
  98. const copyLabelFor = `allowCopy${index}`;
  99. const editLabelFor = `allowEdit${index}`;
  100. return html +
  101. `<li class="card mb-1">
  102. <div class="card-body p-1 row m-0">
  103. ${avatarHTML}
  104. <div class="book-body col-auto pl-0">
  105. <h5 class="mt-0">${realName}</h5>
  106. ${mobile}
  107. </div>
  108. <div class="col-5">${company}</div>
  109. <div class="col ml-auto p-0">
  110. <div class="d-flex justify-content-end">
  111. <div>
  112. <div class="custom-control custom-checkbox">
  113. <input type="checkbox" class="custom-control-input allow-copy" id="${copyLabelFor}" data-user="${user._id}" ${user.allowCopy ? 'checked' : ''}>
  114. <label class="custom-control-label" for="${copyLabelFor}">允许拷贝</label>
  115. </div>
  116. <div class="custom-control custom-checkbox">
  117. <input type="checkbox" class="custom-control-input allow-edit" id="${editLabelFor}" data-user="${user._id}" ${user.allowCooperate ? 'checked' : ''}>
  118. <label class="custom-control-label" for="${editLabelFor}">允许编辑</label>
  119. </div>
  120. </div>
  121. <div class="ml-3 d-flex align-items-center">
  122. <button class="btn btn-sm btn-outline-danger cancel-share" data-user="${user._id}">取消分享</button>
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. </li>`;
  128. }, '');
  129. $('#shared-list').html(html);
  130. // 编辑允许拷贝
  131. $('#shared-list .allow-copy').click(function () {
  132. handleCheckBoxClick.call(this);
  133. handleShareAction.call(this, ShareType.UPDATE);
  134. });
  135. // 编辑允许编辑
  136. $('#shared-list .allow-edit').click(function () {
  137. handleCheckBoxClick.call(this);
  138. handleShareAction.call(this, ShareType.UPDATE);
  139. });
  140. // 取消分享
  141. $('#shared-list .cancel-share').click(function () {
  142. handleShareAction.call(this, ShareType.CANCEL);
  143. });
  144. }
  145. // 初始化搜索结果视图
  146. function initSearchResultView(user) {
  147. if (!user) {
  148. $('#share-search-result').html('')
  149. } else {
  150. const mobile = user.mobile || '';
  151. const realName = user.real_name || '';
  152. const company = user.company || '';
  153. const avatarHTML = getAvatarHTML(mobile, realName);
  154. const html =
  155. `<li class="card mb-1">
  156. <div class="card-body p-1 row m-0">
  157. ${avatarHTML}
  158. <div class="book-body col-auto pl-0">
  159. <h5 class="mt-0">${realName}</h5>
  160. ${mobile}
  161. </div>
  162. <div class="col-5">${company}</div>
  163. <div class="col ml-auto p-0">
  164. <div class="d-flex justify-content-end">
  165. <div>
  166. <div class="custom-control custom-checkbox">
  167. <input type="checkbox" class="custom-control-input" id="allow-copy" checked="">
  168. <label class="custom-control-label" for="allow-copy">允许拷贝</label>
  169. </div>
  170. <div class="custom-control custom-checkbox">
  171. <input type="checkbox" class="custom-control-input" id="allow-edit">
  172. <label class="custom-control-label" for="allow-edit">允许编辑</label>
  173. </div>
  174. </div>
  175. <div class="ml-3 d-flex align-items-center"><button class="btn btn-sm btn-primary" id="share-to" data-user="${user._id}">分享给Ta</button></div>
  176. </div>
  177. </div>
  178. </div>
  179. </li>`;
  180. $('#share-search-result').html(html);
  181. // 允许拷贝
  182. $('#allow-copy').click(function () {
  183. handleCheckBoxClick.call(this);
  184. });
  185. // 允许编辑
  186. $('#allow-edit').click(function () {
  187. handleCheckBoxClick.call(this);
  188. });
  189. // 分享给事件
  190. $('#share-to').click(function () {
  191. handleShareAction.call(this, ShareType.CREATE, user);
  192. });
  193. }
  194. }
  195. // 复选框框的状态随点击更改,不处理的话input的checked并不会自动处理
  196. function handleCheckBoxClick() {
  197. const curChecked = !$(this).attr('checked');
  198. if (curChecked) {
  199. $(this).attr('checked', 'checked');
  200. } else {
  201. $(this).removeAttr('checked');
  202. }
  203. }
  204. // 添加分享、编辑分享、取消分享的动作
  205. async function handleShareAction(shareType, user) {
  206. try {
  207. $.bootstrapLoading.start();
  208. const receiver = $(this).data('user');
  209. let shareData;
  210. let type = ShareType.UPDATE;
  211. if (shareType === ShareType.CREATE) {
  212. const allowCopy = $('#allow-copy').prop('checked');
  213. const allowCooperate = $('#allow-edit').prop('checked');
  214. shareData = [{ userID: receiver, allowCopy, allowCooperate }];
  215. type = ShareType.CREATE; // 上传的服务器的type,删除跟更新是一样的
  216. } else if (shareType === ShareType.UPDATE) {
  217. const allowCopy = $(`[data-user=${receiver}].allow-copy`).prop('checked');
  218. const allowCooperate = $(`[data-user=${receiver}].allow-edit`).prop('checked');
  219. shareData = [{ userID: receiver, allowCopy, allowCooperate }];
  220. } else {
  221. shareData = [{ userID: receiver, isCancel: true }];
  222. }
  223. // 获取权限变更的类型
  224. const permissionType = getPermissionType(shareType, curSharedUsers, shareData[0]);
  225. const postData = {
  226. user_id: userID,
  227. type,
  228. permissionType,
  229. projectID: curProjectID,
  230. count: rencentCount,
  231. shareData
  232. };
  233. const rst = await ajaxPost('/pm/api/share', postData);
  234. // 请求成功后刷新视图
  235. if (shareType === ShareType.CREATE || shareType === ShareType.CANCEL) {
  236. if (shareType === ShareType.CREATE) {
  237. user.allowCopy = shareData[0].allowCopy;
  238. user.allowCooperate = shareData[0].allowCooperate;
  239. curSharedUsers.unshift(user);
  240. $('#share-phone').val('');
  241. initSearchResultView();
  242. } else {
  243. curSharedUsers = curSharedUsers.filter(user => user._id !== receiver);
  244. }
  245. if (Array.isArray(rst.recentUsers)) {
  246. initRecentView(rst.recentUsers);
  247. }
  248. if (Array.isArray(rst.contacts)) {
  249. initContactsView(rst.contacts)
  250. }
  251. initSharedView(curSharedUsers);
  252. refreshShareTip(curSharedUsers);
  253. refreshTreeView();
  254. } else {
  255. const matchItem = curSharedUsers.find(item => item._id === receiver);
  256. if (matchItem) {
  257. matchItem.allowCopy = shareData[0].allowCopy;
  258. matchItem.allowCooperate = shareData[0].allowCooperate;
  259. }
  260. }
  261. if (permissionType !== null) {
  262. const payload = [PermissionType.UPDATE_COOPERATE, PermissionType.UPDATE_COPY].includes(permissionType)
  263. ? { prop: { allowCopy: shareData[0].allowCopy, allowCooperate: shareData[0].allowCooperate } }
  264. : null;
  265. emitPermissionChange(permissionType, receiver, curProjectID, rst.emitTenders, payload);
  266. }
  267. } catch (err) {
  268. console.log(err);
  269. alert(`${String(err)} 请重试。`);
  270. initSharedView(curSharedUsers);
  271. } finally {
  272. $.bootstrapLoading.end();
  273. }
  274. }
  275. // 获取权限变更的类型
  276. function getPermissionType(shareType, cache, curShareData) {
  277. if (shareType === ShareType.CANCEL) {
  278. return PermissionType.CANCEL;
  279. }
  280. if (shareType === ShareType.CREATE) {
  281. return PermissionType.SHARE;
  282. }
  283. if (!cache) {
  284. return null;
  285. }
  286. const match = cache.find(item => item._id === curShareData.userID);
  287. if (!match) {
  288. return null;
  289. }
  290. if (match.allowCooperate !== curShareData.allowCooperate) {
  291. return PermissionType.UPDATE_COOPERATE;
  292. }
  293. if (match.allowCopy !== curShareData.allowCopy) {
  294. return PermissionType.UPDATE_COPY;
  295. }
  296. return null;
  297. }
  298. // 权限变更消息推送
  299. function emitPermissionChange(permissionType, userID, projectID, emitTenders, payload) {
  300. const compilationID = typeof projectObj !== 'undefined' ? projectObj.project.projectInfo.compilation : compilationData._id;
  301. socket.emit('sharePermissionChange', { permissionType, userID, compilationID, projectID, emitTenders, payload });
  302. }
  303. // 权限变更处理监听
  304. function permissionChangeListener() {
  305. socket.on('sharePermissionChange', ({ permissionType, target, payload }) => {
  306. const type = `${permissionType}-${target}`;
  307. switch (type) {
  308. case `${PermissionType.READ}-${PageTarget.PM}`:
  309. handleShareItemRead(payload);
  310. break;
  311. case `${PermissionType.UPDATE_COOPERATE}-${PageTarget.PM}`:
  312. case `${PermissionType.UPDATE_COPY}-${PageTarget.PM}`:
  313. handlePMPropChange(payload);
  314. break;
  315. case `${PermissionType.UPDATE_COOPERATE}-${PageTarget.MAIN}`:
  316. handleMainCooperateChange(payload);
  317. break;
  318. case `${PermissionType.CANCEL}-${PageTarget.PM}`:
  319. handlePMCancelPermission(payload);
  320. break;
  321. case `${PermissionType.CANCEL}-${PageTarget.MAIN}`:
  322. handleMainCancelPermission();
  323. break;
  324. case `${PermissionType.SHARE}-${PageTarget.PM}`:
  325. handlePMSharePermission(payload);
  326. break;
  327. }
  328. });
  329. }
  330. // 项目管理页面分享权限变更后相关处理函数
  331. // 已读分享项目
  332. function handleShareItemRead({ markReadProjectIDs }) {
  333. const isActive = $('#tab_pm_share').hasClass('active');
  334. if (isActive) {
  335. pmShare.handleMarkRead(unreadShareList, markReadProjectIDs);
  336. } else {
  337. markReadProjectIDs.forEach(projectID => {
  338. removeUnread(projectID, unreadShareList);
  339. });
  340. }
  341. }
  342. // 分享条数属性变更(是否可拷贝、是否可编辑)
  343. // @param {Object} prop - 属性变更对象 eg: { allowCopy: false }
  344. function handlePMPropChange({ projectID, prop }) {
  345. // 如果当前在项目管理的分享页面,需要刷新相关节点
  346. const isActive = $('#tab_pm_share').hasClass('active');
  347. if (isActive) {
  348. pmShare.handlePropChange(projectID, prop);
  349. }
  350. }
  351. // 已读某未读分享项目
  352. function removeUnread(projectID, list) {
  353. const idx = list.indexOf(projectID);
  354. if (idx >= 0) {
  355. list.splice(idx, 1);
  356. refreshUnreadCount(list.length);
  357. }
  358. }
  359. // 取消分享后项目管理页面接收到推送后到操作
  360. function handlePMCancelPermission({ projectID }) {
  361. removeUnread(projectID, unreadShareList);
  362. // 如果当前停留在分享标签界面,清楚分享项目
  363. const isActive = $('#tab_pm_share').hasClass('active');
  364. if (isActive) {
  365. pmShare.handleCancelShare(projectID);
  366. }
  367. }
  368. // 通知信息点击事件
  369. function notify() {
  370. pmShare.initShareTree();
  371. $("#notify").hide();
  372. }
  373. // 新增分享后项目管理页面接收到推送后的操作
  374. function handlePMSharePermission({ projectID }) {
  375. unreadShareList.push(projectID);
  376. unreadShareList = [...new Set(unreadShareList)];
  377. refreshUnreadCount(unreadShareList.length);
  378. // 如果当前停留在分享标签界面,需要弹出提示
  379. const isActive = $('#tab_pm_share').hasClass('active');
  380. if (isActive) {
  381. $("#message").html('您有新的分享项目,<a href="javascript:void(0);" id="load-data" onclick="SHARE_TO.notify()">点击刷新列表</a>');
  382. $("#notify").show();
  383. }
  384. }
  385. // 刷新未读分享标记
  386. function refreshUnreadCount(count) {
  387. if (count) {
  388. $('#unread-share-count').text(count);
  389. $('#unread-share-count').show();
  390. } else {
  391. $('#unread-share-count').hide();
  392. }
  393. }
  394. // 主页面的分享权限变更后相关处理函数
  395. function handleMainCooperateChange({ allowCooperate }) {
  396. if (projectObj.project.projectInfo.shareState && projectObj.project.projectInfo.shareState.allowCooperate !== allowCooperate) {
  397. setLocalCache(commonConstants.StorageKey.ONCE_MAIN_LOADED, '分享设置已被修改,当前项目已自动刷新。');
  398. window.location.reload();
  399. }
  400. }
  401. function handleMainCancelPermission() {
  402. // 定位到空白页
  403. window.location.replace(`/blank?type=${commonConstants.BlankType.SHARE_CANCEL}`);
  404. }
  405. // 刷新项目管理树视图
  406. // 如果是在项目管理页面,需要刷新树(分享图标可能需要清除)
  407. function refreshTreeView() {
  408. if (typeof projTreeObj !== 'undefined' && projTreeObj.tree.selected) {
  409. projTreeObj.tree.selected.data.shareInfo = curSharedUsers;
  410. const sheet = projTreeObj.workBook.getSheet(0);
  411. projTreeObj.renderSheetFuc(sheet, function () {
  412. sheet.invalidateLayout();
  413. sheet.repaint();
  414. });
  415. }
  416. }
  417. // 刷新造价书的分享按钮tooltip提示
  418. function refreshShareTip(sharedUsers) {
  419. const $shareTip = $('#share-tip');
  420. if (!$shareTip) {
  421. return;
  422. }
  423. const limit = 2;
  424. const count = sharedUsers.length;
  425. const users = sharedUsers.slice(0, 2);
  426. const tip = users.reduce((acc, user, index) => {
  427. if (index === 0) {
  428. acc += '已分享给';
  429. acc += user.real_name;
  430. } else {
  431. acc += ` ${user.real_name}`;
  432. }
  433. if (index === users.length - 1 && count > limit) {
  434. acc += `等${count}人`;
  435. }
  436. return acc;
  437. }, '');
  438. $shareTip.attr('data-original-title', tip);
  439. }
  440. // 初始化分享给的页面
  441. async function initModal(projectID) {
  442. try {
  443. curProjectID = projectID;
  444. $.bootstrapLoading.start();
  445. // 恢复
  446. $('#share-phone').val('');
  447. initSearchResultView();
  448. $('#share-hint').text('');
  449. const { isFree, sharedUsers, recentUsers, contacts } = await getInitalData(projectID);
  450. if (isFree) {
  451. hintBox.versionBox('此功能仅在专业版中提供,免费公用版可选择单个分段进行分享。');
  452. } else {
  453. curSharedUsers = sharedUsers;
  454. initSharedView(sharedUsers);
  455. initRecentView(recentUsers);
  456. initContactsView(contacts);
  457. setTimeout(() => $('#share-phone').focus(), 200);
  458. $('#share').modal('show');
  459. }
  460. } catch (err) {
  461. console.log(err);
  462. alert(err);
  463. } finally {
  464. $.bootstrapLoading.end();
  465. }
  466. }
  467. // 退出分享给页面
  468. function exitModal() {
  469. clearCache();
  470. }
  471. // 分享给
  472. async function handleSearch() {
  473. let phone = $('#share-phone').val();
  474. phone = phone && phone.trim() || '';
  475. //$('#share-hint').text('');
  476. initSearchResultView();
  477. if (!phone) {
  478. $('#share-hint').text('请输入手机号码。');
  479. return;
  480. }
  481. // 根据手机号获取用户
  482. const user = await ajaxPost('/user/getUserByMobile', { mobile: phone });
  483. if (!user) {
  484. $('#share-hint').text('账号不存在。');
  485. return;
  486. }
  487. if (user._id === userID) {
  488. $('#share-hint').text('不可分享给自己。');
  489. return;
  490. }
  491. const matched = curSharedUsers.find(item => item._id === user._id);
  492. if (matched) {
  493. $('#share-hint').text('已与该用户分享。');
  494. return;
  495. }
  496. $('#share-hint').text('');
  497. initSearchResultView(user);
  498. }
  499. // 一些事件的监听
  500. function handleEventListener() {
  501. // 界面消失
  502. $('#share').on('hide.bs.modal', function () {
  503. exitModal();
  504. });
  505. // 联系人下拉
  506. $('#contacts-dropdown').click(function () {
  507. const $subMenu = $('#contacts-menue');
  508. const visible = $subMenu.is(':visible');
  509. if (visible) {
  510. $subMenu.removeClass('show');
  511. } else {
  512. $subMenu.addClass('show');
  513. }
  514. });
  515. // 点击body时,联系人菜单的处理
  516. $('body').click(function (e) {
  517. const body = $(this)[0];
  518. const $contactsMenu = $('#contacts-menue');
  519. const contactsMenu = $contactsMenu[0];
  520. const dropdownButton = $('#contacts-dropdown')[0]
  521. if (!$contactsMenu.is(':visible')) {
  522. return;
  523. }
  524. let target = e.target;
  525. while (target !== body) {
  526. if ([contactsMenu, dropdownButton].includes(target)) {
  527. return;
  528. }
  529. target = target.parentElement;
  530. }
  531. $(contactsMenu.parentElement).removeClass('show');
  532. $contactsMenu.removeClass('show');
  533. });
  534. // 输入手机号查找要分享给的用户
  535. let keyupTime = 0;
  536. let delayTime = 500;
  537. function delayKeyup(callback) {
  538. const nowTime = Date.now();
  539. keyupTime = nowTime;
  540. setTimeout(function () {
  541. if (nowTime - keyupTime == 0) {
  542. callback();
  543. }
  544. }, delayTime);
  545. }
  546. $('#share-phone').on('keyup', function () {
  547. delayKeyup(function () {
  548. console.log(curSharedUsers);
  549. handleSearch();
  550. });
  551. });
  552. $('#sharePhone').on('keypress', function (e) {
  553. if (e.keyCode === 13) {
  554. $(this).blur();
  555. }
  556. });
  557. }
  558. return {
  559. initModal,
  560. handleEventListener,
  561. permissionChangeListener,
  562. emitPermissionChange,
  563. getAvatarHTML,
  564. notify,
  565. removeUnread,
  566. }
  567. })();