661 lines
20 KiB
Vue
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>
|