hookehuyr

拆分用户选择弹框为组件

...@@ -193,168 +193,11 @@ ...@@ -193,168 +193,11 @@
193 </vue-flow-editor> 193 </vue-flow-editor>
194 </div> 194 </div>
195 195
196 - <el-dialog v-model="state.dialogUserFormVisible" title="成员列表"> 196 + <select-user-view
197 - <div style="border: 1px dashed #DCDFE6; padding: 10px;"> 197 + :visible="state.dialogUserFormVisible"
198 - <el-tag 198 + @close="onCloseUserView"
199 - v-if="state.userTags.length" 199 + @confirm="onConfirmUserView"
200 - v-for="tag in state.userTags" 200 + />
201 - :key="tag.name"
202 - class=""
203 - style="margin-left: 0.25rem; margin-right: 0.25rem;"
204 - closable
205 - @close="handleTagClose(tag)"
206 - >
207 - {{ tag.name }}
208 - </el-tag>
209 - <div v-else style="text-align: center; color: #DCDFE6;">请选择成员</div>
210 - </div>
211 - <div style="border: 1px solid #DCDFE6; padding: 10px; margin-top: 10px;">
212 - <div style="height: 40px">
213 - <div style="padding: 0; position: relative; margin: 0 0 15px;">
214 - <div v-if="!state.is_active_search">
215 - <div class="flow-tabs__nav-wrap">
216 - <div class="flow-tabs__nav-scroll">
217 - <div class="flow-tabs__nav is-top">
218 - <div
219 - ref_key="barRef"
220 - class="flow-tabs__active-bar"
221 - :style="{
222 - width: state.tabTextWidth,
223 - transform: 'translateX' + '(' + state.tabOffset + ')'
224 - }"
225 - ></div>
226 - <div
227 - v-for="(item, index) in state.userTabs"
228 - :key="index"
229 - :class="[
230 - 'flow-tabs__item',
231 - 'is-top',
232 - item.id === state.activeTabId ? 'is-active' : ''
233 - ]"
234 - :id="item.id"
235 - @click="handleTabClick(item, $event, index)"
236 - >
237 - {{ item.label }}
238 - </div>
239 - </div>
240 - </div>
241 - </div>
242 - <div class="flow-tab-search" @click="activeSearchBtn">
243 - <el-icon :size="15"><Search /></el-icon> &nbsp;搜索框
244 - </div>
245 - </div>
246 - <div v-else>
247 - <el-input
248 - v-model="state.search_input"
249 - class="search-btn-wrapper"
250 - placeholder="请输入关键字"
251 - >
252 - <template #prefix>
253 - <el-icon class="el-input__icon"><search /></el-icon>
254 - </template>
255 - <template #append>
256 - <div class="search-group">
257 - <div class="confirm-btn" @click="onSearch">搜索</div>
258 - <div class="cancel-btn" @click="onClearSearch">取消</div>
259 - </div>
260 - </template>
261 - </el-input>
262 - </div>
263 - </div>
264 - </div>
265 - <!-- 未激活搜索框 -->
266 - <div v-if="!state.is_active_search">
267 - <div v-for="(item, index) in state.userTabs" :key="index">
268 - <div v-if="item.id === state.activeTabId && item.type === 'tree'">
269 - <el-row>
270 - <el-col :span="8">
271 - <el-tree
272 - :data="item.data"
273 - :props="state.defaultProps"
274 - node-key="id"
275 - :default-expanded-keys="state.currentNodeKey"
276 - empty-text="暂无数据"
277 - @node-click="handleNodeClick"
278 - />
279 - </el-col>
280 - <el-col :span="16">
281 - <div
282 - style="border-left: 2px solid #e4e7ed; width: 2px; height: 100%; padding-left: 10px;"
283 - >
284 - <el-checkbox-group
285 - class="flow-checkbox-group"
286 - v-model="state.checkedUserList"
287 - >
288 - <el-checkbox
289 - v-for="(user, idx) in state.userList"
290 - :key="idx"
291 - :label="user.id"
292 - :disabled="user.disabled"
293 - @change="handleCheckedUserListChange(user, $event)"
294 - >{{ user.label }}</el-checkbox
295 - >
296 - </el-checkbox-group>
297 - </div>
298 - </el-col>
299 - </el-row>
300 - </div>
301 - <div v-if="item.id === state.activeTabId && item.type === 'list'">
302 - <el-tabs
303 - tab-position="left"
304 - style=""
305 - class="demo-tabs"
306 - v-model="state.activeTabContent"
307 - @tab-click="handleTabContentClick"
308 - >
309 - <el-tab-pane
310 - v-for="(role, idx) in item.data"
311 - :key="idx"
312 - :name="role.label"
313 - :label="role.label"
314 - >
315 - <el-checkbox-group
316 - class="flow-checkbox-group"
317 - v-model="state.checkedUserList"
318 - >
319 - <el-checkbox
320 - v-for="(user, idx) in state.userList"
321 - :key="idx"
322 - :label="user.id"
323 - :disabled="user.disabled"
324 - @change="handleCheckedUserListChange(user, $event)"
325 - >{{ user.label }}</el-checkbox
326 - >
327 - </el-checkbox-group>
328 - </el-tab-pane>
329 - </el-tabs>
330 - </div>
331 - </div>
332 - </div>
333 - <!-- 激活搜索框 -->
334 - <div v-else>
335 - <el-checkbox-group
336 - class="flow-checkbox-group"
337 - style="padding-left: 10px;"
338 - v-model="state.checkedSearchUserList"
339 - >
340 - <el-checkbox
341 - v-for="(user, idx) in state.searchUserList"
342 - :key="idx"
343 - :label="user.id"
344 - :disabled="user.disabled"
345 - @change="handleCheckedUserListChange(user, $event)"
346 - >{{ user.label }}</el-checkbox
347 - >
348 - </el-checkbox-group>
349 - </div>
350 - </div>
351 - <template #footer>
352 - <span class="dialog-footer">
353 - <el-button @click="closeUserForm">取消</el-button>
354 - <el-button type="primary" @click="confirmUserForm">确定</el-button>
355 - </span>
356 - </template>
357 - </el-dialog>
358 </template> 201 </template>
359 202
360 <script lang="ts"> 203 <script lang="ts">
...@@ -365,6 +208,7 @@ import { ElNotification } from "element-plus"; ...@@ -365,6 +208,7 @@ import { ElNotification } from "element-plus";
365 import axios from "./axios"; 208 import axios from "./axios";
366 import $ from "jquery"; 209 import $ from "jquery";
367 import { Calendar, Search } from "@element-plus/icons-vue"; 210 import { Calendar, Search } from "@element-plus/icons-vue";
211 +import SelectUserView from "./selectUserView.vue";
368 212
369 const G6 = (window as any).G6.default as any; 213 const G6 = (window as any).G6.default as any;
370 214
...@@ -373,6 +217,11 @@ function delay(time) { ...@@ -373,6 +217,11 @@ function delay(time) {
373 } 217 }
374 218
375 export default { 219 export default {
220 + components: {
221 + Calendar,
222 + Search,
223 + SelectUserView
224 + },
376 setup(props, context) { 225 setup(props, context) {
377 const state = reactive({ 226 const state = reactive({
378 data: AppData, 227 data: AppData,
...@@ -466,55 +315,9 @@ export default { ...@@ -466,55 +315,9 @@ export default {
466 } 315 }
467 } 316 }
468 ], 317 ],
469 - userTags: [],
470 - currentNodeKey: [], // 当前展开的节点
471 - activeTabId: "tab-1", // TODO: 需要获取默认第一个ID
472 - activeTabContent: "",
473 - userTabs: [],
474 - tabSelectData: [],
475 - userTabType: "tree",
476 - tabOffset: "0px",
477 - tabTextWidth: "76px", // 文字宽度需要打开弹框时重新计算
478 - is_active_search: false,
479 - search_input: "",
480 - userList: [],
481 - checkedUserList: [],
482 - searchUserList: [], // 搜索框 选中 用户ID
483 - checkedSearchUserList: [],
484 - defaultProps: {
485 - children: "children",
486 - label: "label"
487 - }
488 }); 318 });
489 319
490 onMounted(() => { 320 onMounted(() => {
491 - // // 显示提示框的标志位
492 - // var showConfirmation = true;
493 - // // 监听 beforeunload 事件
494 - // window.addEventListener('beforeunload', function (event) {
495 - // if (showConfirmation) {
496 - // // 取消事件的默认行为(弹出确认对话框)
497 - // event.preventDefault();
498 - // // Chrome 和 Firefox 需要返回一个值以显示确认对话框
499 - // event.returnValue = '';
500 - // // 显示自定义的提示信息
501 - // var confirmationMessage = '确定要离开此页面吗?';
502 - // (event || window.event).returnValue = confirmationMessage; // 兼容旧版浏览器
503 - // return confirmationMessage;
504 - // }
505 - // });
506 - // // 监听 unload 事件
507 - // window.addEventListener('unload', function () {
508 - // // 设置标志位为 false,避免在刷新页面时再次显示提示框
509 - // showConfirmation = false;
510 - // });
511 - // axios.get('/srv/?a=query_form_all_field')
512 - // .then(res => {
513 - // console.warn(res.data);
514 - // });
515 - // $('.el-tabs__nav')
516 - // 根据数据获取选中的tag标签
517 - // setTagsList(state.userTabs);
518 }); 321 });
519 322
520 function handleActiveChange(name) { 323 function handleActiveChange(name) {
...@@ -538,411 +341,18 @@ export default { ...@@ -538,411 +341,18 @@ export default {
538 console.warn(val); 341 console.warn(val);
539 }; 342 };
540 343
344 + /****** 用户选择控件弹框 ******/
541 const openUserForm = () => { // 打开设置用户弹框 345 const openUserForm = () => { // 打开设置用户弹框
542 state.dialogUserFormVisible = true; 346 state.dialogUserFormVisible = true;
543 - // TODO:查询到的用户列表数据
544 - state.userTags = [
545 - { id: "user-1-1", name: "用户1-1" },
546 - ]
547 - state.userTabs = [
548 - {
549 - id: "tab-1",
550 - label: "组织架构",
551 - type: "tree",
552 - data: [
553 - {
554 - id: "dept-1",
555 - label: "上级部门 1",
556 - children: [
557 - {
558 - id: "dept-1-1",
559 - label: "部门名称 1-1",
560 - children: [],
561 - list: [
562 - {
563 - id: "user-1-1",
564 - label: "用户1-1",
565 - checked: false,
566 - disabled: false
567 - },
568 - {
569 - id: "user-1-2",
570 - label: "用户1-2",
571 - checked: false,
572 - disabled: false
573 - },
574 - {
575 - id: "user-1-3",
576 - label: "用户1-3",
577 - checked: false,
578 - disabled: true
579 - }
580 - ]
581 - }
582 - ]
583 - },
584 - {
585 - id: "dept-2",
586 - label: "上级部门 2",
587 - children: [
588 - {
589 - id: "dept-2-1",
590 - label: "部门名称 2-1",
591 - children: [],
592 - list: [
593 - {
594 - id: "user-2-1",
595 - label: "用户2-1",
596 - checked: false,
597 - disabled: false
598 - },
599 - {
600 - id: "user-2-2",
601 - label: "用户2-2",
602 - checked: false,
603 - disabled: false
604 - },
605 - {
606 - id: "user-2-3",
607 - label: "用户2-3",
608 - checked: false,
609 - disabled: true
610 - }
611 - ]
612 - }
613 - ]
614 - }
615 - ]
616 - },
617 - {
618 - id: "tab-2",
619 - label: "角色",
620 - type: "list",
621 - data: [
622 - {
623 - id: "role-1-1",
624 - label: "流程发起人",
625 - children: [],
626 - list: [
627 - {
628 - id: "user-1-1",
629 - label: "用户1-1",
630 - checked: true,
631 - disabled: false
632 - },
633 - {
634 - id: "user-2-2",
635 - label: "用户2-2",
636 - checked: false,
637 - disabled: false
638 - },
639 - {
640 - id: "role-1-4",
641 - label: "选项 C",
642 - checked: false,
643 - disabled: true
644 - }
645 - ]
646 - },
647 - {
648 - id: "role-1-2",
649 - label: "成员字段",
650 - children: [],
651 - list: []
652 - },
653 - {
654 - id: "role-1-3",
655 - label: "部门字段",
656 - children: [],
657 - list: []
658 - },
659 - {
660 - id: "role-1-4",
661 - label: "主管",
662 - children: [],
663 - list: []
664 - }
665 - ]
666 - }
667 - ];
668 - // 对比选中的用户的 ID,选中的 ID 同步状态到 userTabs 的数据结构上
669 - state.userTags.forEach(ele => {
670 - modifyFieldValue(state.userTabs, "id", ele.id, true);
671 - });
672 - };
673 -
674 - const handleTabClick = (tab, event, idx) => { // 点击Tab切换回调
675 - state.tabSelectData = tab.data; // tab选中数据提供给list类型使用
676 - state.userList = []; // 清空用户列表
677 - state.checkedUserList = []; // 清空选中用户列表
678 - state.activeTabContent = ""; // 清空侧边栏显示
679 - state.currentNodeKey = []; // 清空树形的默认展开
680 - // console.log(tab, event);
681 - // 设置当前激活的tab
682 - state.userTabType = tab.type;
683 - state.activeTabId = tab.id;
684 - // 子容器相对于父容器的相对x轴位移, 第一项为0
685 - if (idx) {
686 - state.tabOffset = $("#" + tab.id).position().left + 20 + "px";
687 - } else {
688 - state.tabOffset = "0px";
689 - }
690 - // 文字宽度
691 - state.tabTextWidth = $("#" + tab.id).width() + "px";
692 - // 检查列表第一项是否有值
693 - // if (tab?.data[0]?.list) {
694 - // let list = tab.data[0].list;
695 - // state.userList = list;
696 - // state.checkedUserList = list
697 - // .filter(ele => {
698 - // return ele.checked;
699 - // })
700 - // .map(ele => {
701 - // return ele.id;
702 - // });
703 - // }
704 - };
705 -
706 - const handleTabContentClick = (tab, event) => { // 侧边栏Tab点击回调
707 - // console.log(state.activeTabContent);
708 - // console.log(tab);
709 - // console.log(event);
710 - state.tabSelectData.forEach(ele => {
711 - if (ele.label === tab.props.label) {
712 - state.userList = ele.list;
713 - state.checkedUserList = ele.list
714 - .filter(ele => {
715 - return ele.checked;
716 - })
717 - .map(ele => {
718 - return ele.id;
719 - });
720 - }
721 - });
722 - };
723 -
724 - const handleNodeClick = data => { // 树形结构点击回调
725 - // console.log(data);
726 - if (data.list) {
727 - state.userList = data.list;
728 - state.checkedUserList = data.list
729 - .filter(ele => {
730 - return ele.checked;
731 - })
732 - .map(ele => {
733 - return ele.id;
734 - });
735 - // console.warn(state.checkedUserList);
736 - state.currentNodeKey = [data.id];
737 - }
738 }; 347 };
739 348
740 - /** 349 + const onCloseUserView = (status) => {
741 - * 递归查找匹配字段,并修改字段为新值 350 + state.dialogUserFormVisible = status;
742 - *
743 - * @param {object} obj - 修改对象
744 - * @param {string} fieldName - 修改字段
745 - * @param {any} targetValue - 匹配值
746 - * @param {any} newValue - 修改新值
747 - */
748 - function modifyFieldValue(obj, fieldName, targetValue, newValue) {
749 - // 检查当前层级的字段值是否匹配目标值
750 - if (obj[fieldName] === targetValue) {
751 - obj["checked"] = newValue;
752 - }
753 -
754 - // 遍历当前层级的子项(如果有)
755 - for (var key in obj) {
756 - if (typeof obj[key] === "object" && obj[key] !== null) {
757 - modifyFieldValue(obj[key], fieldName, targetValue, newValue);
758 - }
759 - }
760 } 351 }
761 - 352 + const onConfirmUserView = (data) => {
762 - const handleCheckedUserListChange = (user, checked) => { // 勾选用户回调 353 + console.log(data);
763 - // 修改选中值状态
764 - modifyFieldValue(state.userTabs, "id", user.id, checked);
765 - };
766 -
767 - /**
768 - * 递归获取树形结构下的所有list字段数据
769 - *
770 - * @param {Array} arr - The array to traverse.
771 - * @return {Array} - The list items found.
772 - */
773 - function getAllListItems(arr) {
774 - var result = [];
775 -
776 - function recursiveGetListItems(subArr) {
777 - for (var i = 0; i < subArr.length; i++) {
778 - var item = subArr[i];
779 - if (item.list && Array.isArray(item.list)) {
780 - result = result.concat(item.list);
781 - }
782 - if (item.children && Array.isArray(item.children)) {
783 - recursiveGetListItems(item.children);
784 - }
785 - }
786 - }
787 -
788 - recursiveGetListItems(arr);
789 - return result;
790 } 354 }
791 - 355 + /******************* END *******************/
792 - /**
793 - * 根据 userTabs 的选中状态,更新 tag 选中列表显示
794 - * 获取生成 list 集合的 checked 状态,生成 tags 的勾选显示
795 - *
796 - * @param {Array} val - userTabs
797 - */
798 - const setTagsList = (val) => {
799 - let userList = []; // 存储所有的list数据
800 - val.forEach(ele => {
801 - let data = getAllListItems(ele.data);
802 - userList.push(data);
803 - });
804 - // 合并成一维数组列表
805 - userList = [...userList.flat()];
806 - // 如果ID相同,需要数据去重
807 - let uniqueArray = userList.filter(
808 - (value, index, self) =>
809 - index === self.findIndex(obj => obj.id === value.id)
810 - );
811 - // 勾选变化后同步到选中列表
812 - state.userTags = uniqueArray
813 - .filter(ele => {
814 - return ele.checked;
815 - })
816 - .map(ele => {
817 - return {
818 - id: ele.id,
819 - name: ele.label
820 - };
821 - });
822 - };
823 -
824 - watch( // TAG: watch state.userTabs 监听数据结构选中值变化
825 - () => state.userTabs,
826 - val => {
827 - if (val) {
828 - // 更新 tags 选中列表显示
829 - setTagsList(val);
830 - }
831 - },
832 - { deep: true }
833 - );
834 -
835 - const handleTagClose = (tag) => { // 移除 tags 成员标签回调
836 - state.userTags.splice(state.userTags.indexOf(tag), 1); // 删除标签列表中的项
837 - // 移除数据结构中的选中值
838 - modifyFieldValue(state.userTabs, "id", tag.id, false);
839 - //
840 - let tagsId = state.userTags.map(ele => {
841 - return ele.id;
842 - });
843 - // 处理tags列表和下面选中列表同步问题
844 - let checkDataList = [];
845 - if (state.is_active_search) { // 激活搜索状态
846 - checkDataList = state.checkedSearchUserList;
847 - } else { // 激活非搜索状态
848 - checkDataList = state.checkedUserList;
849 - }
850 - // 获取 tags 中已删除的, 勾选列表中还勾选的值
851 - let result = checkDataList.filter(
852 - value => !tagsId.includes(value)
853 - );
854 - // 如果存在不同步的值,需要在列表中删除勾选列表中的项
855 - if (result.length) {
856 - result.forEach(ele => {
857 - checkDataList.splice(
858 - checkDataList.indexOf(ele),
859 - 1
860 - );
861 - });
862 - }
863 - };
864 -
865 - const closeUserForm = () => { // 关闭用户列表表单回调
866 - state.dialogUserFormVisible = false;
867 - };
868 - const confirmUserForm = () => { // 确认用户列表表单回调
869 - state.dialogUserFormVisible = false;
870 - console.log(state.userTags);
871 - };
872 -
873 - const activeSearchBtn = () => { // 激活搜索按钮操作
874 - state.is_active_search = !state.is_active_search;
875 - state.searchUserList = []; // 清空搜索列表
876 - };
877 -
878 - const checkSearchStatus = () => { // 在搜索框中同步显示tag框选中的用户
879 - state.userTags.forEach(ele => {
880 - state.searchUserList.forEach(item => {
881 - if (ele.id === item.id) {
882 - item.checked = true;
883 - }
884 - });
885 - });
886 - // 显示选中tags的用户
887 - state.checkedSearchUserList = state.searchUserList
888 - .filter(ele => ele.checked)
889 - .map(ele => ele.id);
890 - };
891 - const checkUserStatus = () => { // 在搜索框中同步显示tag框选中的用户
892 - state.userTags.forEach(ele => {
893 - state.userList.forEach(item => {
894 - if (ele.id === item.id) {
895 - item.checked = true;
896 - }
897 - });
898 - });
899 - // 显示选中tags的用户
900 - state.checkedUserList = state.userList
901 - .filter(ele => ele.checked)
902 - .map(ele => ele.id);
903 - };
904 -
905 - const onClearSearch = () => { // 清空搜索回调
906 - state.is_active_search = !state.is_active_search;
907 - // 同步显示tag框选中的用户
908 - checkUserStatus();
909 - };
910 - const onSearch = () => { // 搜索回调
911 - // TODO: 获取后台数据
912 - state.searchUserList = [
913 - {
914 - id: "user-1-1",
915 - label: "用户1-1",
916 - checked: false,
917 - disabled: false
918 - },
919 - {
920 - id: "user-1-2",
921 - label: "用户1-2",
922 - checked: false,
923 - disabled: false
924 - },
925 - {
926 - id: "user-1-3",
927 - label: "用户1-3",
928 - checked: false,
929 - disabled: true
930 - }
931 - ];
932 - // 如果 tags 里存在的值,需要在搜索列表中勾选
933 - checkSearchStatus();
934 - };
935 -
936 - watch( // TAG: watch state.dialogUserFormVisible 监听弹框显示,修改tab文字宽度
937 - () => state.dialogUserFormVisible,
938 - val => {
939 - if (val) {
940 - nextTick(() => {
941 - state.tabTextWidth = $("#" + state.activeTabId).width() + "px";
942 - });
943 - }
944 - }
945 - );
946 356
947 357
948 /********** 流程图功能函数 **********/ 358 /********** 流程图功能函数 **********/
...@@ -1119,20 +529,14 @@ export default { ...@@ -1119,20 +529,14 @@ export default {
1119 handleAfterDelete, 529 handleAfterDelete,
1120 handleBeforeAdd, 530 handleBeforeAdd,
1121 handleAfterAdd, 531 handleAfterAdd,
532 +
1122 handleActiveChange, 533 handleActiveChange,
1123 onAuthAllChange, 534 onAuthAllChange,
1124 onAuthAllEditChange, 535 onAuthAllEditChange,
1125 - handleTabClick, 536 +
1126 - handleTabContentClick,
1127 - handleNodeClick,
1128 - handleTagClose,
1129 - closeUserForm,
1130 - confirmUserForm,
1131 - handleCheckedUserListChange,
1132 - onSearch,
1133 - onClearSearch,
1134 - activeSearchBtn,
1135 openUserForm, 537 openUserForm,
538 + onCloseUserView,
539 + onConfirmUserView,
1136 540
1137 logData, 541 logData,
1138 542
......
1 +<!--
2 + * @Date: 2023-11-01 10:18:53
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-11-01 11:28:37
5 + * @FilePath: /vue-flow-editor/doc/selectUserView.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <el-dialog v-model="dialogUserFormVisible" title="成员列表">
10 + <div style="border: 1px dashed #DCDFE6; padding: 10px;">
11 + <el-tag
12 + v-if="userTags.length"
13 + v-for="tag in userTags"
14 + :key="tag.name"
15 + class=""
16 + style="margin-left: 0.25rem; margin-right: 0.25rem;"
17 + closable
18 + @close="handleTagClose(tag)"
19 + >
20 + {{ tag.name }}
21 + </el-tag>
22 + <div v-else style="text-align: center; color: #DCDFE6;">请选择成员</div>
23 + </div>
24 + <div style="border: 1px solid #DCDFE6; padding: 10px; margin-top: 10px;">
25 + <div style="height: 40px">
26 + <div style="padding: 0; position: relative; margin: 0 0 15px;">
27 + <div v-if="!is_active_search">
28 + <div class="flow-tabs__nav-wrap">
29 + <div class="flow-tabs__nav-scroll">
30 + <div class="flow-tabs__nav is-top">
31 + <div
32 + ref_key="barRef"
33 + class="flow-tabs__active-bar"
34 + :style="{
35 + width: tabTextWidth,
36 + transform: 'translateX' + '(' + tabOffset + ')'
37 + }"
38 + ></div>
39 + <div
40 + v-for="(item, index) in userTabs"
41 + :key="index"
42 + :class="[
43 + 'flow-tabs__item',
44 + 'is-top',
45 + item.id === activeTabId ? 'is-active' : ''
46 + ]"
47 + :id="item.id"
48 + @click="handleTabClick(item, $event, index)"
49 + >
50 + {{ item.label }}
51 + </div>
52 + </div>
53 + </div>
54 + </div>
55 + <div class="flow-tab-search" @click="activeSearchBtn">
56 + <el-icon :size="15"><Search /></el-icon> &nbsp;搜索框
57 + </div>
58 + </div>
59 + <div v-else>
60 + <el-input
61 + v-model="search_input"
62 + class="search-btn-wrapper"
63 + placeholder="请输入关键字"
64 + >
65 + <template #prefix>
66 + <el-icon class="el-input__icon"><search /></el-icon>
67 + </template>
68 + <template #append>
69 + <div class="search-group">
70 + <div class="confirm-btn" @click="onSearch">搜索</div>
71 + <div class="cancel-btn" @click="onClearSearch">取消</div>
72 + </div>
73 + </template>
74 + </el-input>
75 + </div>
76 + </div>
77 + </div>
78 + <!-- 未激活搜索框 -->
79 + <div v-if="!is_active_search">
80 + <div v-for="(item, index) in userTabs" :key="index">
81 + <div v-if="item.id === activeTabId && item.type === 'tree'">
82 + <el-row>
83 + <el-col :span="8">
84 + <el-tree
85 + :data="item.data"
86 + :props="defaultProps"
87 + node-key="id"
88 + :default-expanded-keys="currentNodeKey"
89 + empty-text="暂无数据"
90 + @node-click="handleNodeClick"
91 + />
92 + </el-col>
93 + <el-col :span="16">
94 + <div
95 + style="border-left: 2px solid #e4e7ed; width: 2px; height: 100%; padding-left: 10px;"
96 + >
97 + <el-checkbox-group
98 + class="flow-checkbox-group"
99 + v-model="checkedUserList"
100 + >
101 + <el-checkbox
102 + v-for="(user, idx) in userList"
103 + :key="idx"
104 + :label="user.id"
105 + :disabled="user.disabled"
106 + @change="handleCheckedUserListChange(user, $event)"
107 + >{{ user.label }}</el-checkbox
108 + >
109 + </el-checkbox-group>
110 + </div>
111 + </el-col>
112 + </el-row>
113 + </div>
114 + <div v-if="item.id === activeTabId && item.type === 'list'">
115 + <el-tabs
116 + tab-position="left"
117 + style=""
118 + class="demo-tabs"
119 + v-model="activeTabContent"
120 + @tab-click="handleTabContentClick"
121 + >
122 + <el-tab-pane
123 + v-for="(role, idx) in item.data"
124 + :key="idx"
125 + :name="role.label"
126 + :label="role.label"
127 + >
128 + <el-checkbox-group
129 + class="flow-checkbox-group"
130 + v-model="checkedUserList"
131 + >
132 + <el-checkbox
133 + v-for="(user, idx) in userList"
134 + :key="idx"
135 + :label="user.id"
136 + :disabled="user.disabled"
137 + @change="handleCheckedUserListChange(user, $event)"
138 + >{{ user.label }}</el-checkbox
139 + >
140 + </el-checkbox-group>
141 + </el-tab-pane>
142 + </el-tabs>
143 + </div>
144 + </div>
145 + </div>
146 + <!-- 激活搜索框 -->
147 + <div v-else>
148 + <el-checkbox-group
149 + class="flow-checkbox-group"
150 + style="padding-left: 10px;"
151 + v-model="checkedSearchUserList"
152 + >
153 + <el-checkbox
154 + v-for="(user, idx) in searchUserList"
155 + :key="idx"
156 + :label="user.id"
157 + :disabled="user.disabled"
158 + @change="handleCheckedUserListChange(user, $event)"
159 + >{{ user.label }}</el-checkbox
160 + >
161 + </el-checkbox-group>
162 + </div>
163 + </div>
164 + <template #footer>
165 + <span class="dialog-footer">
166 + <el-button @click="closeUserForm">取消</el-button>
167 + <el-button type="primary" @click="confirmUserForm">确定</el-button>
168 + </span>
169 + </template>
170 + </el-dialog>
171 +</template>
172 +
173 +<script setup>
174 +import { ref, onMounted, watch, nextTick } from "vue";
175 +import { useRoute, useRouter } from "vue-router";
176 +import axios from "./axios";
177 +import $ from "jquery";
178 +
179 +const props = defineProps({
180 + visible: Boolean
181 +});
182 +const emit = defineEmits(["close", "confirm"]);
183 +
184 +const dialogUserFormVisible = ref(false);
185 +const userTags = ref([]);
186 +const currentNodeKey = ref([]); // 当前展开的节点
187 +const activeTabId = ref("tab-1"); // TODO: 需要获取默认第一个ID
188 +const activeTabContent = ref("");
189 +const userTabs = ref([]);
190 +const tabSelectData = ref([]);
191 +const userTabType = ref("tree");
192 +const tabOffset = ref("0px");
193 +const tabTextWidth = ref("76px"); // 文字宽度需要打开弹框时重新计算
194 +const is_active_search = ref(false);
195 +const search_input = ref("");
196 +const userList = ref([]);
197 +const checkedUserList = ref([]);
198 +const searchUserList = ref([]); // 搜索框 选中 用户ID
199 +const checkedSearchUserList = ref([]);
200 +const defaultProps = ref({
201 + children: "children",
202 + label: "label"
203 +});
204 +
205 +onMounted(() => {
206 + // console.log(props.visible);
207 + // // 显示提示框的标志位
208 + // var showConfirmation = true;
209 + // // 监听 beforeunload 事件
210 + // window.addEventListener('beforeunload', function (event) {
211 + // if (showConfirmation) {
212 + // // 取消事件的默认行为(弹出确认对话框)
213 + // event.preventDefault();
214 + // // Chrome 和 Firefox 需要返回一个值以显示确认对话框
215 + // event.returnValue = '';
216 + // // 显示自定义的提示信息
217 + // var confirmationMessage = '确定要离开此页面吗?';
218 + // (event || window.event).returnValue = confirmationMessage; // 兼容旧版浏览器
219 + // return confirmationMessage;
220 + // }
221 + // });
222 + // // 监听 unload 事件
223 + // window.addEventListener('unload', function () {
224 + // // 设置标志位为 false,避免在刷新页面时再次显示提示框
225 + // showConfirmation = false;
226 + // });
227 + // axios.get('/srv/?a=query_form_all_field')
228 + // .then(res => {
229 + // console.warn(res.data);
230 + // });
231 + // $('.el-tabs__nav')
232 + // 根据数据获取选中的tag标签
233 + // setTagsList(userTabs);
234 + // TODO:查询到的用户列表数据
235 + userTags.value = [{ id: "user-1-1", name: "用户1-1" }];
236 + userTabs.value = [
237 + {
238 + id: "tab-1",
239 + label: "组织架构",
240 + type: "tree",
241 + data: [
242 + {
243 + id: "dept-1",
244 + label: "上级部门 1",
245 + children: [
246 + {
247 + id: "dept-1-1",
248 + label: "部门名称 1-1",
249 + children: [],
250 + list: [
251 + {
252 + id: "user-1-1",
253 + label: "用户1-1",
254 + checked: false,
255 + disabled: false
256 + },
257 + {
258 + id: "user-1-2",
259 + label: "用户1-2",
260 + checked: false,
261 + disabled: false
262 + },
263 + {
264 + id: "user-1-3",
265 + label: "用户1-3",
266 + checked: false,
267 + disabled: true
268 + }
269 + ]
270 + }
271 + ]
272 + },
273 + {
274 + id: "dept-2",
275 + label: "上级部门 2",
276 + children: [
277 + {
278 + id: "dept-2-1",
279 + label: "部门名称 2-1",
280 + children: [],
281 + list: [
282 + {
283 + id: "user-2-1",
284 + label: "用户2-1",
285 + checked: false,
286 + disabled: false
287 + },
288 + {
289 + id: "user-2-2",
290 + label: "用户2-2",
291 + checked: false,
292 + disabled: false
293 + },
294 + {
295 + id: "user-2-3",
296 + label: "用户2-3",
297 + checked: false,
298 + disabled: true
299 + }
300 + ]
301 + }
302 + ]
303 + }
304 + ]
305 + },
306 + {
307 + id: "tab-2",
308 + label: "角色",
309 + type: "list",
310 + data: [
311 + {
312 + id: "role-1-1",
313 + label: "流程发起人",
314 + children: [],
315 + list: [
316 + {
317 + id: "user-1-1",
318 + label: "用户1-1",
319 + checked: true,
320 + disabled: false
321 + },
322 + {
323 + id: "user-2-2",
324 + label: "用户2-2",
325 + checked: false,
326 + disabled: false
327 + },
328 + {
329 + id: "role-1-4",
330 + label: "选项 C",
331 + checked: false,
332 + disabled: true
333 + }
334 + ]
335 + },
336 + {
337 + id: "role-1-2",
338 + label: "成员字段",
339 + children: [],
340 + list: []
341 + },
342 + {
343 + id: "role-1-3",
344 + label: "部门字段",
345 + children: [],
346 + list: []
347 + },
348 + {
349 + id: "role-1-4",
350 + label: "主管",
351 + children: [],
352 + list: []
353 + }
354 + ]
355 + }
356 + ];
357 + // 对比选中的用户的 ID,选中的 ID 同步状态到 userTabs 的数据结构上
358 + userTags.value.forEach(ele => {
359 + modifyFieldValue(userTabs.value, "id", ele.id, true);
360 + });
361 +});
362 +
363 +watch(() => {
364 + //
365 + if (props.visible) {
366 + dialogUserFormVisible.value = true;
367 + } else {
368 + dialogUserFormVisible.value = false;
369 + }
370 +});
371 +
372 +const handleTabClick = (tab, event, idx) => {
373 + // 点击Tab切换回调
374 + tabSelectData.value = tab.data; // tab选中数据提供给list类型使用
375 + userList.value = []; // 清空用户列表
376 + checkedUserList.value = []; // 清空选中用户列表
377 + activeTabContent.value = ""; // 清空侧边栏显示
378 + currentNodeKey.value = []; // 清空树形的默认展开
379 + // console.log(tab, event);
380 + // 设置当前激活的tab
381 + userTabType.value = tab.type;
382 + activeTabId.value = tab.id;
383 + // 子容器相对于父容器的相对x轴位移, 第一项为0
384 + if (idx) {
385 + tabOffset.value = $("#" + tab.id).position().left + 20 + "px";
386 + } else {
387 + tabOffset.value = "0px";
388 + }
389 + // 文字宽度
390 + tabTextWidth.value = $("#" + tab.id).width() + "px";
391 + // 检查列表第一项是否有值
392 + // if (tab?.data[0]?.list) {
393 + // let list = tab.data[0].list;
394 + // userList.value = list;
395 + // checkedUserList.value = list
396 + // .filter(ele => {
397 + // return ele.checked;
398 + // })
399 + // .map(ele => {
400 + // return ele.id;
401 + // });
402 + // }
403 +};
404 +
405 +const handleTabContentClick = (tab, event) => {
406 + // 侧边栏Tab点击回调
407 + // console.log(activeTabContent.value);
408 + // console.log(tab);
409 + // console.log(event);
410 + tabSelectData.value.forEach(ele => {
411 + if (ele.label === tab.props.label) {
412 + userList.value = ele.list;
413 + checkedUserList.value = ele.list
414 + .filter(ele => {
415 + return ele.checked;
416 + })
417 + .map(ele => {
418 + return ele.id;
419 + });
420 + }
421 + });
422 +};
423 +
424 +const handleNodeClick = data => {
425 + // 树形结构点击回调
426 + // console.log(data);
427 + if (data.list) {
428 + userList.value = data.list;
429 + checkedUserList.value = data.list
430 + .filter(ele => {
431 + return ele.checked;
432 + })
433 + .map(ele => {
434 + return ele.id;
435 + });
436 + // console.warn(checkedUserList.value);
437 + currentNodeKey.value = [data.id];
438 + }
439 +};
440 +
441 +/**
442 + * 递归查找匹配字段,并修改字段为新值
443 + *
444 + * @param {object} obj - 修改对象
445 + * @param {string} fieldName - 修改字段
446 + * @param {any} targetValue - 匹配值
447 + * @param {any} newValue - 修改新值
448 + */
449 +function modifyFieldValue(obj, fieldName, targetValue, newValue) {
450 + // 检查当前层级的字段值是否匹配目标值
451 + if (obj[fieldName] === targetValue) {
452 + obj["checked"] = newValue;
453 + }
454 +
455 + // 遍历当前层级的子项(如果有)
456 + for (var key in obj) {
457 + if (typeof obj[key] === "object" && obj[key] !== null) {
458 + modifyFieldValue(obj[key], fieldName, targetValue, newValue);
459 + }
460 + }
461 +}
462 +
463 +const handleCheckedUserListChange = (user, checked) => {
464 + // 勾选用户回调
465 + // 修改选中值状态
466 + modifyFieldValue(userTabs.value, "id", user.id, checked);
467 +};
468 +
469 +/**
470 + * 递归获取树形结构下的所有list字段数据
471 + *
472 + * @param {Array} arr - The array to traverse.
473 + * @return {Array} - The list items found.
474 + */
475 +function getAllListItems(arr) {
476 + var result = [];
477 +
478 + function recursiveGetListItems(subArr) {
479 + for (var i = 0; i < subArr.length; i++) {
480 + var item = subArr[i];
481 + if (item.list && Array.isArray(item.list)) {
482 + result = result.concat(item.list);
483 + }
484 + if (item.children && Array.isArray(item.children)) {
485 + recursiveGetListItems(item.children);
486 + }
487 + }
488 + }
489 +
490 + recursiveGetListItems(arr);
491 + return result;
492 +}
493 +
494 +/**
495 + * 根据 userTabs 的选中状态,更新 tag 选中列表显示
496 + * 获取生成 list 集合的 checked 状态,生成 tags 的勾选显示
497 + *
498 + * @param {Array} val - userTabs
499 + */
500 +const setTagsList = val => {
501 + let userList = []; // 存储所有的list数据
502 + val.forEach(ele => {
503 + let data = getAllListItems(ele.data);
504 + userList.push(data);
505 + });
506 + // 合并成一维数组列表
507 + userList = [...userList.flat()];
508 + // 如果ID相同,需要数据去重
509 + let uniqueArray = userList.filter(
510 + (value, index, self) => index === self.findIndex(obj => obj.id === value.id)
511 + );
512 + // 勾选变化后同步到选中列表
513 + userTags.value = uniqueArray
514 + .filter(ele => {
515 + return ele.checked;
516 + })
517 + .map(ele => {
518 + return {
519 + id: ele.id,
520 + name: ele.label
521 + };
522 + });
523 +};
524 +
525 +watch(
526 + // TAG: watch userTabs.value 监听数据结构选中值变化
527 + () => userTabs.value,
528 + val => {
529 + if (val) {
530 + // 更新 tags 选中列表显示
531 + setTagsList(val);
532 + }
533 + },
534 + { deep: true }
535 +);
536 +
537 +const handleTagClose = tag => {
538 + // 移除 tags 成员标签回调
539 + userTags.value.splice(userTags.value.indexOf(tag), 1); // 删除标签列表中的项
540 + // 移除数据结构中的选中值
541 + modifyFieldValue(userTabs.value, "id", tag.id, false);
542 + //
543 + let tagsId = userTags.value.map(ele => {
544 + return ele.id;
545 + });
546 + // 处理tags列表和下面选中列表同步问题
547 + let checkDataList = [];
548 + if (is_active_search.value) {
549 + // 激活搜索状态
550 + checkDataList = checkedSearchUserList.value;
551 + } else {
552 + // 激活非搜索状态
553 + checkDataList = checkedUserList.value;
554 + }
555 + // 获取 tags 中已删除的, 勾选列表中还勾选的值
556 + let result = checkDataList.filter(value => !tagsId.includes(value));
557 + // 如果存在不同步的值,需要在列表中删除勾选列表中的项
558 + if (result.length) {
559 + result.forEach(ele => {
560 + checkDataList.splice(checkDataList.indexOf(ele), 1);
561 + });
562 + }
563 +};
564 +
565 +const closeUserForm = () => {
566 + // 关闭用户列表表单回调
567 + dialogUserFormVisible.value = false;
568 + emit("close", dialogUserFormVisible.value);
569 +};
570 +const confirmUserForm = () => {
571 + // 确认用户列表表单回调
572 + dialogUserFormVisible.value = false;
573 + // console.log(userTags.value);
574 + emit("close", dialogUserFormVisible.value);
575 + emit("confirm", userTags.value);
576 +};
577 +
578 +const activeSearchBtn = () => {
579 + // 激活搜索按钮操作
580 + is_active_search.value = !is_active_search.value;
581 + searchUserList.value = []; // 清空搜索列表
582 +};
583 +
584 +const checkSearchStatus = () => {
585 + // 在搜索框中同步显示tag框选中的用户
586 + userTags.value.forEach(ele => {
587 + searchUserList.value.forEach(item => {
588 + if (ele.id === item.id) {
589 + item.checked = true;
590 + }
591 + });
592 + });
593 + // 显示选中tags的用户
594 + checkedSearchUserList.value = searchUserList.value
595 + .filter(ele => ele.checked)
596 + .map(ele => ele.id);
597 +};
598 +const checkUserStatus = () => {
599 + // 在搜索框中同步显示tag框选中的用户
600 + userTags.value.forEach(ele => {
601 + userList.value.forEach(item => {
602 + if (ele.id === item.id) {
603 + item.checked = true;
604 + }
605 + });
606 + });
607 + // 显示选中tags的用户
608 + checkedUserList.value = userList.value
609 + .filter(ele => ele.checked)
610 + .map(ele => ele.id);
611 +};
612 +
613 +const onClearSearch = () => {
614 + // 清空搜索回调
615 + is_active_search.value = !is_active_search.value;
616 + // 同步显示tag框选中的用户
617 + checkUserStatus();
618 +};
619 +const onSearch = () => {
620 + // 搜索回调
621 + // TODO: 获取后台数据
622 + searchUserList.value = [
623 + {
624 + id: "user-1-1",
625 + label: "用户1-1",
626 + checked: false,
627 + disabled: false
628 + },
629 + {
630 + id: "user-1-2",
631 + label: "用户1-2",
632 + checked: false,
633 + disabled: false
634 + },
635 + {
636 + id: "user-1-3",
637 + label: "用户1-3",
638 + checked: false,
639 + disabled: true
640 + }
641 + ];
642 + // 如果 tags 里存在的值,需要在搜索列表中勾选
643 + checkSearchStatus();
644 +};
645 +
646 +watch(
647 + // TAG: watch dialogUserFormVisible.value 监听弹框显示,修改tab文字宽度
648 + () => dialogUserFormVisible.value,
649 + val => {
650 + if (val) {
651 + nextTick(() => {
652 + tabTextWidth.value = $("#" + activeTabId.value).width() + "px";
653 + });
654 + }
655 + }
656 +);
657 +</script>
658 +
659 +<style lang="less" scoped></style>