Files
hzdyjs/mobile/src/pages/visit/index.vue
T
2025-10-25 17:19:54 +08:00

661 lines
20 KiB
Vue

<template>
<view class="visit-container">
<!-- 顶部导航 -->
<u-navbar title="班主任走访" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
<view slot="left" @click="goBack">
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
</view>
</u-navbar>
<!-- 搜索栏 -->
<view class="search-bar">
<u-search
v-model="searchKeyword"
placeholder="搜索学生姓名、学号或企业"
:show-action="false"
@search="handleSearch"
@custom="handleSearch"
bg-color="#f6f6f6"
shape="round"
/>
</view>
<!-- 筛选条件 -->
<view class="filter-bar" v-if="showFilter">
<view class="filter-item">
<u-input
v-model="queryParams.visitorName"
placeholder="走访人姓名"
border="surround"
clearable
/>
</view>
<view class="filter-item">
<view @click="showStatusPicker = true">
<u-input
v-model="statusText"
placeholder="选择走访状态"
readonly
border="surround"
suffix-icon="arrow-down"
/>
</view>
</view>
<view class="filter-item">
<view @click="showDatePicker = true">
<u-input
v-model="dateRangeText"
placeholder="选择走访日期"
readonly
border="surround"
suffix-icon="calendar"
/>
</view>
</view>
<view class="filter-actions">
<u-button
type="primary"
shape="round"
size="small"
@click="handleFilter"
:loading="loading"
>
筛选
</u-button>
<u-button
type="info"
shape="round"
size="small"
@click="resetFilter"
style="margin-left: 10rpx"
>
重置
</u-button>
</view>
</view>
<!-- 筛选按钮 -->
<view class="action-bar">
<u-button
type="info"
shape="round"
size="small"
:icon="showFilter ? 'arrow-up' : 'arrow-down'"
@click="showFilter = !showFilter"
>
{{ showFilter ? '收起筛选' : '展开筛选' }}
</u-button>
<u-button type="primary" shape="round" size="small" icon="plus" @click="handleAdd">
新增走访
</u-button>
</view>
<!-- 加载状态 -->
<u-loading-page
:loading="loading && visitList.length === 0"
loading-text="加载中..."
></u-loading-page>
<!-- 走访记录列表 -->
<view v-if="!loading || visitList.length > 0" class="content-wrapper">
<view v-if="visitList.length === 0" class="empty-container">
<u-empty text="暂无走访记录" mode="data"></u-empty>
</view>
<view class="list-content" v-else>
<view
class="visit-card"
v-for="item in visitList"
:key="item.visitId"
@click="handleView(item)"
>
<!-- 学生信息 -->
<view class="card-header">
<view class="student-info">
<text class="student-name">{{ item.studentName }}</text>
<text class="student-no">{{ item.studentNo }}</text>
</view>
<view class="status-badge">
<u-tag
:text="getStatusLabel(item.status)"
:type="getStatusType(item.status)"
size="mini"
/>
</view>
</view>
<!-- 企业信息 -->
<view class="card-row">
<u-icon name="home" size="16" color="#909399" />
<text class="info-text">{{ item.companyName || '-' }}</text>
</view>
<!-- 走访信息 -->
<view class="card-row">
<u-icon name="calendar" size="16" color="#909399" />
<text class="info-text">{{ item.visitDate || '-' }}</text>
<u-icon
name="account"
size="16"
color="#909399"
style="margin-left: 20rpx"
/>
<text class="info-text">{{ item.visitorName || '-' }}</text>
</view>
<!-- 班级专业信息 -->
<view class="card-row">
<u-icon name="bookmark" size="16" color="#909399" />
<text class="info-text">
{{ item.className || '-' }} / {{ item.majorName || '-' }}
</text>
</view>
<!-- 操作按钮 -->
<view class="card-actions">
<u-button
type="primary"
shape="round"
size="small"
@click.stop="handleEdit(item)"
v-if="item.status === 'planned' || item.status === '0'"
>
<u-icon
name="edit-pen"
size="14"
color="#fff"
style="margin-right: 4rpx"
></u-icon>
编辑
</u-button>
<u-button
type="error"
shape="round"
size="small"
@click.stop="handleDelete(item)"
style="margin-left: 10rpx"
v-if="item.status === 'planned' || item.status === '0'"
>
<u-icon
name="trash"
size="14"
color="#fff"
style="margin-right: 4rpx"
></u-icon>
删除
</u-button>
<u-button
type="info"
shape="round"
size="small"
plain
@click.stop="handleView(item)"
v-else
>
<u-icon name="eye" size="14" style="margin-right: 4rpx"></u-icon>
查看
</u-button>
</view>
</view>
</view>
<!-- 上拉加载更多 -->
<view class="load-more" v-if="hasMore">
<u-loadmore
:status="loadMoreStatus"
@loadmore="loadMore"
load-text="加载更多"
loading-text="正在加载..."
no-more-text="没有更多了"
/>
</view>
</view>
<!-- 状态选择器 -->
<u-picker
:show="showStatusPicker"
:columns="statusOptions"
:default-index="[currentStatusIndex]"
@confirm="handleStatusConfirm"
@cancel="showStatusPicker = false"
/>
<!-- 日期范围选择器 -->
<u-calendar
:show="showDatePicker"
mode="range"
@confirm="handleDateConfirm"
@close="showDatePicker = false"
/>
</view>
</template>
<script>
import { listVisit, delVisit, getVisitStatusDict } from '@/api/visit.js';
export default {
name: 'VisitList',
data() {
return {
// 搜索和筛选
searchKeyword: '',
showFilter: false,
queryParams: {
pageNum: 1,
pageSize: 10,
studentKeyword: '',
companyName: '',
visitorName: '',
status: '',
beginVisitDate: '',
endVisitDate: '',
},
// 列表数据
visitList: [],
total: 0,
loading: false,
hasMore: true,
loadMoreStatus: 'loadmore',
// 状态选择
showStatusPicker: false,
statusOptions: [],
currentStatusIndex: -1,
statusText: '',
statusDict: [],
// 日期选择
showDatePicker: false,
dateRangeText: '',
selectedDateRange: [],
};
},
onLoad() {
this.initPage();
},
onPullDownRefresh() {
this.refreshList();
},
onReachBottom() {
if (this.hasMore && this.loadMoreStatus === 'loadmore') {
this.loadMore();
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// 初始化页面
async initPage() {
await this.loadStatusDict();
this.getList();
},
// 加载状态字典
async loadStatusDict() {
try {
const response = await getVisitStatusDict();
if (response.code === 200 && response.data) {
this.statusDict = response.data;
this.statusOptions = [
[
{ text: '全部状态', value: '' },
...response.data.map((item) => ({
text: item.dictLabel,
value: item.dictValue,
})),
],
];
}
} catch (error) {
console.error('加载状态字典失败:', error);
// 设置默认状态选项
this.statusOptions = [
[
{ text: '全部状态', value: '' },
{ text: '计划中', value: 'planned' },
{ text: '已完成', value: 'completed' },
{ text: '已取消', value: 'cancelled' },
],
];
}
},
// 获取列表数据
async getList(isLoadMore = false) {
if (this.loading) return;
this.loading = true;
if (!isLoadMore) {
this.loadMoreStatus = 'loadmore';
} else {
this.loadMoreStatus = 'loading';
}
try {
const response = await listVisit(this.queryParams);
if (response.code === 200) {
// 后端返回的是TableDataInfo格式,直接包含rows和total字段
const { rows, total } = response;
if (isLoadMore) {
this.visitList = [...this.visitList, ...(rows || [])];
} else {
this.visitList = rows || [];
}
this.total = total || 0;
// 判断是否还有更多数据
const currentTotal = this.visitList.length;
this.hasMore = currentTotal < this.total;
if (!this.hasMore) {
this.loadMoreStatus = 'nomore';
}
} else {
uni.showToast({ title: response.msg || '获取数据失败', icon: 'none' });
}
} catch (error) {
console.error('获取走访记录列表失败:', error);
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
} finally {
this.loading = false;
if (this.queryParams.pageNum === 1) {
uni.stopPullDownRefresh();
}
}
},
// 刷新列表
refreshList() {
this.queryParams.pageNum = 1;
this.getList();
},
// 加载更多
loadMore() {
if (this.hasMore && !this.loading) {
this.queryParams.pageNum++;
this.getList(true);
}
},
// 搜索
handleSearch() {
this.queryParams.studentKeyword = this.searchKeyword;
this.refreshList();
},
// 筛选
handleFilter() {
this.refreshList();
},
// 重置筛选
resetFilter() {
this.queryParams = {
...this.queryParams,
studentKeyword: this.searchKeyword,
companyName: '',
visitorName: '',
status: '',
beginVisitDate: '',
endVisitDate: '',
};
this.statusText = '';
this.dateRangeText = '';
this.selectedDateRange = [];
this.currentStatusIndex = -1;
this.refreshList();
},
// 状态选择确认
handleStatusConfirm(value) {
const selected = value.value[0];
this.queryParams.status = selected.value;
this.statusText = selected.text;
this.currentStatusIndex = value.index[0];
this.showStatusPicker = false;
},
// 日期选择确认
handleDateConfirm(value) {
this.selectedDateRange = value;
if (value && value.length === 2) {
this.queryParams.beginVisitDate = value[0];
this.queryParams.endVisitDate = value[1];
this.dateRangeText = `${value[0]} ~ ${value[1]}`;
} else {
this.queryParams.beginVisitDate = '';
this.queryParams.endVisitDate = '';
this.dateRangeText = '';
}
this.showDatePicker = false;
},
// 获取状态标签文本
getStatusLabel(status) {
// 如果statusDict字典已加载,从字典中查找
if (this.statusDict && this.statusDict.length > 0) {
const dictItem = this.statusDict.find((item) => item.dictValue === String(status));
if (dictItem) {
return dictItem.dictLabel;
}
}
// 如果字典未加载或找不到,使用默认映射
switch (String(status)) {
case 'planned':
case '0':
return '计划中';
case 'completed':
case '1':
return '已完成';
case 'cancelled':
case '2':
return '已取消';
default:
return status || '未知';
}
},
// 获取状态类型
getStatusType(status) {
switch (status) {
case 'planned':
case '0':
return 'primary';
case 'completed':
case '1':
return 'success';
case 'cancelled':
case '2':
return 'error';
default:
return 'default';
}
},
// 查看详情
handleView(item) {
uni.navigateTo({
url: `/pages/visit/detail?id=${item.visitId}`,
});
},
// 新增走访
handleAdd() {
uni.navigateTo({
url: '/pages/visit/add',
});
},
// 编辑走访
handleEdit(item) {
uni.navigateTo({
url: `/pages/visit/edit?id=${item.visitId}`,
});
},
// 删除走访
handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除${item.studentName}的走访记录吗?`,
success: async (res) => {
if (res.confirm) {
await this.deleteVisit(item.visitId);
}
},
});
},
// 执行删除
async deleteVisit(id) {
try {
uni.showLoading({ title: '删除中...' });
const response = await delVisit(id);
if (response.code === 200) {
uni.showToast({ title: '删除成功', icon: 'success' });
this.refreshList();
} else {
uni.showToast({ title: response.msg || '删除失败', icon: 'none' });
}
} catch (error) {
console.error('删除走访记录失败:', error);
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
} finally {
uni.hideLoading();
}
},
},
};
</script>
<style lang="scss" scoped>
.visit-container {
min-height: 100vh;
background-color: #f8f8f8;
}
.search-bar {
padding: 20rpx;
background: #ffffff;
}
.filter-bar {
background: #ffffff;
padding: 0 20rpx 20rpx;
border-bottom: 1rpx solid #ebeef5;
.filter-item {
margin-bottom: 20rpx;
}
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 10rpx;
}
}
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background: #ffffff;
border-bottom: 1rpx solid #ebeef5;
}
.content-wrapper {
padding: 20rpx;
}
.empty-container {
padding: 80rpx 20rpx;
text-align: center;
}
.list-content {
.visit-card {
background: #ffffff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:active {
transform: scale(0.99);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
.student-info {
flex: 1;
.student-name {
font-size: 32rpx;
font-weight: 600;
color: #303133;
margin-right: 16rpx;
}
.student-no {
font-size: 26rpx;
color: #909399;
}
}
.status-badge {
flex-shrink: 0;
}
}
.card-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.info-text {
font-size: 28rpx;
color: #606266;
margin-left: 8rpx;
}
&:last-of-type {
margin-bottom: 16rpx;
}
}
.card-actions {
display: flex;
justify-content: flex-end;
gap: 10rpx;
padding-top: 16rpx;
border-top: 1rpx solid #ebeef5;
}
}
}
.load-more {
padding: 20rpx 0;
}
</style>