对接通讯录

This commit is contained in:
ywyonui 2025-07-09 11:02:03 +08:00
parent 6a30a11617
commit 275aacce23
3 changed files with 161 additions and 360 deletions

View File

@ -21,9 +21,6 @@ export const kmFindAllApi = async () => {
export const findAllXxXqNjTree = async () => { export const findAllXxXqNjTree = async () => {
return await get("/api/nj/findAllXxXqNjTree"); return await get("/api/nj/findAllXxXqNjTree");
}; };
export const findAllNjBjTree = async () => {
return await get("/api/nj/findAllNjBjTree");
};
export const jsConfirmJsDataApi = async (params: any) => { export const jsConfirmJsDataApi = async (params: any) => {
return await post("/api/js/confirmJsData", params); return await post("/api/js/confirmJsData", params);
}; };
@ -83,27 +80,11 @@ export const mobilejllistApi = async (params: any) => {
return res.result; return res.result;
}; };
export const getByJlIdApi = async (params: any) => {
const res = await get("/mobile/jl/getByJlId", params);
return res.result;
};
// 提交点名信息 // 提交点名信息
export const jsdXkdmListApi = async (params: any) => { export const jsdXkdmListApi = async (params: any) => {
return await post("/mobile/js/xkdm/add", params); return await post("/mobile/js/xkdm/add", params);
}; };
// 推送清单相关API
// 根据接龙ID获取学生信息
export const jlzxFindByJlParamsApi = async (params: { jlId: string }) => {
return await get("/api/jlzx/findByJlParams", params);
};
// 保存推送信息
export const xxtsSaveByJlzxParamsApi = async (params: { jlId: string }) => {
return await post("/api/xxts/saveByJlzxParams", params);
};
// 获取待办列表 // 获取待办列表
export const dbListApi = async (params: any) => { export const dbListApi = async (params: any) => {
return await get("/api/db/findPage", params); return await get("/api/db/findPage", params);
@ -124,11 +105,6 @@ 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) => { export const xsFindList = async (params: any) => {
return await get("/api/xs/findPage", params); return await get("/api/xs/findPage", params);

View File

@ -6,19 +6,19 @@
<view class="header-content"> <view class="header-content">
<image <image
class="avatar-large" class="avatar-large"
:src="studentDetail?.avatar" :src="xsInfo?.avatar"
mode="aspectFill" mode="aspectFill"
></image> ></image>
<text class="student-name">{{ studentDetail?.name }}</text> <text class="student-name">{{ xsInfo?.xm }}</text>
<view class="student-meta"> <view class="student-meta">
<uni-icons <uni-icons
:type="studentDetail?.gender === '女' ? 'female' : 'male'" :type="xsInfo?.gender === '女' ? 'female' : 'male'"
size="16" size="16"
:color="studentDetail?.gender === '女' ? '#ff8d8f' : '#fff'" :color="xsInfo?.gender === '女' ? '#ff8d8f' : '#fff'"
></uni-icons> ></uni-icons>
<text class="meta-text">{{ studentDetail?.gender }}</text> <text class="meta-text">{{ xsInfo?.gender }}</text>
<view class="separator"></view> <view class="separator"></view>
<text class="meta-text">{{ studentDetail?.age }}</text> <text class="meta-text">{{ xsInfo?.age }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -31,35 +31,35 @@
<view class="info-list"> <view class="info-list">
<view class="info-item"> <view class="info-item">
<text class="info-label">班级:</text> <text class="info-label">班级:</text>
<text class="info-value">{{ studentDetail?.classInfo }}</text> <text class="info-value">{{ xsInfo?.njmc + " " + xsInfo?.bjmc }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="info-label">出生日期:</text> <text class="info-label">出生日期:</text>
<text class="info-value">{{ studentDetail?.birthDate }}</text> <text class="info-value">{{ xsInfo?.birthDate }}</text>
</view> </view>
<view class="info-item"> <!-- <view class="info-item">
<text class="info-label">现居住地:</text> <text class="info-label">现居住地:</text>
<text class="info-value">{{ studentDetail?.address }}</text> <text class="info-value">{{ xsInfo?.address }}</text>
</view> </view> -->
</view> </view>
</view> </view>
<!-- 家长信息卡片 --> <!-- 家长信息卡片 -->
<view class="info-card"> <view class="info-card">
<text class="section-title">家长信息</text> <text class="section-title">家长信息</text>
<view class="parent-list-wrap"> <view class="parent-list-wrap" v-if="jzList.length > 0">
<view <view
v-for="parent in studentDetail?.parents" v-for="jz in jzList"
:key="parent.id" :key="jz.id"
class="parent-card-item" class="parent-card-item"
> >
<view class="parent-avatar-container"> <view class="parent-avatar-container">
<image <image
class="parent-avatar" class="parent-avatar"
:src="parent.avatar" :src="imagUrl(jz.avatar)"
mode="aspectFill" mode="aspectFill"
></image> ></image>
<view class="call-icon-overlay" @click="callParent(parent.phone)"> <view class="call-icon-overlay" @click="callParent(jz.jzsj)">
<uni-icons <uni-icons
type="phone-filled" type="phone-filled"
size="16" size="16"
@ -67,175 +67,43 @@
></uni-icons> ></uni-icons>
</view> </view>
</view> </view>
<text class="parent-name">{{ parent.name }}</text> <text class="parent-name">{{ jz.jzxm }}</text>
<text class="parent-relation">({{ parent.relation }})</text> <text class="parent-relation">({{ jz.jzxsgxId }})</text>
</view> </view>
</view> </view>
<view v-else class="no-parent-info">暂无家长信息</view>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from "vue"; import dayjs from "dayjs";
import { imagUrl } from "@/utils";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { xsJzListByXsIdApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
const { getData } = useDataStore();
interface Parent { const xsInfo = computed(() => {
id: string; const xs = getData || {};
name: string; // 使dayjs
relation: string; // '', '', '' etc. const birthDate = dayjs(xs.sfzh.substring(6, 10) + xs.sfzh.substring(10, 12) + xs.sfzh.substring(12, 14));
avatar: string; xs.birthDate = birthDate.format("YYYY-MM-DD");
phone: string; xs.age = dayjs().diff(birthDate, "year");
} return xs;
interface StudentDetail {
id: string;
name: string;
avatar: string;
gender: "男" | "女";
age: number;
classInfo: string;
birthDate: string;
address: string;
parents: Parent[];
}
const studentId = ref<string>("");
const studentDetail = ref<StudentDetail | null>(null);
onLoad((options) => {
if (options && options.id) {
studentId.value = options.id;
fetchStudentDetail();
} else {
console.error("Student ID is missing!");
uni.showToast({ title: "加载失败,缺少学生信息", icon: "none" });
// Optionally navigate back
// uni.navigateBack();
}
}); });
// const jzList = ref<any[]>([]);
const fetchStudentDetail = async () => {
console.log(`Fetching details for student: ${studentId.value}`);
// API
await new Promise((resolve) => setTimeout(resolve, 300));
// --- studentId.value --- onLoad(async (options) => {
if (xsInfo && xsInfo.value && xsInfo.value.id) {
// ( URL) uni.showLoading({ title: "加载中..." });
const mockData: Record<string, StudentDetail> = { const res = await xsJzListByXsIdApi({ xsId: xsInfo.value.id });
s1: { jzList.value = res.result || [];
id: "s1", uni.hideLoading();
name: "蒋晓",
avatar: "/static/mock/avatar1.png",
gender: "女",
age: 7,
classInfo: "一年级 (3) 班",
birthDate: "2017-03-15",
address: "四川省 泸州市",
parents: [
{
id: "p1",
name: "蒋爸爸",
relation: "爸爸",
avatar: "/static/mock/parent1.png",
phone: "13800001111",
},
{
id: "p2",
name: "蒋妈妈",
relation: "妈妈",
avatar: "/static/mock/parent2.png",
phone: "13900002222",
},
{
id: "p1",
name: "蒋爸爸",
relation: "爸爸",
avatar: "/static/mock/parent1.png",
phone: "13800001111",
},
{
id: "p2",
name: "蒋妈妈",
relation: "妈妈",
avatar: "/static/mock/parent2.png",
phone: "13900002222",
},
{
id: "p1",
name: "蒋爸爸",
relation: "爸爸",
avatar: "/static/mock/parent1.png",
phone: "13800001111",
},
{
id: "p2",
name: "蒋妈妈",
relation: "妈妈",
avatar: "/static/mock/parent2.png",
phone: "13900002222",
},
{
id: "p1",
name: "蒋爸爸",
relation: "爸爸",
avatar: "/static/mock/parent1.png",
phone: "13800001111",
},
{
id: "p2",
name: "蒋妈妈",
relation: "妈妈",
avatar: "/static/mock/parent2.png",
phone: "13900002222",
},
],
},
s2: {
id: "s2",
name: "卫振宇",
avatar: "/static/mock/avatar2.png",
gender: "女",
age: 7,
classInfo: "一年级 (3) 班",
birthDate: "2011-01-01",
address: "四川省 泸州市",
parents: [
{
id: "p3",
name: "周一鸣",
relation: "爸爸",
avatar: "/static/mock/parent3.png",
phone: "13700003333",
},
{
id: "p4",
name: "马泽惠",
relation: "妈妈",
avatar: "/static/mock/parent4.png",
phone: "13600004444",
},
{
id: "p5",
name: "关雄霖",
relation: "爷爷",
avatar: "/static/mock/parent5.png",
phone: "13500005555",
},
],
},
// Add more mock details for other students if needed
};
studentDetail.value = mockData[studentId.value] || null;
if (!studentDetail.value) {
console.error(`Details not found for student ID: ${studentId.value}`);
uni.showToast({ title: "未找到学生详情", icon: "none" });
} }
}; });
// //
const callParent = (phoneNumber: string) => { const callParent = (phoneNumber: string) => {
@ -361,6 +229,14 @@ const callParent = (phoneNumber: string) => {
} }
} }
.no-parent-info {
text-align: center;
color: #999;
font-size: 14px;
margin-top: 10px;
line-height: 80px;
}
/* 家长信息自动换行布局 */ /* 家长信息自动换行布局 */
.parent-list-wrap { .parent-list-wrap {
// display: flex; // Grid // display: flex; // Grid

View File

@ -2,35 +2,37 @@
<template> <template>
<view class="address-book-page"> <view class="address-book-page">
<!-- 1. 班级选择器 人数显示 --> <!-- 1. 班级选择器 人数显示 -->
<view class="class-selector"> <view class="filter-group">
<view class="bj-picker">
<NjBjPicker @change="changeNjBj" />
</view>
<!-- 添加一个搜索框搜索学生姓名 --> <!-- 添加一个搜索框搜索学生姓名 -->
<view class="search-section"> <view class="search-section">
<view class="search-box"> <view class="search-box">
<uni-icons type="search" size="18" color="#999"></uni-icons> <uni-icons type="search" size="18" color="#999"></uni-icons>
<input class="search-input" type="text" placeholder="搜索学生姓名..." v-model="searchKeyword" @input="onSearchInput" <input class="search-input" type="text" placeholder="搜索学生姓名" v-model="searchKeyword" />
@confirm="onSearchConfirm" />
<view class="search-clear" v-if="searchKeyword" @click="clearSearch"> <view class="search-clear" v-if="searchKeyword" @click="clearSearch">
<uni-icons type="clear" size="16" color="#999"></uni-icons> <uni-icons type="clear" size="16" color="#999"></uni-icons>
</view> </view>
</view> </view>
<!-- 增加一个搜索按钮 -->
<u-button text="搜索" class="search-btn" type="primary" @click="onSearchConfirm"/>
</view>
<view class="bj-xs-count">
<view class="bj-picker">
<NjBjPicker @change="changeNjBj" />
</view>
<text class="student-count"> {{ !isLoading && xsTotal > 0 ? xsTotal : 0 }} </text>
</view> </view>
<text class="student-count" v-if="!isLoading && studentList.length > 0"> {{ studentList.length }} </text>
</view> </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="xsList.length > 0"> <template v-else-if="xsList.length > 0">
<view v-for="xs in xsList" :key="xs.id" class="student-item" @click="goToDetail(xs.id)"> <view v-for="xs in xsList" :key="xs.id" class="student-item" @click="goToDetail(xs)">
<view class="student-info"> <view class="student-info">
<image class="avatar" :src="imagUrl(xs.avatar)" mode="aspectFill"></image> <image class="avatar" :src="xs.avatar" mode="aspectFill"></image>
<view class="details"> <view class="details">
<view class="name-role"> <view class="name-role">
<text class="name">{{ xs.name }}</text> <text class="name">{{ xs.xm }}</text>
<text v-if="xs.role" class="role-tag">{{ xs.role }}</text> <text v-if="xs.role" class="role-tag">{{ xs.role }}</text>
</view> </view>
<view class="gender"> <view class="gender">
@ -40,7 +42,7 @@
</view> </view>
</view> </view>
</view> </view>
<button class="contact-button" @click.stop="contactParent(xs.id)">联系家长</button> <button class="contact-button" @click.stop="contactParent(xs)">联系家长</button>
</view> </view>
</template> </template>
<view v-else class="empty-state">暂无学生数据</view> <view v-else class="empty-state">暂无学生数据</view>
@ -51,150 +53,81 @@
<script lang="ts" setup> <script lang="ts" setup>
import NjBjPicker from "@/pages/components/NjBjPicker/index.vue"; import NjBjPicker from "@/pages/components/NjBjPicker/index.vue";
import { ref, computed, onMounted } from 'vue';
import { imagUrl } from "@/utils"; import { imagUrl } from "@/utils";
import { xsFindList } from "@/api/base/server"; import { xsFindList } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
const { setData } = useDataStore();
const xsList = ref<any>([]); const xsList = ref<any>([]);
const xsTotal = ref(0);
let bjId = '';
let inputTimer: any = null;
// //
const changeNjBj = (nj: string, bj: string) => { const changeNjBj = (nj: any, bj: any) => {
bjId = bj.key;
getXsList();
console.log(nj, bj); console.log(nj, bj);
}; };
// --- ---
interface CombinedClass {
id: string;
name: string;
}
const combinedClassList = ref<CombinedClass[]>([]);
const selectedCombinedClassId = ref<string>('');
const isLoading = ref(false); 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 : '';
});
// --- Search State --- // --- Search State ---
const searchKeyword = ref<string>(''); const searchKeyword = ref<string>('');
const onSearchInput = () => { const onSearchInput = () => {
fetchStudentList();
}; };
const onSearchConfirm = () => { const onSearchConfirm = () => {
fetchStudentList(); getXsList();
}; };
const clearSearch = () => { const clearSearch = () => {
searchKeyword.value = ''; searchKeyword.value = '';
fetchStudentList(); getXsList();
}; };
//
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;
// curBjIndex.value = 0;
}
isLoading.value = false; //
};
//
const onCombinedClassChange = (e: any) => {
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();
}
};
// --- ---
// --- ---
interface Student {
id: string;
name: string;
avatar: string;
gender: '男' | '女';
role?: string; // ''
}
const studentList = ref<Student[]>([]);
// //
const fetchStudentList = async () => { const getXsList = async () => {
if (!selectedCombinedClassId.value) { if (!bjId) {
console.log("No class selected, cannot fetch student list."); xsList.value = []; //
studentList.value = [];
return; return;
} }
console.log(`Fetching student list for class: ${selectedCombinedClassName.value} (${selectedCombinedClassId.value})`);
isLoading.value = true; isLoading.value = true;
studentList.value = []; // xsList.value = []; //
await new Promise(resolve => setTimeout(resolve, 500)); // const res = await xsFindList({
bjId: bjId,
// --- API selectedCombinedClassId.value --- rows: 100,
let mockStudents: Student[] = []; xm: searchKeyword.value
if (selectedCombinedClassId.value === 'g1c101') { });
mockStudents = [ xsTotal.value = res.records as number;
{ id: 's1', name: '蒋晓', avatar: '/static/mock/avatar1.png', gender: '女', role: '班长' }, const list = res.rows as any[];
{ id: 's2', name: '卫振宇', avatar: '/static/mock/avatar2.png', gender: '女' }, xsList.value = list.map((xs: any) => {
{ id: 's3', name: '余建政', avatar: '/static/mock/avatar3.png', gender: '男' }, xs.avatar = imagUrl(xs.avatar);
]; // sfzh
} else if (selectedCombinedClassId.value === 'g1c102') { xs.gender = xs.sfzh.substring(xs.sfzh.length - 2, xs.sfzh.length - 1) % 2 === 0 ? '女' : '男';
mockStudents = [ return xs;
{ 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; isLoading.value = false;
console.log("Student list loaded.");
}; };
// //
const goToDetail = (studentId: string) => { const goToDetail = (xs: any) => {
setData(xs);
uni.navigateTo({ uni.navigateTo({
url: `/pages/view/homeSchool/parentAddressBook/detail?id=${studentId}&classId=${selectedCombinedClassId.value}` // id url: `/pages/view/homeSchool/parentAddressBook/detail`
}); });
}; };
// //
const contactParent = (studentId: string) => { const contactParent = (xs: any) => {
console.log('联系家长 for student:', studentId); console.log('联系家长 for student:', xs);
goToDetail(studentId); // goToDetail(xs); //
}; };
onMounted(async () => { onMounted(async () => {
await fetchCombinedClassList(); // await getXsList(); //
await fetchStudentList(); //
}); });
</script> </script>
@ -208,69 +141,85 @@ onMounted(async () => {
} }
/* 班级选择器样式 */ /* 班级选择器样式 */
.class-selector { .filter-group {
display: flex;
justify-content: space-between; // space-between justify-content: space-between; // space-between
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);
.student-count {
font-size: 14px;
color: #666;
white-space: nowrap; //
}
.bj-picker {
flex: 1 0 1px;
max-width: 45%;
}
.search-section { .search-section {
flex: 1 0 1px; display: flex;
background-color: #ffffff;
position: sticky;
top: 0;
z-index: 10;
padding: 0;
margin-bottom: 6px;
.search-box {
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 50rpx;
padding: 0 20rpx;
height: 70rpx;
flex: 1 0 1px;
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;
}
}
.search-btn {
margin-left: 10px;
flex: 0 0 80px;
height: 35px;
border-radius: 20px;
}
}
.bj-xs-count {
display: flex;
align-items: center;
.bj-picker {
flex: 1 0 1px;
}
.student-count {
margin-left: 10px;
font-size: 14px;
color: #666;
white-space: nowrap; //
flex: 0 0 80px;
text-align: center;
}
} }
} }
.search-section { .search-section {
padding: 0rpx 15rpx; 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 {