准备调整通讯录

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

View File

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

View File

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