index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. const SHARE_TO = (() => {
  2. const ShareType = {
  3. CREATE: 'create',
  4. UPDATE: 'update',
  5. CANCEL: 'cancel',
  6. };
  7. // 当前分享的项目ID
  8. let curProjectID;
  9. // 当前项目的已分享列表
  10. let curSharedUsers = [];
  11. // 清除缓存
  12. function clearCache() {
  13. curProjectID = null;
  14. curSharedUsers = [];
  15. }
  16. // 最近联系人显示数量
  17. const rencentCount = 5;
  18. // 获取初始数据:1.最近分享人 2.联系人 3.已分享人
  19. async function getInitalData(projectID) {
  20. return await ajaxPost('/pm/api/getInitialShareData', { user_id: userID, count: rencentCount, projectID }, false);
  21. }
  22. // 获取头像视图html
  23. function getAvatarHTML(mobile, realName) {
  24. // 手机最后一位
  25. const lastMobileNumer = mobile.substr(-1);
  26. // 显示名称为真实名称后两位
  27. const nickName = realName.substr(-2);
  28. return `<span class="avatar bg-${lastMobileNumer}">${nickName}</span>`;
  29. }
  30. /**
  31. * 获取用户列表视图html:最近分享和联系人用
  32. * @param {Array} users - 排序过的用户数据
  33. * @param {Boolean} showAlphabet - 是否显示字母表分类
  34. * @return {HTMLString}
  35. * */
  36. function getUserHTML(users, showAlphabet) {
  37. let curLetter = '';
  38. return users.reduce((html, user) => {
  39. const mobile = user.mobile || '';
  40. const realName = user.real_name || '';
  41. const company = user.company || '';
  42. if (showAlphabet) {
  43. // 名字首个字符对应拼音
  44. const letter = pinyinUtil.getFirstLetter(realName).substr(0, 1);
  45. if (letter !== curLetter) {
  46. curLetter = letter;
  47. html += `<li class="letter">${letter}</li>`;
  48. }
  49. }
  50. const avatarHtml = getAvatarHTML(mobile, realName);
  51. return html +
  52. `<li>
  53. ${avatarHtml}
  54. <div class="book-body">
  55. <h5 class="mt-0" title="${company}">${realName}</h5>
  56. <span>${mobile}</span>
  57. </div>
  58. </li>`;
  59. }, '');
  60. }
  61. // 初始化最近分享视图
  62. function initRecentView(recentUsers) {
  63. const recentShareHTML = getUserHTML(recentUsers, false);
  64. $('#recent-share').html(recentShareHTML);
  65. // 点击最近分享列表自动添加该用户添加到搜索框中
  66. $('#recent-share li').click(function () {
  67. const mobile = $(this).find('div span')[0].textContent;
  68. $('#share-phone').val(mobile);
  69. handleSearch()
  70. })
  71. }
  72. // 初始化联系人视图
  73. function initContactsView(contacts) {
  74. // 联系人按拼英首字母降序排序
  75. contacts.sort((a, b) => {
  76. const realNameA = a.real_name || '';
  77. const realNameB = b.real_name || '';
  78. return realNameA.localeCompare(realNameB, 'zh-Hans-CN', { sensitivity: 'accent' })
  79. });
  80. const contactsHTML = getUserHTML(contacts, true);
  81. $('#contacts').html(contactsHTML);
  82. // 点击联系人自动添加该联系人添加到搜索框中
  83. $('#contacts li:not(.letter)').click(function () {
  84. const mobile = $(this).find('div span')[0].textContent;
  85. $('#share-phone').val(mobile);
  86. $('#contacts-menue').removeClass('show');
  87. handleSearch()
  88. });
  89. }
  90. // 初始化已分享视图
  91. function initSharedView(sharedUsers) {
  92. const html = sharedUsers.reduce((html, user, index) => {
  93. const mobile = user.mobile || '';
  94. const realName = user.real_name || '';
  95. const company = user.company || '';
  96. const avatarHTML = getAvatarHTML(mobile, realName);
  97. const copyLabelFor = `allowCopy${index}`;
  98. const editLabelFor = `allowEdit${index}`;
  99. return html +
  100. `<li class="card mb-1">
  101. <div class="card-body p-1 row m-0">
  102. ${avatarHTML}
  103. <div class="book-body col-auto pl-0">
  104. <h5 class="mt-0">${realName}</h5>
  105. ${mobile}
  106. </div>
  107. <div class="col-5">${company}</div>
  108. <div class="col ml-auto p-0">
  109. <div class="d-flex justify-content-end">
  110. <div>
  111. <div class="custom-control custom-checkbox">
  112. <input type="checkbox" class="custom-control-input allow-copy" id="${copyLabelFor}" data-user="${user._id}" ${user.allowCopy ? 'checked' : ''}>
  113. <label class="custom-control-label" for="${copyLabelFor}">允许拷贝</label>
  114. </div>
  115. <div class="custom-control custom-checkbox">
  116. <input type="checkbox" class="custom-control-input allow-edit" id="${editLabelFor}" data-user="${user._id}" ${user.allowCooperate ? 'checked' : ''}>
  117. <label class="custom-control-label" for="${editLabelFor}">允许编辑</label>
  118. </div>
  119. </div>
  120. <div class="ml-3 d-flex align-items-center">
  121. <button class="btn btn-sm btn-outline-danger cancel-share" data-user="${user._id}">取消分享</button>
  122. </div>
  123. </div>
  124. </div>
  125. </div>
  126. </li>`;
  127. }, '');
  128. $('#shared-list').html(html);
  129. // 编辑允许拷贝
  130. $('#shared-list .allow-copy').click(function () {
  131. handleCheckBoxClick.call(this);
  132. handleShareAction.call(this, ShareType.UPDATE);
  133. });
  134. // 编辑允许编辑
  135. $('#shared-list .allow-edit').click(function () {
  136. handleCheckBoxClick.call(this);
  137. handleShareAction.call(this, ShareType.UPDATE);
  138. });
  139. // 取消分享
  140. $('#shared-list .cancel-share').click(function () {
  141. handleShareAction.call(this, ShareType.CANCEL);
  142. });
  143. }
  144. // 初始化搜索结果视图
  145. function initSearchResultView(user) {
  146. if (!user) {
  147. $('#share-search-result').html('')
  148. } else {
  149. const mobile = user.mobile || '';
  150. const realName = user.real_name || '';
  151. const company = user.company || '';
  152. const avatarHTML = getAvatarHTML(mobile, realName);
  153. const html =
  154. `<li class="card mb-1">
  155. <div class="card-body p-1 row m-0">
  156. ${avatarHTML}
  157. <div class="book-body col-auto pl-0">
  158. <h5 class="mt-0">${realName}</h5>
  159. ${mobile}
  160. </div>
  161. <div class="col-5">${company}</div>
  162. <div class="col ml-auto p-0">
  163. <div class="d-flex justify-content-end">
  164. <div>
  165. <div class="custom-control custom-checkbox">
  166. <input type="checkbox" class="custom-control-input" id="allow-copy" checked="">
  167. <label class="custom-control-label" for="allow-copy">允许拷贝</label>
  168. </div>
  169. <div class="custom-control custom-checkbox">
  170. <input type="checkbox" class="custom-control-input" id="allow-edit">
  171. <label class="custom-control-label" for="allow-edit">允许编辑</label>
  172. </div>
  173. </div>
  174. <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>
  175. </div>
  176. </div>
  177. </div>
  178. </li>`;
  179. $('#share-search-result').html(html);
  180. // 允许拷贝
  181. $('#allow-copy').click(function () {
  182. handleCheckBoxClick.call(this);
  183. });
  184. // 允许编辑
  185. $('#allow-edit').click(function () {
  186. handleCheckBoxClick.call(this);
  187. });
  188. // 分享给事件
  189. $('#share-to').click(function () {
  190. handleShareAction.call(this, ShareType.CREATE, user);
  191. });
  192. }
  193. }
  194. // 复选框框的状态随点击更改,不处理的话input的checked并不会自动处理
  195. function handleCheckBoxClick() {
  196. const curChecked = !$(this).attr('checked');
  197. if (curChecked) {
  198. $(this).attr('checked', 'checked');
  199. } else {
  200. $(this).removeAttr('checked');
  201. }
  202. }
  203. // 添加分享、编辑分享、取消分享的动作
  204. async function handleShareAction(shareType, user) {
  205. try {
  206. $.bootstrapLoading.start();
  207. const receiver = $(this).data('user');
  208. let shareData;
  209. let type = ShareType.UPDATE;
  210. if (shareType === ShareType.CREATE) {
  211. const allowCopy = !!$('#allow-copy').attr('checked');
  212. const allowCooperate = !!$('#allow-edit').attr('checked');
  213. shareData = [{ userID: receiver, allowCopy, allowCooperate }];
  214. type = ShareType.CREATE; // 上传的服务器的type,删除跟更新是一样的
  215. } else if (shareType === ShareType.UPDATE) {
  216. const allowCopy = !!$(`[data-user=${receiver}].allow-copy`).attr('checked');
  217. const allowCooperate = !!$(`[data-user=${receiver}].allow-edit`).attr('checked');
  218. shareData = [{ userID: receiver, allowCopy, allowCooperate }];
  219. } else {
  220. shareData = [{ userID: receiver, isCancel: true }];
  221. }
  222. const postData = {
  223. user_id: userID,
  224. type,
  225. projectID: curProjectID,
  226. count: rencentCount,
  227. shareData
  228. };
  229. const rst = await ajaxPost('/pm/api/share', postData);
  230. // 请求成功后刷新视图
  231. if (shareType === ShareType.CREATE || shareType === ShareType.CANCEL) {
  232. if (shareType === ShareType.CREATE) {
  233. user.allowCopy = shareData[0].allowCopy;
  234. user.allowCooperate = shareData[0].allowCooperate;
  235. curSharedUsers.unshift(user);
  236. $('#share-phone').val('');
  237. initSearchResultView();
  238. } else {
  239. curSharedUsers = curSharedUsers.filter(user => user._id !== receiver);
  240. }
  241. if (Array.isArray(rst.recentUsers)) {
  242. initRecentView(rst.recentUsers);
  243. }
  244. if (Array.isArray(rst.contacts)) {
  245. initContactsView(rst.contacts)
  246. }
  247. initSharedView(curSharedUsers);
  248. refreshShareTip(curSharedUsers);
  249. refreshTreeView();
  250. }
  251. } catch (err) {
  252. console.log(err);
  253. alert(`${String(err)} 请重试。`);
  254. initSharedView(curSharedUsers);
  255. } finally {
  256. $.bootstrapLoading.end();
  257. }
  258. }
  259. // 刷新项目管理树视图
  260. // 如果是在项目管理页面,需要刷新树(分享图标可能需要清除)
  261. function refreshTreeView() {
  262. if (typeof projTreeObj !== 'undefined' && projTreeObj.tree.selected) {
  263. projTreeObj.tree.selected.data.shareInfo = curSharedUsers;
  264. const sheet = projTreeObj.workBook.getSheet(0);
  265. projTreeObj.renderSheetFuc(sheet, function () {
  266. sheet.invalidateLayout();
  267. sheet.repaint();
  268. });
  269. }
  270. }
  271. // 刷新造价书的分享按钮tooltip提示
  272. function refreshShareTip(sharedUsers) {
  273. const $shareTip = $('#share-tip');
  274. if (!$shareTip) {
  275. return;
  276. }
  277. const limit = 2;
  278. const count = sharedUsers.length;
  279. const users = sharedUsers.slice(0, 2);
  280. const tip = users.reduce((acc, user, index) => {
  281. if (index === 0) {
  282. acc += '已分享给';
  283. acc += user.real_name;
  284. } else {
  285. acc += ` ${user.real_name}`;
  286. }
  287. if (index === users.length - 1 && count > limit) {
  288. acc += `等${count}人`;
  289. }
  290. return acc;
  291. }, '');
  292. $shareTip.attr('data-original-title', tip);
  293. }
  294. // 初始化分享给的页面
  295. async function initModal(projectID) {
  296. try {
  297. curProjectID = projectID;
  298. $.bootstrapLoading.start();
  299. // 恢复
  300. $('#share-phone').val('');
  301. initSearchResultView();
  302. $('#share-hint').text('');
  303. const { isFree, sharedUsers, recentUsers, contacts } = await getInitalData(projectID);
  304. if (isFree) {
  305. hintBox.versionBox('此功能仅在专业版中提供,免费公用版可选择单个分段进行分享。');
  306. } else {
  307. curSharedUsers = sharedUsers;
  308. initSharedView(sharedUsers);
  309. initRecentView(recentUsers);
  310. initContactsView(contacts);
  311. setTimeout(() => $('#share-phone').focus(), 200);
  312. $('#share').modal('show');
  313. }
  314. } catch (err) {
  315. console.log(err);
  316. alert(err);
  317. } finally {
  318. $.bootstrapLoading.end();
  319. }
  320. }
  321. // 退出分享给页面
  322. function exitModal() {
  323. clearCache();
  324. }
  325. // 分享给
  326. async function handleSearch() {
  327. let phone = $('#share-phone').val();
  328. phone = phone && phone.trim() || '';
  329. //$('#share-hint').text('');
  330. initSearchResultView();
  331. if (!phone) {
  332. $('#share-hint').text('请输入手机号码。');
  333. return;
  334. }
  335. // 根据手机号获取用户
  336. const user = await ajaxPost('/user/getUserByMobile', { mobile: phone });
  337. if (!user) {
  338. $('#share-hint').text('账号不存在。');
  339. return;
  340. }
  341. if (user._id === userID) {
  342. $('#share-hint').text('不可分享给自己。');
  343. return;
  344. }
  345. const matched = curSharedUsers.find(item => item._id === user._id);
  346. if (matched) {
  347. $('#share-hint').text('已与该用户分享。');
  348. return;
  349. }
  350. $('#share-hint').text('');
  351. initSearchResultView(user);
  352. }
  353. // 一些事件的监听
  354. function handleEventListener() {
  355. // 界面消失
  356. $('#share').on('hide.bs.modal', function () {
  357. exitModal();
  358. });
  359. // 联系人下拉
  360. $('#contacts-dropdown').click(function () {
  361. const $subMenu = $('#contacts-menue');
  362. const visible = $subMenu.is(':visible');
  363. if (visible) {
  364. $subMenu.removeClass('show');
  365. } else {
  366. $subMenu.addClass('show');
  367. }
  368. });
  369. // 点击body时,联系人菜单的处理
  370. $('body').click(function (e) {
  371. const body = $(this)[0];
  372. const $contactsMenu = $('#contacts-menue');
  373. const contactsMenu = $contactsMenu[0];
  374. const dropdownButton = $('#contacts-dropdown')[0]
  375. if (!$contactsMenu.is(':visible')) {
  376. return;
  377. }
  378. let target = e.target;
  379. while (target !== body) {
  380. if ([contactsMenu, dropdownButton].includes(target)) {
  381. return;
  382. }
  383. target = target.parentElement;
  384. }
  385. $(contactsMenu.parentElement).removeClass('show');
  386. $contactsMenu.removeClass('show');
  387. });
  388. // 输入手机号查找要分享给的用户
  389. let keyupTime = 0;
  390. let delayTime = 500;
  391. function delayKeyup(callback) {
  392. const nowTime = Date.now();
  393. keyupTime = nowTime;
  394. setTimeout(function () {
  395. if (nowTime - keyupTime == 0) {
  396. callback();
  397. }
  398. }, delayTime);
  399. }
  400. $('#share-phone').on('keyup', function () {
  401. delayKeyup(function () {
  402. handleSearch();
  403. });
  404. });
  405. $('#sharePhone').on('keypress', function (e) {
  406. if (e.keyCode === 13) {
  407. $(this).blur();
  408. }
  409. });
  410. }
  411. return {
  412. initModal,
  413. handleEventListener,
  414. getAvatarHTML,
  415. }
  416. })();