准备调整通讯录

This commit is contained in:
ywyonui 2025-07-08 22:20:22 +08:00
parent a8a0edc5d4
commit 4caeb1d349
5 changed files with 306 additions and 139 deletions

View File

@ -108,3 +108,19 @@ export const xsQjSpApi = async (params: any) => {
return await post("/api/xsQj/sp", params); return await post("/api/xsQj/sp", params);
}; };
// 获取所有班级
export const bjFindAllApi = async (params: any) => {
return await get("/api/bj/findAll", params);
};
// 获取学生列表
export const xsFindList = async (params: any) => {
return await get("/api/xs/findPage", params);
};
// 获取学生家长列表
export const xsJzListByXsIdApi = async (params: any) => {
return await get("/api/jz/getListByXsId", params);
};

View File

@ -0,0 +1,93 @@
<template>
<picker mode="multiSelector" :range="njBjRange" :value="curIndex" @change="onMultiChange"
@columnchange="onColumnChange">
<view class="picker-item">
<text>{{ curNjBjLabel || "选择班级" }}</text>
<uni-icons type="bottom" size="14" color="#666"></uni-icons>
</view>
</picker>
</template>
<script lang="ts" setup>
import { findAllNjBjTreeApi } from "@/api/base/server";
import { on } from "events";
//
const props = withDefaults(defineProps<{
defaultValue: any
}>(), {
defaultValue: []
});
// emit
const emit = defineEmits(['change'])
const bjList = ref<any>([]);
const njBjRange = ref<any>([[], []]);
const curIndex = ref([0, 0]);
const curNjBjLabel = ref("");
const rebuildNjBjList = (njIndex: number) => {
const bjRange = bjList.value[njIndex].children.map((bj: any) => bj.title);
njBjRange.value[1] = bjRange;
}
const onChange = (index: any) => {
curIndex.value = index;
if (index.length === 2 && index[0] >= 0 && index[1] >= 0) {
curNjBjLabel.value = njBjRange.value[0][index[0]] + " " + njBjRange.value[1][index[1]];
emit("change", bjList.value[index[0]], bjList.value[index[0]].children[index[1]]);
} else {
curNjBjLabel.value = "";
emit("change", null, null);
}
}
const onMultiChange = (e: any) => {
const index = e.detail.value;
onChange(index);
}
const onColumnChange = (e: any) => {
const column = e.detail.column;
const index = e.detail.value;
if (column == 0) {
rebuildNjBjList(index);
}
}
onMounted(async () => {
const res = await findAllNjBjTreeApi();
bjList.value = res.result;
const njRange = bjList.value.map((nj: any) => nj.title);
const bjRange = bjList.value[0].children.map((bj: any) => bj.title);
njBjRange.value = [njRange, bjRange];
onChange([0, 0]);
});
if (props.defaultValue && props.defaultValue.length > 0) {
curIndex.value = props.defaultValue;
}
</script>
<style scoped lang="scss">
.picker-item {
display: flex;
align-items: center;
padding: 7px 15px;
background-color: #f7f7f7;
border: 1px solid #eee;
border-radius: 16px;
font-size: 14px;
color: #333;
justify-content: space-between;
text {
margin-right: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>

View File

@ -3,102 +3,127 @@
<view class="address-book-page"> <view class="address-book-page">
<!-- 1. 班级选择器 人数显示 --> <!-- 1. 班级选择器 人数显示 -->
<view class="class-selector"> <view class="class-selector">
<picker <view class="bj-picker">
mode="selector" <NjBjPicker @change="changeNjBj" />
:range="combinedClassRange" </view>
:value="selectedCombinedClassIndex" <!-- 添加一个搜索框搜索学生姓名 -->
@change="onCombinedClassChange" <view class="search-section">
> <view class="search-box">
<view class="picker-item"> <uni-icons type="search" size="18" color="#999"></uni-icons>
<text>{{ selectedCombinedClassName || "选择班级" }}</text> <input class="search-input" type="text" placeholder="搜索学生姓名..." v-model="searchKeyword" @input="onSearchInput"
<uni-icons type="bottom" size="14" color="#666"></uni-icons> @confirm="onSearchConfirm" />
</view> <view class="search-clear" v-if="searchKeyword" @click="clearSearch">
</picker> <uni-icons type="clear" size="16" color="#999"></uni-icons>
<text class="student-count" v-if="!isLoading && studentList.length > 0"> {{ studentList.length }} </text> </view>
</view> </view>
</view>
<text class="student-count" v-if="!isLoading && studentList.length > 0"> {{ studentList.length }} </text>
</view>
<!-- 2. 学生列表 --> <!-- 2. 学生列表 -->
<scroll-view scroll-y class="student-list-container"> <scroll-view scroll-y class="student-list-container">
<view v-if="isLoading" class="loading-indicator">加载中...</view> <view v-if="isLoading" class="loading-indicator">加载中...</view>
<template v-else-if="studentList.length > 0"> <template v-else-if="xsList.length > 0">
<view <view v-for="xs in xsList" :key="xs.id" class="student-item" @click="goToDetail(xs.id)">
v-for="student in studentList" <view class="student-info">
:key="student.id" <image class="avatar" :src="imagUrl(xs.avatar)" mode="aspectFill"></image>
class="student-item" <view class="details">
@click="goToDetail(student.id)" <view class="name-role">
> <text class="name">{{ xs.name }}</text>
<view class="student-info"> <text v-if="xs.role" class="role-tag">{{ xs.role }}</text>
<image class="avatar" :src="student.avatar" mode="aspectFill"></image> </view>
<view class="details"> <view class="gender">
<view class="name-role"> <uni-icons :type="xs.gender === '女' ? 'person-filled' : 'person'" size="14"
<text class="name">{{ student.name }}</text> :color="xs.gender === '女' ? '#ff5a5f' : '#007aff'"></uni-icons>
<text v-if="student.role" class="role-tag">{{ student.role }}</text> <text>{{ xs.gender }}</text>
</view> </view>
<view class="gender"> </view>
<uni-icons :type="student.gender === '女' ? 'person-filled' : 'person'" size="14" :color="student.gender === '女' ? '#ff5a5f' : '#007aff'"></uni-icons> </view>
<text>{{ student.gender }}</text> <button class="contact-button" @click.stop="contactParent(xs.id)">联系家长</button>
</view> </view>
</view> </template>
</view> <view v-else class="empty-state">暂无学生数据</view>
<button class="contact-button" @click.stop="contactParent(student.id)">联系家长</button>
</view>
</template>
<view v-else class="empty-state">暂无学生数据</view>
</scroll-view> </scroll-view>
</view> </view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import NjBjPicker from "@/pages/components/NjBjPicker/index.vue";
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { imagUrl } from "@/utils";
import { xsFindList } from "@/api/base/server";
const xsList = ref<any>([]);
//
const changeNjBj = (nj: string, bj: string) => {
console.log(nj, bj);
};
// --- --- // --- ---
interface CombinedClass { interface CombinedClass {
id: string; id: string;
name: string; name: string;
} }
const combinedClassList = ref<CombinedClass[]>([]); const combinedClassList = ref<CombinedClass[]>([]);
const selectedCombinedClassId = ref<string>(''); const selectedCombinedClassId = ref<string>('');
const selectedCombinedClassIndex = ref(-1);
const isLoading = ref(false); const isLoading = ref(false);
const combinedClassRange = computed(() => combinedClassList.value.map(c => c.name)); const combinedClassRange = computed(() => combinedClassList.value.map(c => c.name));
const selectedCombinedClassName = computed(() => { const selectedCombinedClassName = computed(() => {
const cls = combinedClassList.value.find(c => c.id === selectedCombinedClassId.value); const cls = combinedClassList.value.find(c => c.id === selectedCombinedClassId.value);
return cls ? cls.name : ''; return cls ? cls.name : '';
}); });
// --- Search State ---
const searchKeyword = ref<string>('');
const onSearchInput = () => {
fetchStudentList();
};
const onSearchConfirm = () => {
fetchStudentList();
};
const clearSearch = () => {
searchKeyword.value = '';
fetchStudentList();
};
// //
const fetchCombinedClassList = async () => { const fetchCombinedClassList = async () => {
console.log("Fetching combined class list..."); console.log("Fetching combined class list...");
isLoading.value = true; isLoading.value = true;
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
combinedClassList.value = [ combinedClassList.value = [
{ id: 'g1c101', name: '一年级 01班' }, { id: 'g1c101', name: '一年级 01班' },
{ id: 'g1c102', name: '一年级 02班' }, { id: 'g1c102', name: '一年级 02班' },
{ id: 'g2c201', name: '二年级 01班' }, { id: 'g2c201', name: '二年级 01班' },
{ id: 'g2c202', name: '二年级 02班' }, { id: 'g2c202', name: '二年级 02班' },
{ id: 'g3c301', name: '三年级 01班' }, { id: 'g3c301', name: '三年级 01班' },
]; ];
console.log("Class list loaded."); console.log("Class list loaded.");
// //
if (combinedClassList.value.length > 0 && !selectedCombinedClassId.value) { if (combinedClassList.value.length > 0 && !selectedCombinedClassId.value) {
selectedCombinedClassId.value = combinedClassList.value[0].id; selectedCombinedClassId.value = combinedClassList.value[0].id;
selectedCombinedClassIndex.value = 0; // curBjIndex.value = 0;
} }
isLoading.value = false; // isLoading.value = false; //
}; };
// //
const onCombinedClassChange = (e: any) => { const onCombinedClassChange = (e: any) => {
const index = parseInt(e.detail.value); const index = parseInt(e.detail.value);
selectedCombinedClassIndex.value = index; // curBjIndex.value = index;
const selectedClass = combinedClassList.value[index]; const selectedClass = combinedClassList.value[index];
if (selectedClass && selectedClass.id !== selectedCombinedClassId.value) { if (selectedClass && selectedClass.id !== selectedCombinedClassId.value) {
selectedCombinedClassId.value = selectedClass.id; selectedCombinedClassId.value = selectedClass.id;
// //
fetchStudentList(); fetchStudentList();
} }
}; };
// --- --- // --- ---
@ -116,41 +141,41 @@ const studentList = ref<Student[]>([]);
// //
const fetchStudentList = async () => { const fetchStudentList = async () => {
if (!selectedCombinedClassId.value) { if (!selectedCombinedClassId.value) {
console.log("No class selected, cannot fetch student list."); console.log("No class selected, cannot fetch student list.");
studentList.value = []; studentList.value = [];
return; return;
} }
console.log(`Fetching student list for class: ${selectedCombinedClassName.value} (${selectedCombinedClassId.value})`); console.log(`Fetching student list for class: ${selectedCombinedClassName.value} (${selectedCombinedClassId.value})`);
isLoading.value = true; isLoading.value = true;
studentList.value = []; // studentList.value = []; //
await new Promise(resolve => setTimeout(resolve, 500)); // await new Promise(resolve => setTimeout(resolve, 500)); //
// --- API selectedCombinedClassId.value --- // --- API selectedCombinedClassId.value ---
let mockStudents: Student[] = []; let mockStudents: Student[] = [];
if (selectedCombinedClassId.value === 'g1c101') { if (selectedCombinedClassId.value === 'g1c101') {
mockStudents = [ mockStudents = [
{ id: 's1', name: '蒋晓', avatar: '/static/mock/avatar1.png', gender: '女', role: '班长' }, { id: 's1', name: '蒋晓', avatar: '/static/mock/avatar1.png', gender: '女', role: '班长' },
{ id: 's2', name: '卫振宇', avatar: '/static/mock/avatar2.png', gender: '女' }, { id: 's2', name: '卫振宇', avatar: '/static/mock/avatar2.png', gender: '女' },
{ id: 's3', name: '余建政', avatar: '/static/mock/avatar3.png', gender: '男' }, { id: 's3', name: '余建政', avatar: '/static/mock/avatar3.png', gender: '男' },
]; ];
} else if (selectedCombinedClassId.value === 'g1c102') { } else if (selectedCombinedClassId.value === 'g1c102') {
mockStudents = [ mockStudents = [
{ id: 's4', name: '马钰源', avatar: '/static/mock/avatar4.png', gender: '女' }, { id: 's4', name: '马钰源', avatar: '/static/mock/avatar4.png', gender: '女' },
{ id: 's5', name: '关帆', avatar: '/static/mock/avatar5.png', gender: '女' }, { id: 's5', name: '关帆', avatar: '/static/mock/avatar5.png', gender: '女' },
]; ];
} else if (selectedCombinedClassId.value === 'g2c201') { } else if (selectedCombinedClassId.value === 'g2c201') {
mockStudents = [ mockStudents = [
{ id: 's6', name: '苗佳怡', avatar: '/static/mock/avatar6.png', gender: '男' }, { id: 's6', name: '苗佳怡', avatar: '/static/mock/avatar6.png', gender: '男' },
{ id: 's7', name: '丁溶溶', avatar: '/static/mock/avatar7.png', gender: '女' }, { id: 's7', name: '丁溶溶', avatar: '/static/mock/avatar7.png', gender: '女' },
{ id: 's8', name: '薛夫子', avatar: '/static/mock/avatar8.png', gender: '男' }, { id: 's8', name: '薛夫子', avatar: '/static/mock/avatar8.png', gender: '男' },
]; ];
} }
// ... ... // ... ...
studentList.value = mockStudents; studentList.value = mockStudents;
isLoading.value = false; isLoading.value = false;
console.log("Student list loaded."); console.log("Student list loaded.");
}; };
@ -168,8 +193,8 @@ const contactParent = (studentId: string) => {
}; };
onMounted(async () => { onMounted(async () => {
await fetchCombinedClassList(); // await fetchCombinedClassList(); //
await fetchStudentList(); // await fetchStudentList(); //
}); });
</script> </script>
@ -188,34 +213,65 @@ onMounted(async () => {
justify-content: space-between; // space-between justify-content: space-between; // space-between
align-items: center; align-items: center;
padding: 10px 15px; padding: 10px 15px;
background-color: #fff; background-color: #fff;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
.picker-item {
display: flex;
align-items: center;
padding: 5px 15px;
background-color: #f7f7f7;
border: 1px solid #eee;
border-radius: 16px;
font-size: 14px;
color: #333;
min-width: 160px;
justify-content: space-between;
text {
margin-right: 8px;
}
}
.student-count { .student-count {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
white-space: nowrap; // white-space: nowrap; //
} }
.bj-picker {
flex: 1 0 1px;
max-width: 45%;
}
.search-section {
flex: 1 0 1px;
}
} }
.search-section {
padding: 0rpx 15rpx;
background-color: #ffffff;
position: sticky;
top: 0;
z-index: 10;
.search-box {
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 50rpx;
padding: 0 20rpx;
height: 70rpx;
uni-icons {
margin-right: 15rpx;
}
.search-input {
flex: 1;
height: 100%;
border: none;
background: transparent;
font-size: 28rpx;
color: #333;
&::placeholder {
color: #999;
}
}
.search-clear {
margin-left: 15rpx;
cursor: pointer;
}
}
}
.student-list-container { .student-list-container {
flex: 1; flex: 1;
@ -226,10 +282,10 @@ onMounted(async () => {
.loading-indicator, .loading-indicator,
.empty-state { .empty-state {
text-align: center; text-align: center;
color: #999; color: #999;
padding: 30px 15px; padding: 30px 15px;
font-size: 14px; font-size: 14px;
} }
.student-item { .student-item {
@ -298,9 +354,10 @@ onMounted(async () => {
uni-icons { uni-icons {
margin-right: 3px; margin-right: 3px;
} }
text {
line-height: 1; // text {
} line-height: 1; //
}
} }
} }
@ -319,9 +376,10 @@ onMounted(async () => {
&::after { &::after {
border: none; border: none;
} }
// //
&:active { &:active {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
} }
</style> </style>

View File

@ -28,7 +28,7 @@ import { useForm } from "@/components/BasicForm/hooks/useForm";
import { dicApi } from "@/api/system/dic"; import { dicApi } from "@/api/system/dic";
import { useDataStore } from "@/store/modules/data"; import { useDataStore } from "@/store/modules/data";
import { import {
findAllNjBjTree, findAllNjBjTreeApi,
findAllXxXqNjTree, findAllXxXqNjTree,
kmFindAllApi, kmFindAllApi,
} from "@/api/base/server"; } from "@/api/base/server";
@ -82,7 +82,7 @@ const [register, { getValue, setValue }] = useForm({
// label: "", // label: "",
// component: "BasicPicker", // component: "BasicPicker",
// componentProps: { // componentProps: {
// api: findAllNjBjTree, // api: findAllNjBjTreeApi,
// rangeKey: "title", // rangeKey: "title",
// savaKey: "key", // savaKey: "key",
// }, // },
@ -102,7 +102,7 @@ const [register, { getValue, setValue }] = useForm({
// label: "", // label: "",
// component: "BasicPicker", // component: "BasicPicker",
// componentProps: { // componentProps: {
// api: findAllNjBjTree, // api: findAllNjBjTreeApi,
// rangeKey: "title", // rangeKey: "title",
// savaKey: "key", // savaKey: "key",
// }, // },
@ -122,7 +122,7 @@ const [register, { getValue, setValue }] = useForm({
// label: "", // label: "",
// component: "BasicTree", // component: "BasicTree",
// componentProps: { // componentProps: {
// api: findAllNjBjTree, // api: findAllNjBjTreeApi,
// rangeKey: "title", // rangeKey: "title",
// savaKey: "key", // savaKey: "key",
// }, // },

View File

@ -223,7 +223,7 @@ import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import BasicTree from "@/components/BasicTree/Tree.vue"; import BasicTree from "@/components/BasicTree/Tree.vue";
import { attachmentUpload } from "@/api/system/upload"; import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";
import { findAllNjBjTree, mobilejlstudentListApi } from "@/api/base/server"; import { findAllNjBjTreeApi, mobilejlstudentListApi } from "@/api/base/server";
interface Attachment { interface Attachment {
name: string; name: string;
@ -286,7 +286,7 @@ const displayNames = computed(() => {
// //
const loadTreeData = async () => { const loadTreeData = async () => {
try { try {
const res = await findAllNjBjTree(); const res = await findAllNjBjTreeApi();
if (res.resultCode === 1 && res.result) { if (res.resultCode === 1 && res.result) {
// BasicTree // BasicTree
treeData.value = res.result.map((item: any) => ({ treeData.value = res.result.map((item: any) => ({