sp_setting_data_range.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. $(document).ready(function() {
  2. autoFlashHeight();
  3. const RangeType = {
  4. unitType: 1,
  5. unit: 2,
  6. user: 3,
  7. };
  8. const typeIndex = {};
  9. const unitIndex = {};
  10. const userIndex = {};
  11. dataRangeTree.forEach(type => {
  12. typeIndex[Number(type.id)] = type;
  13. type.units.forEach(unit => {
  14. unit.parentTypeId = Number(type.id);
  15. unitIndex[Number(unit.id)] = unit;
  16. unit.users.forEach(user => {
  17. user.parentTypeId = Number(type.id);
  18. user.parentUnitId = Number(unit.id);
  19. userIndex[Number(user.id)] = user;
  20. });
  21. });
  22. });
  23. dataRangeUnassignedUsers.forEach(user => {
  24. user.parentTypeId = Number(user.account_group) || 0;
  25. userIndex[Number(user.id)] = user;
  26. });
  27. const makeSelection = function(config) {
  28. return {
  29. unitTypeIds: new Set((config.unit_type_ids || []).map(Number)),
  30. unitIds: new Set((config.unit_ids || []).map(Number)),
  31. userIds: new Set((config.user_ids || []).map(Number)),
  32. };
  33. };
  34. const cloneSelection = function(source) {
  35. return {
  36. unitTypeIds: new Set(Array.from(source.unitTypeIds)),
  37. unitIds: new Set(Array.from(source.unitIds)),
  38. userIds: new Set(Array.from(source.userIds)),
  39. };
  40. };
  41. let savedSelection = makeSelection(dataRangeConfig);
  42. let savedConfigured = Boolean(dataRangeConfig.configured);
  43. let currentSelection = cloneSelection(savedSelection);
  44. const escapeHtml = function(value) {
  45. return $('<div>').text(value === undefined || value === null ? '' : value).html();
  46. };
  47. const containsKeyword = function(value, keyword) {
  48. return !keyword || String(value || '').toLowerCase().indexOf(keyword) >= 0;
  49. };
  50. const getSelectionSet = function(rangeType) {
  51. if (rangeType === RangeType.unitType) return currentSelection.unitTypeIds;
  52. if (rangeType === RangeType.unit) return currentSelection.unitIds;
  53. return currentSelection.userIds;
  54. };
  55. const getSelectionSetFrom = function(selection, rangeType) {
  56. if (rangeType === RangeType.unitType) return selection.unitTypeIds;
  57. if (rangeType === RangeType.unit) return selection.unitIds;
  58. return selection.userIds;
  59. };
  60. const getRangeName = function(rangeType, targetId) {
  61. let node;
  62. if (rangeType === RangeType.unitType) node = typeIndex[targetId];
  63. if (rangeType === RangeType.unit) node = unitIndex[targetId];
  64. if (rangeType === RangeType.user) node = userIndex[targetId];
  65. return node ? node.name : '';
  66. };
  67. const getRangeTitle = function(rangeType) {
  68. if (rangeType === RangeType.unitType) return '单位类型';
  69. if (rangeType === RangeType.unit) return '单位';
  70. return '用户';
  71. };
  72. const removeTypeDescendants = function(typeId) {
  73. const type = typeIndex[typeId];
  74. if (!type) return;
  75. type.units.forEach(unit => {
  76. currentSelection.unitIds.delete(Number(unit.id));
  77. unit.users.forEach(user => currentSelection.userIds.delete(Number(user.id)));
  78. });
  79. dataRangeUnassignedUsers.forEach(user => {
  80. if (Number(user.parentTypeId) === Number(typeId)) currentSelection.userIds.delete(Number(user.id));
  81. });
  82. };
  83. const removeUnitDescendants = function(unitId) {
  84. const unit = unitIndex[unitId];
  85. if (!unit) return;
  86. unit.users.forEach(user => currentSelection.userIds.delete(Number(user.id)));
  87. };
  88. const renderCheckbox = function(rangeType, targetId, disabled) {
  89. const checked = getSelectionSet(rangeType).has(Number(targetId)) ? ' checked' : '';
  90. const disabledAttr = disabled ? ' disabled' : '';
  91. return `<input type="checkbox" class="mr-1 data-range-check" data-range-type="${rangeType}" data-target-id="${targetId}"${checked}${disabledAttr}>`;
  92. };
  93. const renderNode = function(options) {
  94. const indent = options.level * 20;
  95. const toggle = options.hasChildren
  96. ? '<span class="data-range-toggle"><i class="fa fa-caret-down"></i></span>'
  97. : '<span class="data-range-toggle empty"></span>';
  98. const checkbox = options.selectable === false ? '' : renderCheckbox(options.rangeType, options.targetId, options.disabled);
  99. const icon = options.rangeType === RangeType.user
  100. ? '<i class="fa fa-user text-warning mr-1"></i>'
  101. : '<i class="fa fa-folder text-warning mr-1"></i>';
  102. const extra = options.extra ? ` <small class="text-muted">${escapeHtml(options.extra)}</small>` : '';
  103. return `<div class="data-range-node" style="padding-left:${indent}px">${toggle}${checkbox}${icon}<span>${escapeHtml(options.name)}</span>${extra}</div>`;
  104. };
  105. const renderTree = function() {
  106. const keyword = $('#data-range-keyword').val().trim().toLowerCase();
  107. const html = [];
  108. dataRangeTree.forEach(type => {
  109. const typeMatch = containsKeyword(type.name, keyword);
  110. const visibleUnits = [];
  111. type.units.forEach(unit => {
  112. const unitMatch = containsKeyword(unit.name, keyword);
  113. const users = typeMatch || unitMatch ? unit.users : unit.users.filter(user => {
  114. return containsKeyword(user.name, keyword) || containsKeyword(user.role, keyword);
  115. });
  116. if (!keyword || typeMatch || unitMatch || users.length > 0) visibleUnits.push({ unit, users });
  117. });
  118. if (keyword && !typeMatch && visibleUnits.length === 0) return;
  119. html.push('<div class="data-range-tree-group">');
  120. html.push(renderNode({
  121. level: 0,
  122. hasChildren: visibleUnits.length > 0,
  123. rangeType: RangeType.unitType,
  124. targetId: Number(type.id),
  125. name: type.name,
  126. }));
  127. html.push('<div class="data-range-children">');
  128. visibleUnits.forEach(item => {
  129. const typeSelected = currentSelection.unitTypeIds.has(Number(type.id));
  130. html.push('<div class="data-range-tree-group">');
  131. html.push(renderNode({
  132. level: 1,
  133. hasChildren: item.users.length > 0,
  134. rangeType: RangeType.unit,
  135. targetId: Number(item.unit.id),
  136. name: item.unit.name,
  137. disabled: typeSelected,
  138. }));
  139. html.push('<div class="data-range-children">');
  140. item.users.forEach(user => {
  141. const unitSelected = currentSelection.unitIds.has(Number(item.unit.id));
  142. html.push(renderNode({
  143. level: 2,
  144. hasChildren: false,
  145. rangeType: RangeType.user,
  146. targetId: Number(user.id),
  147. name: user.name,
  148. extra: user.role,
  149. disabled: typeSelected || unitSelected,
  150. }));
  151. });
  152. html.push('</div></div>');
  153. });
  154. html.push('</div></div>');
  155. });
  156. const otherUsers = keyword ? dataRangeUnassignedUsers.filter(user => {
  157. return containsKeyword(user.name, keyword) || containsKeyword(user.company, keyword) || containsKeyword(user.role, keyword);
  158. }) : dataRangeUnassignedUsers;
  159. if (otherUsers.length > 0) {
  160. html.push('<div class="data-range-tree-group">');
  161. html.push(renderNode({ level: 0, hasChildren: true, selectable: false, name: '未关联单位用户' }));
  162. html.push('<div class="data-range-children">');
  163. otherUsers.forEach(user => {
  164. const typeSelected = currentSelection.unitTypeIds.has(Number(user.parentTypeId));
  165. html.push(renderNode({
  166. level: 1,
  167. hasChildren: false,
  168. rangeType: RangeType.user,
  169. targetId: Number(user.id),
  170. name: user.name,
  171. extra: user.company || user.role,
  172. disabled: typeSelected,
  173. }));
  174. });
  175. html.push('</div></div>');
  176. }
  177. if (html.length === 0) html.push('<div class="text-muted text-center py-4">没有匹配的数据</div>');
  178. $('#data-range-tree').html(html.join(''));
  179. };
  180. const renderSelected = function() {
  181. const html = [];
  182. const appendItems = function(rangeType, ids) {
  183. Array.from(ids).sort((a, b) => a - b).forEach(targetId => {
  184. html.push('<div class="data-range-selected-item d-flex align-items-center">');
  185. html.push(`<span class="badge badge-light mr-2">${getRangeTitle(rangeType)}</span>`);
  186. html.push(`<span>${escapeHtml(getRangeName(rangeType, targetId))}</span>`);
  187. html.push(`<a href="javascript:void(0);" class="ml-auto text-danger remove-data-range" data-range-type="${rangeType}" data-target-id="${targetId}" title="移除"><i class="fa fa-times"></i></a>`);
  188. html.push('</div>');
  189. });
  190. };
  191. appendItems(RangeType.unitType, currentSelection.unitTypeIds);
  192. appendItems(RangeType.unit, currentSelection.unitIds);
  193. appendItems(RangeType.user, currentSelection.userIds);
  194. const total = currentSelection.unitTypeIds.size + currentSelection.unitIds.size + currentSelection.userIds.size;
  195. if (total === 0) html.push('<div class="text-muted text-center py-4">暂未选择范围</div>');
  196. $('#data-range-selected-count').text(total);
  197. $('#selected-unit-type-count').text(currentSelection.unitTypeIds.size);
  198. $('#selected-unit-count').text(currentSelection.unitIds.size);
  199. $('#selected-user-count').text(currentSelection.userIds.size);
  200. $('#data-range-selected-list').html(html.join(''));
  201. };
  202. const renderSummary = function() {
  203. if (!savedConfigured) {
  204. $('#data-range-summary').text('当前未配置数据范围,默认可选择当前子项目全部启用成员。');
  205. return;
  206. }
  207. $('#data-range-summary').text(
  208. `已设置:单位类型 ${savedSelection.unitTypeIds.size} 项,单位 ${savedSelection.unitIds.size} 项,具体用户 ${savedSelection.userIds.size} 项。`
  209. );
  210. };
  211. const renderMainSelection = function() {
  212. const html = [];
  213. const appendItems = function(rangeType, ids) {
  214. Array.from(ids).sort((a, b) => a - b).forEach(targetId => {
  215. const rangeName = getRangeName(rangeType, targetId);
  216. if (!rangeName) return;
  217. html.push(`<span class="data-range-value-item" title="${escapeHtml(getRangeTitle(rangeType))}">`);
  218. html.push(`<span class="data-range-value-name">${escapeHtml(rangeName)}</span>`);
  219. html.push(`<a href="javascript:void(0);" class="remove-main-data-range" data-range-type="${rangeType}" data-target-id="${targetId}" title="移除${escapeHtml(rangeName)}" aria-label="移除${escapeHtml(rangeName)}"><i class="fa fa-times"></i></a>`);
  220. html.push('</span>');
  221. });
  222. };
  223. appendItems(RangeType.unitType, savedSelection.unitTypeIds);
  224. appendItems(RangeType.unit, savedSelection.unitIds);
  225. appendItems(RangeType.user, savedSelection.userIds);
  226. $('#data-range-value-list').html(html.join(''));
  227. };
  228. const selectionToRanges = function(selection) {
  229. const ranges = [];
  230. const appendRanges = function(rangeType, ids) {
  231. ids.forEach(targetId => ranges.push({ range_type: rangeType, target_id: targetId }));
  232. };
  233. appendRanges(RangeType.unitType, selection.unitTypeIds);
  234. appendRanges(RangeType.unit, selection.unitIds);
  235. appendRanges(RangeType.user, selection.userIds);
  236. return ranges;
  237. };
  238. let saving = false;
  239. const saveSelection = function(selection, successCallback) {
  240. if (saving) return;
  241. saving = true;
  242. $('#save-data-range').prop('disabled', true);
  243. postData(`/sp/${dataRangeSpid}/setting/user/data-range/update`, {
  244. module_key: dataRangeModuleKey,
  245. ranges: selectionToRanges(selection),
  246. }, function(result) {
  247. const config = result || {
  248. configured: selectionToRanges(selection).length > 0,
  249. unit_type_ids: Array.from(selection.unitTypeIds),
  250. unit_ids: Array.from(selection.unitIds),
  251. user_ids: Array.from(selection.userIds),
  252. };
  253. savedSelection = makeSelection(config);
  254. savedConfigured = Boolean(config.configured);
  255. saving = false;
  256. $('#save-data-range').prop('disabled', false);
  257. renderMainSelection();
  258. renderSummary();
  259. if (successCallback) successCallback();
  260. }, function() {
  261. saving = false;
  262. $('#save-data-range').prop('disabled', false);
  263. });
  264. };
  265. const refreshModal = function() {
  266. renderTree();
  267. renderSelected();
  268. };
  269. $('#open-data-range-modal').on('click keydown', function(event) {
  270. if ($(event.target).closest('.remove-main-data-range').length > 0) return;
  271. if (event.type === 'keydown' && event.keyCode !== 13 && event.keyCode !== 32) return;
  272. event.preventDefault();
  273. currentSelection = cloneSelection(savedSelection);
  274. $('#data-range-keyword').val('');
  275. refreshModal();
  276. $('#data-range-modal').modal('show');
  277. });
  278. $('body').on('click', '.remove-main-data-range', function(event) {
  279. event.preventDefault();
  280. event.stopPropagation();
  281. if (saving) return;
  282. const rangeType = Number($(this).attr('data-range-type'));
  283. const targetId = Number($(this).attr('data-target-id'));
  284. const nextSelection = cloneSelection(savedSelection);
  285. getSelectionSetFrom(nextSelection, rangeType).delete(targetId);
  286. saveSelection(nextSelection, function() {
  287. toastr.success('授权用户范围已移除');
  288. });
  289. });
  290. $('#data-range-search').click(renderTree);
  291. $('#data-range-keyword').on('keydown', function(event) {
  292. if (event.keyCode === 13) {
  293. event.preventDefault();
  294. renderTree();
  295. }
  296. });
  297. $('#data-range-keyword').on('input', renderTree);
  298. $('body').on('click', '.data-range-toggle:not(.empty)', function() {
  299. const children = $(this).parent().next('.data-range-children');
  300. children.toggleClass('collapsed');
  301. $('i', this).toggleClass('fa-caret-down fa-caret-right');
  302. });
  303. $('body').on('change', '.data-range-check', function() {
  304. const rangeType = Number($(this).attr('data-range-type'));
  305. const targetId = Number($(this).attr('data-target-id'));
  306. const selection = getSelectionSet(rangeType);
  307. if (this.checked) {
  308. selection.add(targetId);
  309. if (rangeType === RangeType.unitType) removeTypeDescendants(targetId);
  310. if (rangeType === RangeType.unit) removeUnitDescendants(targetId);
  311. } else {
  312. selection.delete(targetId);
  313. }
  314. refreshModal();
  315. });
  316. $('body').on('click', '.remove-data-range', function() {
  317. const rangeType = Number($(this).attr('data-range-type'));
  318. const targetId = Number($(this).attr('data-target-id'));
  319. getSelectionSet(rangeType).delete(targetId);
  320. refreshModal();
  321. });
  322. $('#save-data-range').click(function() {
  323. saveSelection(currentSelection, function() {
  324. $('#data-range-modal').modal('hide');
  325. toastr.success('数据范围保存成功');
  326. });
  327. });
  328. renderMainSelection();
  329. renderSummary();
  330. });