| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750 |
- <template>
- <view class="page_select_object">
- <CommonHeader title="选择通知对象" @back="goBack"></CommonHeader>
- <view class="tabs_container" :style="{ top: tabsTop + 'rpx' }">
- <view v-for="tab in tabs" :key="tab.key" class="tab_item" :class="{ active: activeTab === tab.key, disabled: editMode }"
- @click="!editMode && handleTabChange(tab.key)">
- <text class="tab_text">{{ tab.label }}</text>
- </view>
- </view>
- <SearchBar v-model="searchKeyword" @search="searchTree" :border="true" :isFixed="true" :top="searchBarTop + 'rpx'" :zIndex="100"></SearchBar>
- <scroll-view scroll-y class="content_list" :style="{ paddingTop: contentPaddingTop + 'rpx' }">
- <TreeNode :tree-data="treeData" :expanded-ids="expandedIds" @toggle-expand="toggleExpand"
- @toggle-select="toggleSelect" />
- </scroll-view>
- <view class="select_footer">
- <view class="confirm_btn" @click="handleConfirm">
- <text class="btn_text">确定(已选{{ selectedCount }}人)</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, computed, onMounted } from 'vue';
- import TreeNode from '@/components/tree-node.vue';
- import SearchBar from '@/components/search-bar.vue';
- import CommonHeader from '@/components/common-header.vue';
- import overview from '@/reqApi/overview.js';
- import { useSafeArea } from '@/common/safeArea';
- const { statusBarHeight, initSafeArea } = useSafeArea();
- const HEADER_CONTENT_HEIGHT = 130;
- const TABS_HEIGHT = 120;
- const SEARCHBAR_HEIGHT = 84;
- const SPACING = 16;
- const tabsTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT);
- const searchBarTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT);
- const contentPaddingTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT + SEARCHBAR_HEIGHT + SPACING);
- const activeTab = ref('department');
- const searchKeyword = ref('');
- const schoolYearId = ref('');
- const editMode = ref(false);
- const noticeType = ref(0);
- const selectedUserIds = ref([]);
- const tabs = [
- { key: 'department', label: '按部门' },
- { key: 'subject', label: '按学科' },
- { key: 'grade', label: '按年级' },
- { key: 'permission', label: '按权限' },
- { key: 'student', label: '按学生' }
- ];
- const treeData = ref([]);
- const fullTreeData = ref([]);
- const expandedIds = ref([]);
- // 按 teacherId 去重用户列表
- const deduplicateUsers = (users) => {
- if (!users || !Array.isArray(users)) return [];
- const seen = new Set();
- return users.filter(user => {
- const userId = user.userId;
- if (userId && seen.has(userId)) {
- return false;
- }
- if (userId) {
- seen.add(userId);
- }
- return true;
- });
- };
- // 转换按部门接口数据为树形结构
- const transformDepartmentData = (data) => {
- if (!data || !Array.isArray(data)) return [];
-
- return data.map(dept => {
- const groups = (dept.groupVOS || []).map(group => ({
- id: `${dept.id}_${group.groupId}`,
- name: group.groupName,
- checked: false,
- halfChecked: false,
- children: deduplicateUsers(group.groupUserVOS || []).map(user => ({
- id: user.userId,
- name: user.teacherName,
- userId: user.userId,
- userAccount: user.userAccount,
- checked: false,
- halfChecked: false
- }))
- }));
-
- return {
- id: dept.id,
- name: dept.name,
- checked: false,
- halfChecked: false,
- children: groups
- };
- });
- };
- // 根据 userId 同步所有相同用户的选中状态
- const syncTeacherSelection = (userId, checked) => {
- const syncNode = (nodes) => {
- nodes.forEach(node => {
- if (node.userId && node.userId === userId) {
- node.checked = checked;
- node.halfChecked = false;
- }
- if (node.children && node.children.length > 0) {
- syncNode(node.children);
- }
- });
- };
- syncNode(treeData.value);
- };
- // 转换按学科的数据为树形结构
- const transformSubjectData = (data) => {
- if (!data || !Array.isArray(data)) return [];
-
- return data.map(subject => {
- const grades = (subject.gradePersonVos || []).map(grade => ({
- id: `${subject.subjectCode}_${grade.schoolYearGradeId || grade.gradeCode}`,
- name: grade.gradeName,
- checked: false,
- halfChecked: false,
- children: deduplicateUsers(grade.personVoList || []).map(user => ({
- id: user.userId,
- name: user.teacherName,
- userId: user.userId,
- userAccount: user.userAccount,
- checked: false,
- halfChecked: false
- }))
- }));
-
- return {
- id: `subject_${subject.subjectCode}`,
- name: subject.subjectName,
- checked: false,
- halfChecked: false,
- children: grades
- };
- });
- };
- // 转换按年级的数据为树形结构
- const transformGradeData = (data) => {
- if (!data || !Array.isArray(data)) return [];
-
- return data.map(grade => {
- const classTypes = (grade.classTypePersonVoList || []).map(classType => {
- const classes = (classType.classInfoPersonVoList || []).map(cls => ({
- id: `${classType.classType}_${cls.classCode}`,
- name: cls.className,
- checked: false,
- halfChecked: false,
- children: deduplicateUsers(cls.personVoList || []).map(user => ({
- id: user.userId,
- name: user.teacherName,
- userId: user.userId,
- userAccount: user.userAccount,
- checked: false,
- halfChecked: false
- }))
- }));
-
- return {
- id: `${grade.schoolYearGradeId || grade.gradeCode}_${classType.classType}`,
- name: classType.classTypeName,
- checked: false,
- halfChecked: false,
- children: classes
- };
- });
-
- return {
- id: grade.schoolYearGradeId || grade.gradeCode,
- name: grade.gradeName,
- checked: false,
- halfChecked: false,
- children: classTypes
- };
- });
- };
- // 转换按权限的数据为树形结构
- const transformPersonalData = (data) => {
- if (!data || !Array.isArray(data)) return [];
-
- return data.map(item => {
- return {
- id: item.roleId,
- name: item.roleName,
- checked: false,
- halfChecked: false,
- children: deduplicateUsers(item.personVoList || []).map(user => ({
- id: user.userId,
- name: user.teacherName,
- userId: user.userId,
- userAccount: user.userAccount,
- checked: false,
- halfChecked: false
- }))
- };
- });
- };
- // 转换按学生的数据为树形结构
- const transformStudentData = (data) => {
- if (!data || !Array.isArray(data)) return [];
-
- return data.map(grade => {
- const classes = (grade.clsVOS || []).map(cls => ({
- id: `${grade.schoolYearGradeId || grade.gradeCode}_${cls.classId}`,
- name: cls.className,
- checked: false,
- halfChecked: false,
- children: deduplicateUsers(cls.studentTblVOS || []).map(student => ({
- id: student.userId,
- name: student.studentName,
- userId: student.userId,
- userAccount: student.studentCode,
- checked: false,
- halfChecked: false
- }))
- }));
-
- return {
- id: `student_${grade.schoolYearGradeId || grade.gradeCode}`,
- name: grade.gradeName,
- checked: false,
- halfChecked: false,
- children: classes
- };
- });
- };
- // 获取学年
- const getSchoolYear = async () => {
- try {
- const res = await overview.findSchoolYear();
- if (res.data && res.data.length > 0) {
- schoolYearId.value = res.data[0].id;
- }
- } catch (error) {
- console.error('获取学年失败:', error);
- }
- };
- // 获取按部门数据
- const loadDepartmentData = async () => {
- if (!schoolYearId.value) return;
-
- try {
- const res = await overview.find_depart_contain_user({ schoolYearId: schoolYearId.value });
- fullTreeData.value = transformDepartmentData(res.data);
- treeData.value = fullTreeData.value;
- expandedIds.value = [];
- } catch (error) {
- console.error('获取部门数据失败:', error);
- }
- };
- // 获取按学科/年级/权限数据
- const loadPersonalData = async (type) => {
- if (!schoolYearId.value) return;
-
- try {
- const res = await overview.find_collect_personal_info({ schoolYearId: schoolYearId.value });
-
- let data = [];
- let transformFn = transformPersonalData;
-
- if (type === 'subject') {
- data = res.data.subjectPersonVoList || [];
- transformFn = transformSubjectData;
- } else if (type === 'grade') {
- data = res.data.collectGradePersonVos || [];
- transformFn = transformGradeData;
- } else if (type === 'permission') {
- data = res.data.collectPermissionPersonVos || [];
- }
-
- fullTreeData.value = transformFn(data);
- treeData.value = fullTreeData.value;
- expandedIds.value = [];
- } catch (error) {
- console.error(`获取${type}数据失败:`, error);
- }
- };
- // 获取按学生数据
- const loadStudentData = async () => {
- if (!schoolYearId.value) return;
-
- try {
- const res = await overview.query_notice_student({ id: 0, schoolYearId: schoolYearId.value });
- fullTreeData.value = transformStudentData(res.data || []);
- treeData.value = fullTreeData.value;
- expandedIds.value = [];
- } catch (error) {
- console.error('获取学生数据失败:', error);
- }
- };
- // 切换标签
- const handleTabChange = async (key) => {
- activeTab.value = key;
-
- if (key === 'department') {
- await loadDepartmentData();
- } else if (key === 'subject' || key === 'grade' || key === 'permission') {
- await loadPersonalData(key);
- } else if (key === 'student') {
- await loadStudentData();
- }
- };
- const getDescendantIds = (nodes, parentId) => {
- const ids = [];
- const findDescendants = (items, targetId) => {
- items.forEach(item => {
- if (item.id === targetId && item.children && item.children.length > 0) {
- const collectIds = (children) => {
- children.forEach(child => {
- ids.push(child.id);
- if (child.children && child.children.length > 0) {
- collectIds(child.children);
- }
- });
- };
- collectIds(item.children);
- }
- if (item.children && item.children.length > 0) {
- findDescendants(item.children, targetId);
- }
- });
- };
- findDescendants(nodes, parentId);
- return ids;
- };
- const searchTree = (keyword) => {
- if (!keyword.trim()) {
- treeData.value = fullTreeData.value;
- return;
- }
-
- const filterNodes = (nodes) => {
- const filtered = [];
- nodes.forEach(node => {
- const hasMatch = node.name && node.name.includes(keyword);
- let childrenFiltered = [];
-
- if (node.children && node.children.length > 0) {
- childrenFiltered = filterNodes(node.children);
- }
-
- if (hasMatch || childrenFiltered.length > 0) {
- filtered.push({
- ...node,
- children: childrenFiltered
- });
- }
- });
- return filtered;
- };
-
- treeData.value = filterNodes(fullTreeData.value);
- };
- const toggleExpand = ({ itemId, siblingIds, isExpanded }) => {
- if (isExpanded) {
- expandedIds.value = expandedIds.value.filter(id => id !== itemId);
- } else {
- expandedIds.value = expandedIds.value.filter(id => {
- if (siblingIds.includes(id)) {
- return false;
- }
- return true;
- });
- expandedIds.value = [...expandedIds.value, itemId];
- }
- };
- // 向下级联:更新所有子节点的选中状态
- const toggleChildren = (children, checked) => {
- children.forEach(child => {
- child.checked = checked;
- child.halfChecked = false;
- if (child.children && child.children.length > 0) {
- toggleChildren(child.children, checked);
- }
- });
- };
- // 向上级联:更新父节点的选中状态
- const updateParentStatus = () => {
- const updateNode = (nodes) => {
- nodes.forEach(node => {
- if (node.children && node.children.length > 0) {
- updateNode(node.children);
- // 检查子节点的选中状态
- const childrenChecked = node.children.filter(c => c.checked);
- const childrenHalfChecked = node.children.filter(c => c.halfChecked);
- if (childrenChecked.length === node.children.length) {
- // 所有子节点都选中,父节点也选中
- node.checked = true;
- node.halfChecked = false;
- } else if (childrenChecked.length > 0 || childrenHalfChecked.length > 0) {
- // 部分子节点选中,父节点半选
- node.checked = false;
- node.halfChecked = true;
- } else {
- // 没有子节点选中
- node.checked = false;
- node.halfChecked = false;
- }
- }
- });
- };
- updateNode(treeData.value);
- };
- const toggleSelect = (item) => {
- item.checked = !item.checked;
- item.halfChecked = false;
- // 如果是叶子节点(用户),同步所有相同 userId 的节点
- if (!item.children || item.children.length === 0) {
- if (item.userId) {
- syncTeacherSelection(item.userId, item.checked);
- }
- } else {
- // 向下级联
- toggleChildren(item.children, item.checked);
-
- // 获取所有叶子节点的 userId 并同步到其他分组
- const getUserIds = (nodes) => {
- const ids = [];
- nodes.forEach(node => {
- if (node.userId) {
- ids.push(node.userId);
- } else if (node.children && node.children.length > 0) {
- ids.push(...getUserIds(node.children));
- }
- });
- return ids;
- };
-
- const userIds = getUserIds(item.children);
- userIds.forEach(id => {
- syncTeacherSelection(id, item.checked);
- });
- }
- // 向上级联
- updateParentStatus();
- };
- const selectedCount = computed(() => {
- const selectedUserIds = new Set();
- const collectChecked = (items) => {
- items.forEach(item => {
- if (item.children) {
- collectChecked(item.children);
- } else if (item.checked && item.userId) {
- selectedUserIds.add(item.userId);
- }
- });
- };
- collectChecked(treeData.value);
- return selectedUserIds.size;
- });
- const goBack = () => {
- uni.navigateBack();
- };
- const collectSelectedUsers = () => {
- const selectedUsers = [];
- const seen = new Set();
- const collect = (nodes, parentInfo = {}) => {
- nodes.forEach(node => {
- if (node.children && node.children.length > 0) {
- const newParentInfo = { ...parentInfo };
- if (node.name && !node.userId) {
- if (!newParentInfo.departmentName && !newParentInfo.subjectName && !newParentInfo.gradeName && !newParentInfo.roleName) {
- if (activeTab.value === 1) {
- newParentInfo.departmentName = node.name;
- } else if (activeTab.value === 2) {
- newParentInfo.subjectName = node.name;
- } else if (activeTab.value === 3) {
- newParentInfo.gradeName = node.name;
- } else if (activeTab.value === 4) {
- newParentInfo.roleName = node.name;
- } else if (activeTab.value === 5) {
- newParentInfo.gradeName = node.name;
- }
- } else if (!newParentInfo.groupName && !newParentInfo.classTypeName && !newParentInfo.className) {
- if (activeTab.value === 1) {
- newParentInfo.groupName = node.name;
- } else if (activeTab.value === 3) {
- newParentInfo.classTypeName = node.name;
- } else if (activeTab.value === 5) {
- newParentInfo.className = node.name;
- }
- } else if (!newParentInfo.className && activeTab.value === 3) {
- newParentInfo.className = node.name;
- }
- }
- collect(node.children, newParentInfo);
- } else if (node.checked && node.userId && !seen.has(node.userId)) {
- seen.add(node.userId);
- selectedUsers.push({
- id: node.id,
- name: node.name,
- userId: node.userId,
- thirdCode: node.userAccount,
- departmentName: parentInfo.departmentName || '',
- groupName: parentInfo.groupName || '',
- subjectName: parentInfo.subjectName || '',
- gradeName: parentInfo.gradeName || '',
- classTypeName: parentInfo.classTypeName || '',
- className: parentInfo.className || '',
- roleName: parentInfo.roleName || ''
- });
- }
- });
- };
- collect(treeData.value);
- return selectedUsers;
- };
- const handleConfirm = () => {
- const data = {
- tab: tabs.find(t => t.key === activeTab.value).label,
- count: selectedCount.value,
- users: collectSelectedUsers()
- };
-
- uni.setStorageSync('selectObjectResult', JSON.stringify(data));
- uni.navigateBack();
- };
- const setSelectedUsers = (users) => {
- if (!users || !Array.isArray(users)) return;
-
- const selectedTeacherIds = new Set(users.map(u => u.teacherId));
-
- const setNode = (nodes) => {
- nodes.forEach(node => {
- if (node.children && node.children.length > 0) {
- setNode(node.children);
- } else if (node.teacherId && selectedTeacherIds.has(node.teacherId)) {
- node.checked = true;
- }
- });
- };
-
- setNode(treeData.value);
- updateParentStatus();
- };
- const setSelectedUsersById = (userIds) => {
- if (!userIds || !Array.isArray(userIds)) return;
-
- const selectedIds = new Set(userIds);
-
- const setNode = (nodes) => {
- nodes.forEach(node => {
- if (node.children && node.children.length > 0) {
- setNode(node.children);
- } else if (node.id && selectedIds.has(node.id)) {
- node.checked = true;
- if (node.teacherId) {
- syncTeacherSelection(node.teacherId, true);
- }
- }
- });
- };
-
- setNode(treeData.value);
- updateParentStatus();
- };
- onMounted(async () => {
- initSafeArea();
- const pages = getCurrentPages();
- if (pages.length === 1) {
- uni.redirectTo({ url: '/pages/notice/publish/index' });
- return;
- }
-
- const lastSelection = uni.getStorageSync('selectObjectLastSelection');
- if (lastSelection) {
- try {
- const data = JSON.parse(lastSelection);
-
- if (data.isEdit) {
- editMode.value = true;
- }
-
- if (data.noticeType) {
- noticeType.value = data.noticeType;
- const typeMap = {
- 1: 'department',
- 2: 'subject',
- 3: 'grade',
- 4: 'permission',
- 5: 'student'
- };
- const tabKey = typeMap[data.noticeType];
- if (tabKey) {
- activeTab.value = tabKey;
- }
- } else if (data.tab) {
- const tab = tabs.find(t => t.label === data.tab);
- if (tab) {
- activeTab.value = tab.key;
- }
- }
-
- if (data.users) {
- selectedUserIds.value = data.users.map(u => u.userId || u.id);
- }
- } catch (e) {
- console.error('解析上次选择失败:', e);
- }
- }
-
- await getSchoolYear();
-
- if (activeTab.value === 'department') {
- await loadDepartmentData();
- } else if (activeTab.value === 'subject' || activeTab.value === 'grade' || activeTab.value === 'permission') {
- await loadPersonalData(activeTab.value);
- } else if (activeTab.value === 'student') {
- await loadStudentData();
- }
-
- if (selectedUserIds.value.length > 0) {
- setSelectedUsersById(selectedUserIds.value);
- } else if (lastSelection) {
- try {
- const data = JSON.parse(lastSelection);
- if (data.users) {
- setSelectedUsers(data.users);
- }
- } catch (e) {
- console.error('设置选中状态失败:', e);
- }
- }
- });
- </script>
- <style lang="scss" scoped>
- .page_select_object {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- }
- .tabs_container {
- position: fixed;
- left: 0;
- right: 0;
- z-index: 99;
- display: flex;
- padding: 24rpx ;
- background-color: #FFFFFF;
- justify-content: space-between;
- .tab_item {
- flex-shrink: 0;
- width: 122rpx;
- height: 72rpx;
- background: #FFFFFF;
- border-radius: 8rpx;
- border: 2rpx solid #DCDFE6;
- display: flex;
- align-items: center;
- justify-content: center;
- &.active {
- background: #2E64FA;
- border-color: #2E64FA;
- .tab_text {
- color: #FFFFFF;
- }
- }
- .tab_text {
- font-weight: 400;
- font-size: 28rpx;
- color: #333333;
- }
- &.disabled {
- pointer-events: none;
- opacity: 0.5;
- }
- }
- }
- .content_list {
- flex: 1;
- padding-bottom: 150rpx;
- }
- .select_footer {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- padding: 24rpx;
- background-color: #FFFFFF;
- border-top: 2rpx solid #F3F3F3;
- .confirm_btn {
- width: 100%;
- height: 90rpx;
- background: #2E64FA;
- border-radius: 8rpx;
- text-align: center;
- line-height: 90rpx;
- .btn_text {
- font-size: 32rpx;
- font-weight: 500;
- color: #FFFFFF;
- }
- }
- }
- </style>
|