wuzhen update
This commit is contained in:
1 parent
03f400f9c9
commit
1d5e62b7f6
5 files changed
+2675
No files matched your search
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 班主任走访管理API接口
|
||||
* 提供走访记录增删改查、学生查询等功能
|
||||
*/
|
||||
|
||||
import { request } from '@/common/request.js';
|
||||
|
||||
/**
|
||||
* 查询走访记录列表
|
||||
* @param {Object} query 查询参数
|
||||
* @returns Promise
|
||||
*/
|
||||
export const listVisit = (query) => {
|
||||
return request({
|
||||
url: '/zsjy/internshipVisit/list',
|
||||
method: 'GET',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取走访记录详情
|
||||
* @param {string|number} id 走访记录ID
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getVisit = (id) => {
|
||||
return request({
|
||||
url: `/zsjy/internshipVisit/${id}`,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增走访记录
|
||||
* @param {Object} data 走访记录数据
|
||||
* @returns Promise
|
||||
*/
|
||||
export const addVisit = (data) => {
|
||||
return request({
|
||||
url: '/zsjy/internshipVisit',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改走访记录
|
||||
* @param {Object} data 走访记录数据
|
||||
* @returns Promise
|
||||
*/
|
||||
export const updateVisit = (data) => {
|
||||
return request({
|
||||
url: '/zsjy/internshipVisit',
|
||||
method: 'PUT',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除走访记录
|
||||
* @param {string|number|Array} ids 走访记录ID(支持批量删除)
|
||||
* @returns Promise
|
||||
*/
|
||||
export const delVisit = (ids) => {
|
||||
return request({
|
||||
url: `/zsjy/internshipVisit/${ids}`,
|
||||
method: 'DELETE'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出走访记录
|
||||
* @param {Object} query 查询参数
|
||||
* @returns Promise
|
||||
*/
|
||||
export const exportVisit = (query) => {
|
||||
return request({
|
||||
url: '/zsjy/internshipVisit/export',
|
||||
method: 'POST',
|
||||
data: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取班主任管理的学生实习信息列表(用于学生选择)
|
||||
* @param {Object} params 查询参数,包含classTeacherId
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getStudentInternshipList = (params) => {
|
||||
return request({
|
||||
url: '/zsjy/internship/list',
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据关键字搜索学生
|
||||
* @param {string} keyword 搜索关键字(学生姓名或学号)
|
||||
* @param {string|number} classTeacherId 班主任ID
|
||||
* @returns Promise
|
||||
*/
|
||||
export const searchStudents = (keyword, classTeacherId) => {
|
||||
const params = {
|
||||
classTeacherId,
|
||||
pageNum: 1,
|
||||
pageSize: 50 // 搜索结果限制50条
|
||||
};
|
||||
|
||||
// 判断关键词类型,优化搜索参数
|
||||
if (keyword && keyword.trim()) {
|
||||
// 如果是纯数字,优先按学号搜索
|
||||
if (/^\d+$/.test(keyword.trim())) {
|
||||
params.studentNo = keyword.trim();
|
||||
} else {
|
||||
// 否则按姓名搜索
|
||||
params.studentName = keyword.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return request({
|
||||
url: '/zsjy/internship/list',
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取走访状态字典数据
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getVisitStatusDict = () => {
|
||||
return request({
|
||||
url: '/system/dict/data/type/zsjy_visit_status',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取走访方式字典数据
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getVisitTypeDict = () => {
|
||||
return request({
|
||||
url: '/system/dict/data/type/zsjy_visit_type',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,891 @@
|
||||
<template>
|
||||
<view class="visit-add-container">
|
||||
<!-- 表单内容 -->
|
||||
<u-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="180rpx"
|
||||
label-position="left"
|
||||
>
|
||||
<!-- 学生选择 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">学生信息</view>
|
||||
|
||||
<u-form-item label="选择学生" prop="studentId" required>
|
||||
<view @click="handleStudentSelect" style="width: 100%">
|
||||
<u-input
|
||||
v-model="studentDisplayText"
|
||||
placeholder="请选择学生"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 学生基础信息展示 -->
|
||||
<view class="student-info-section" v-if="selectedStudent">
|
||||
<view class="info-row">
|
||||
<text class="info-label">学号:</text>
|
||||
<text class="info-value">{{ selectedStudent.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">班级:</text>
|
||||
<text class="info-value">{{ selectedStudent.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">专业:</text>
|
||||
<text class="info-value">{{ selectedStudent.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">实习单位:</text>
|
||||
<text class="info-value">{{ selectedStudent.companyName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 走访基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">走访信息</view>
|
||||
|
||||
<u-form-item label="走访日期" prop="visitDate" required>
|
||||
<view @click="openVisitDatePicker" style="width: 100%">
|
||||
<u-input
|
||||
v-model="form.visitDate"
|
||||
placeholder="请选择走访日期"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="calendar"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访人员" prop="visitorName" required>
|
||||
<u-input
|
||||
v-model="form.visitorName"
|
||||
placeholder="请输入走访人员姓名"
|
||||
border="none"
|
||||
clearable
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访人职务" prop="visitorPosition">
|
||||
<u-input
|
||||
v-model="form.visitorPosition"
|
||||
placeholder="请输入走访人员职务"
|
||||
border="none"
|
||||
clearable
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访目的" prop="visitPurpose" required>
|
||||
<u-textarea
|
||||
v-model="form.visitPurpose"
|
||||
placeholder="请输入走访目的"
|
||||
:maxlength="500"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
|
||||
<!-- 走访内容 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">走访内容</view>
|
||||
|
||||
<u-form-item label="企业情况" prop="companySituation">
|
||||
<u-textarea
|
||||
v-model="form.companySituation"
|
||||
placeholder="请描述企业基本情况"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="学生表现" prop="studentPerformance">
|
||||
<u-textarea
|
||||
v-model="form.studentPerformance"
|
||||
placeholder="请描述学生实习表现"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="发现问题" prop="problemsFound">
|
||||
<u-textarea
|
||||
v-model="form.problemsFound"
|
||||
placeholder="请描述发现的问题"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="改进建议" prop="suggestions">
|
||||
<u-textarea
|
||||
v-model="form.suggestions"
|
||||
placeholder="请提出改进建议"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访总结" prop="visitSummary">
|
||||
<u-textarea
|
||||
v-model="form.visitSummary"
|
||||
placeholder="请输入走访总结"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
|
||||
<!-- 现场照片 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">现场照片</view>
|
||||
|
||||
<view class="upload-section">
|
||||
<view class="image-list">
|
||||
<view
|
||||
class="image-item"
|
||||
v-for="(image, index) in imageList"
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
:src="image.url"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(index)"
|
||||
/>
|
||||
<view class="delete-btn" @click="removeImage(index)">
|
||||
<u-icon name="close" size="12" color="#ffffff" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="upload-btn"
|
||||
@click="chooseImage"
|
||||
v-if="imageList.length < maxImageCount"
|
||||
>
|
||||
<u-icon name="camera" size="24" color="#c8c9cc" />
|
||||
<text class="upload-text">添加照片</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="upload-tip">最多上传{{ maxImageCount }}张照片</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">备注信息</view>
|
||||
|
||||
<u-form-item label="备注" prop="remark">
|
||||
<u-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注信息(可选)"
|
||||
:maxlength="500"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
</u-form>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
:loading="submitLoading"
|
||||
style="width: 100%;"
|
||||
>
|
||||
提交走访记录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 学生选择弹窗 -->
|
||||
<u-popup
|
||||
:show="showStudentSelector"
|
||||
mode="bottom"
|
||||
height="80%"
|
||||
:round="10"
|
||||
:closeable="false"
|
||||
@close="showStudentSelector = false"
|
||||
>
|
||||
<view class="student-selector">
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择学生</text>
|
||||
<u-icon
|
||||
name="close"
|
||||
size="20"
|
||||
@click="showStudentSelector = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="search-section">
|
||||
<u-search
|
||||
v-model="studentSearchKeyword"
|
||||
placeholder="搜索学生姓名或学号"
|
||||
@search="searchStudents"
|
||||
@custom="searchStudents"
|
||||
height="72"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="student-list">
|
||||
<view
|
||||
class="student-item"
|
||||
v-for="student in studentList"
|
||||
:key="student.studentId"
|
||||
@click="selectStudent(student)"
|
||||
>
|
||||
<view class="student-main">
|
||||
<text class="student-name">{{ student.studentName }}</text>
|
||||
<text class="student-no">{{ student.studentNo }}</text>
|
||||
</view>
|
||||
<view class="student-detail">
|
||||
<text class="detail-text">{{ student.className || '-' }}</text>
|
||||
<text class="detail-text">{{ student.companyName || '暂无实习信息' }}</text>
|
||||
</view>
|
||||
<u-icon
|
||||
name="checkmark"
|
||||
size="16"
|
||||
color="#007AFF"
|
||||
v-if="form.studentId === student.studentId"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="student-empty" v-if="studentList.length === 0 && !studentLoading">
|
||||
<u-empty
|
||||
mode="search"
|
||||
text="暂无学生数据"
|
||||
textColor="#c8c9cc"
|
||||
iconColor="#c8c9cc"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="student-loading" v-if="studentLoading">
|
||||
<u-loading-icon mode="circle" />
|
||||
<text style="margin-left: 10rpx; color: #909399;">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<u-datetime-picker
|
||||
:show="showVisitDatePicker"
|
||||
v-model="visitDateValue"
|
||||
mode="date"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@confirm="handleVisitDateConfirm"
|
||||
@cancel="showVisitDatePicker = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addVisit, getStudentInternshipList, searchStudents } from '@/api/visit.js';
|
||||
import { userStorage } from '@/common/storage.js';
|
||||
|
||||
export default {
|
||||
name: 'VisitAdd',
|
||||
data() {
|
||||
return {
|
||||
// 表单数据
|
||||
form: {
|
||||
studentId: '',
|
||||
internshipId: '',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
visitDate: '',
|
||||
visitorName: '',
|
||||
visitorPosition: '',
|
||||
visitPurpose: '',
|
||||
companySituation: '',
|
||||
studentPerformance: '',
|
||||
problemsFound: '',
|
||||
suggestions: '',
|
||||
visitSummary: '',
|
||||
visitImages: '',
|
||||
remark: '',
|
||||
status: 'planned' // 默认状态为计划中
|
||||
},
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
studentId: [
|
||||
{ required: true, message: '请选择学生', trigger: ['blur', 'change'] }
|
||||
],
|
||||
visitDate: [
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] }
|
||||
],
|
||||
visitorName: [
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] }
|
||||
],
|
||||
visitPurpose: [
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] }
|
||||
]
|
||||
},
|
||||
|
||||
// 学生选择
|
||||
showStudentSelector: false,
|
||||
studentList: [],
|
||||
selectedStudent: null,
|
||||
studentDisplayText: '',
|
||||
studentSearchKeyword: '',
|
||||
studentLoading: false,
|
||||
|
||||
// 日期选择
|
||||
showVisitDatePicker: false,
|
||||
visitDateValue: Date.now(),
|
||||
minDate: Date.now() - 30 * 24 * 60 * 60 * 1000, // 最小日期为30天前
|
||||
maxDate: Date.now() + 365 * 24 * 60 * 60 * 1000, // 最大日期为一年后
|
||||
|
||||
// 图片上传
|
||||
imageList: [],
|
||||
maxImageCount: 10,
|
||||
|
||||
// 加载状态
|
||||
submitLoading: false
|
||||
};
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.initForm();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化表单
|
||||
async initForm() {
|
||||
// 获取当前登录用户信息,自动填入走访人
|
||||
try {
|
||||
const userInfo = userStorage.getUserInfo();
|
||||
// 修复:用户信息结构为 {user: {userId, nickName, ...}, permissions, roles}
|
||||
const user = userInfo?.user;
|
||||
if (user && user.nickName) {
|
||||
this.form.visitorName = user.nickName;
|
||||
}
|
||||
if (user && user.position) {
|
||||
this.form.visitorPosition = user.position;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取用户信息失败:', error);
|
||||
}
|
||||
|
||||
// 加载学生列表
|
||||
this.loadStudentList();
|
||||
},
|
||||
|
||||
// 加载学生列表
|
||||
async loadStudentList() {
|
||||
if (this.studentLoading) return;
|
||||
|
||||
this.studentLoading = true;
|
||||
try {
|
||||
// 获取当前登录用户信息,获取班主任ID
|
||||
const userInfo = userStorage.getUserInfo();
|
||||
// 修复:从正确的路径获取用户ID
|
||||
const classTeacherId = userInfo?.user?.userId;
|
||||
|
||||
if (!classTeacherId) {
|
||||
uni.showToast({ title: '无法获取班主任信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
classTeacherId,
|
||||
pageNum: 1,
|
||||
pageSize: 100 // 获取较多数据供选择
|
||||
};
|
||||
|
||||
const response = await getStudentInternshipList(params);
|
||||
|
||||
if (response.code === 200) {
|
||||
// 实习信息接口返回TableDataInfo格式
|
||||
this.studentList = response.rows || [];
|
||||
} else {
|
||||
uni.showToast({ title: '获取学生列表失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生列表失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
} finally {
|
||||
this.studentLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索学生
|
||||
async searchStudents() {
|
||||
if (!this.studentSearchKeyword.trim()) {
|
||||
this.loadStudentList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.studentLoading) return;
|
||||
|
||||
this.studentLoading = true;
|
||||
try {
|
||||
const userInfo = userStorage.getUserInfo();
|
||||
// 修复:从正确的路径获取用户ID
|
||||
const classTeacherId = userInfo?.user?.userId;
|
||||
|
||||
const response = await searchStudents(this.studentSearchKeyword, classTeacherId);
|
||||
|
||||
if (response.code === 200) {
|
||||
// 实习信息接口返回TableDataInfo格式
|
||||
this.studentList = response.rows || [];
|
||||
} else {
|
||||
this.studentList = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索学生失败:', error);
|
||||
this.studentList = [];
|
||||
} finally {
|
||||
this.studentLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 显示学生选择器
|
||||
handleStudentSelect() {
|
||||
this.showStudentSelector = true;
|
||||
},
|
||||
|
||||
// 选择学生
|
||||
selectStudent(student) {
|
||||
this.selectedStudent = student;
|
||||
this.form.studentId = student.studentId;
|
||||
this.form.internshipId = student.internshipId;
|
||||
this.form.companyName = student.companyName;
|
||||
this.form.companyAddress = student.companyAddress;
|
||||
this.studentDisplayText = `${student.studentName}(${student.studentNo})`;
|
||||
this.showStudentSelector = false;
|
||||
},
|
||||
|
||||
// 打开走访日期选择器
|
||||
openVisitDatePicker() {
|
||||
console.log('打开日期选择器,当前表单日期:', this.form.visitDate);
|
||||
console.log('当前visitDateValue:', this.visitDateValue);
|
||||
|
||||
// 如果表单中已有日期,使用该日期初始化选择器
|
||||
if (this.form.visitDate) {
|
||||
try {
|
||||
const existingDate = new Date(this.form.visitDate);
|
||||
if (!isNaN(existingDate.getTime())) {
|
||||
this.visitDateValue = existingDate.getTime();
|
||||
console.log('使用现有日期初始化:', this.visitDateValue);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('解析现有日期失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.showVisitDatePicker = true;
|
||||
},
|
||||
|
||||
// 走访日期确认
|
||||
handleVisitDateConfirm(value) {
|
||||
console.log('=== 走访日期确认开始 ===');
|
||||
console.log('接收到的value:', value);
|
||||
console.log('value类型:', typeof value);
|
||||
console.log('value是否为null/undefined:', value == null);
|
||||
|
||||
if (value == null) {
|
||||
console.error('接收到空值');
|
||||
uni.showToast({ title: '请选择有效日期', icon: 'none' });
|
||||
this.showVisitDatePicker = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let timestamp;
|
||||
|
||||
// 处理uview-ui返回的对象格式 {value: timestamp, mode: 'date'}
|
||||
if (typeof value === 'object' && value.value !== undefined) {
|
||||
timestamp = value.value;
|
||||
console.log('从对象中提取timestamp:', timestamp);
|
||||
} else if (typeof value === 'number') {
|
||||
timestamp = value;
|
||||
console.log('直接使用number类型timestamp:', timestamp);
|
||||
} else if (typeof value === 'string') {
|
||||
timestamp = parseInt(value);
|
||||
console.log('从string转换timestamp:', timestamp);
|
||||
} else {
|
||||
throw new Error('无法识别的value格式');
|
||||
}
|
||||
|
||||
// 创建Date对象
|
||||
const date = new Date(timestamp);
|
||||
console.log('创建的Date对象:', date);
|
||||
console.log('Date.getTime():', date.getTime());
|
||||
console.log('是否为有效日期:', !isNaN(date.getTime()));
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error('无效的日期值');
|
||||
}
|
||||
|
||||
const formattedDate = this.formatDate(date);
|
||||
console.log('格式化后的日期:', formattedDate);
|
||||
|
||||
if (!formattedDate) {
|
||||
throw new Error('日期格式化失败');
|
||||
}
|
||||
|
||||
this.form.visitDate = formattedDate;
|
||||
this.visitDateValue = timestamp; // 保存时间戳而不是原始对象
|
||||
|
||||
console.log('最终form.visitDate:', this.form.visitDate);
|
||||
console.log('最终visitDateValue:', this.visitDateValue);
|
||||
console.log('日期设置成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('日期处理异常:', error.message);
|
||||
console.error('异常详情 - value:', value, 'type:', typeof value);
|
||||
uni.showToast({ title: '日期选择失败,请重试', icon: 'none' });
|
||||
}
|
||||
|
||||
this.showVisitDatePicker = false;
|
||||
console.log('=== 走访日期确认结束 ===');
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(date) {
|
||||
console.log('formatDate - 输入的date:', date);
|
||||
console.log('formatDate - date类型:', typeof date);
|
||||
|
||||
if (!date) {
|
||||
console.log('formatDate - date为空');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
console.log('formatDate - date.getTime()为NaN');
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const result = `${year}-${month}-${day}`;
|
||||
|
||||
console.log('formatDate - 格式化结果:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('formatDate - 格式化异常:', error);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
const remainCount = this.maxImageCount - this.imageList.length;
|
||||
uni.chooseImage({
|
||||
count: remainCount,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.handleImageUpload(res.tempFilePaths);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 处理图片上传
|
||||
async handleImageUpload(filePaths) {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
for (const filePath of filePaths) {
|
||||
// 这里应该调用具体的文件上传接口
|
||||
// 暂时使用本地路径作为示例
|
||||
this.imageList.push({
|
||||
url: filePath,
|
||||
path: filePath
|
||||
});
|
||||
}
|
||||
|
||||
// 更新表单中的图片数据
|
||||
this.form.visitImages = JSON.stringify(this.imageList);
|
||||
} catch (error) {
|
||||
console.error('上传图片失败:', error);
|
||||
uni.showToast({ title: '上传图片失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
});
|
||||
},
|
||||
|
||||
// 删除图片
|
||||
removeImage(index) {
|
||||
this.imageList.splice(index, 1);
|
||||
this.form.visitImages = JSON.stringify(this.imageList);
|
||||
},
|
||||
|
||||
|
||||
|
||||
// 提交表单
|
||||
async submitForm() {
|
||||
// 表单验证
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
} catch (error) {
|
||||
console.log('表单验证失败:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitLoading = true;
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: '1' // 已提交状态 (0-草稿 1-已提交 2-已审核)
|
||||
};
|
||||
|
||||
const response = await addVisit(formData);
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '提交失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交走访记录失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
} finally {
|
||||
this.submitLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visit-add-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
padding-bottom: 120rpx; // 为底部按钮留出空间
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 0 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.student-info-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
width: 120rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border: 2rpx dashed #c8c9cc;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.upload-text {
|
||||
font-size: 24rpx;
|
||||
color: #c8c9cc;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #ebeef5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.student-selector {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 20rpx;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
|
||||
.selector-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
}
|
||||
|
||||
.student-list {
|
||||
height: 600rpx; /* 固定高度,确保可以滚动 */
|
||||
padding: 0 20rpx;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
position: relative;
|
||||
border: 1px solid #eee; /* 添加边框便于调试 */
|
||||
box-sizing: border-box;
|
||||
|
||||
.student-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
|
||||
.student-main {
|
||||
flex: 1;
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.student-detail {
|
||||
flex: 2;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.detail-text {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.student-empty,
|
||||
.student-loading {
|
||||
padding: 80rpx 20rpx;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,424 @@
|
||||
<template>
|
||||
<view class="visit-detail-container">
|
||||
<!-- 学生信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">学生信息</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item">
|
||||
<text class="info-label">学生姓名:</text>
|
||||
<text class="info-value">{{ visitInfo.studentName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">学号:</text>
|
||||
<text class="info-value">{{ visitInfo.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">班级:</text>
|
||||
<text class="info-value">{{ visitInfo.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">专业:</text>
|
||||
<text class="info-value">{{ visitInfo.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">学院:</text>
|
||||
<text class="info-value">{{ visitInfo.collegeName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">企业信息</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item">
|
||||
<text class="info-label">企业名称:</text>
|
||||
<text class="info-value">{{ visitInfo.companyName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">企业地址:</text>
|
||||
<text class="info-value">{{ visitInfo.companyAddress || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 走访基本信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">走访基本信息</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item">
|
||||
<text class="info-label">走访日期:</text>
|
||||
<text class="info-value">{{ visitInfo.visitDate || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">走访人员:</text>
|
||||
<text class="info-value">{{ visitInfo.visitorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item" v-if="visitInfo.visitorPosition">
|
||||
<text class="info-label">走访人职务:</text>
|
||||
<text class="info-value">{{ visitInfo.visitorPosition }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">走访状态:</text>
|
||||
<u-tag
|
||||
:text="getStatusText(visitInfo.status)"
|
||||
:type="getStatusType(visitInfo.status)"
|
||||
size="mini"
|
||||
/>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.visitPurpose">
|
||||
<text class="info-label">走访目的:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.visitPurpose }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 走访内容 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">走访内容</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item info-item-full" v-if="visitInfo.companySituation">
|
||||
<text class="info-label">企业情况:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.companySituation }}</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.studentPerformance">
|
||||
<text class="info-label">学生表现:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.studentPerformance }}</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.problemsFound">
|
||||
<text class="info-label">发现问题:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.problemsFound }}</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.suggestions">
|
||||
<text class="info-label">改进建议:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.suggestions }}</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.visitSummary">
|
||||
<text class="info-label">走访总结:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.visitSummary }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 现场照片 -->
|
||||
<view class="info-section" v-if="imageList.length > 0">
|
||||
<view class="section-title">现场照片</view>
|
||||
<view class="section-content">
|
||||
<view class="image-grid">
|
||||
<view
|
||||
class="image-item"
|
||||
v-for="(image, index) in imageList"
|
||||
:key="index"
|
||||
@click="previewImage(index)"
|
||||
>
|
||||
<image :src="image.url" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<view class="info-section" v-if="visitInfo.remark">
|
||||
<view class="section-title">备注信息</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item info-item-full">
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 系统信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">系统信息</view>
|
||||
<view class="section-content">
|
||||
<view class="info-item">
|
||||
<text class="info-label">创建时间:</text>
|
||||
<text class="info-value">{{ visitInfo.createTime || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item" v-if="visitInfo.updateTime">
|
||||
<text class="info-label">更新时间:</text>
|
||||
<text class="info-value">{{ visitInfo.updateTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-section" v-if="canEdit">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="handleEdit"
|
||||
:loading="loading"
|
||||
style="margin-right: 20rpx;"
|
||||
>
|
||||
编辑走访记录
|
||||
</u-button>
|
||||
<u-button
|
||||
type="error"
|
||||
@click="handleDelete"
|
||||
:loading="loading"
|
||||
plain
|
||||
>
|
||||
删除记录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 加载遮罩 -->
|
||||
<u-loading-page
|
||||
:loading="pageLoading"
|
||||
loading-text="加载中..."
|
||||
loading-mode="circle"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getVisit, delVisit } from '@/api/visit.js';
|
||||
|
||||
export default {
|
||||
name: 'VisitDetail',
|
||||
data() {
|
||||
return {
|
||||
// 页面参数
|
||||
visitId: '',
|
||||
|
||||
// 页面状态
|
||||
pageLoading: true,
|
||||
loading: false,
|
||||
|
||||
// 走访信息
|
||||
visitInfo: {},
|
||||
|
||||
// 图片数据
|
||||
imageList: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 是否可以编辑
|
||||
canEdit() {
|
||||
// 只有计划中或草稿状态的记录可以编辑
|
||||
return this.visitInfo.status === 'planned' ||
|
||||
this.visitInfo.status === '0' ||
|
||||
this.visitInfo.status === 'draft';
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.visitId = options.id;
|
||||
this.loadVisitDetail();
|
||||
} else {
|
||||
uni.showToast({ title: '参数错误', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载走访记录详情
|
||||
async loadVisitDetail() {
|
||||
this.pageLoading = true;
|
||||
try {
|
||||
const response = await getVisit(this.visitId);
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
this.visitInfo = response.data;
|
||||
|
||||
// 处理图片数据
|
||||
if (response.data.visitImages) {
|
||||
try {
|
||||
this.imageList = JSON.parse(response.data.visitImages) || [];
|
||||
} catch (e) {
|
||||
console.warn('解析图片数据失败:', e);
|
||||
this.imageList = [];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取数据失败', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载走访记录详情失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
} finally {
|
||||
this.pageLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
switch (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';
|
||||
}
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑走访记录
|
||||
handleEdit() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/visit/edit?id=${this.visitId}`
|
||||
});
|
||||
},
|
||||
|
||||
// 删除走访记录
|
||||
handleDelete() {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条走访记录吗?删除后无法恢复。',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await this.deleteVisit();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 执行删除
|
||||
async deleteVisit() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await delVisit(this.visitId);
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({ title: '删除成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '删除失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除走访记录失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visit-detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 20rpx 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.info-item-full {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
|
||||
&.info-value-multiline {
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
|
||||
.image-item {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,622 @@
|
||||
<template>
|
||||
<view class="visit-edit-container">
|
||||
<!-- 表单内容 -->
|
||||
<u-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="180rpx"
|
||||
label-position="left"
|
||||
>
|
||||
<!-- 学生信息展示 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">学生信息</view>
|
||||
|
||||
<view class="student-info-section">
|
||||
<view class="info-row">
|
||||
<text class="info-label">学生姓名:</text>
|
||||
<text class="info-value">{{ visitInfo.studentName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">学号:</text>
|
||||
<text class="info-value">{{ visitInfo.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">班级:</text>
|
||||
<text class="info-value">{{ visitInfo.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">专业:</text>
|
||||
<text class="info-value">{{ visitInfo.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">实习单位:</text>
|
||||
<text class="info-value">{{ visitInfo.companyName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 走访基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">走访信息</view>
|
||||
|
||||
<u-form-item label="走访日期" prop="visitDate" required>
|
||||
<view @click="showVisitDatePicker = true" style="width: 100%">
|
||||
<u-input
|
||||
v-model="form.visitDate"
|
||||
placeholder="请选择走访日期"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="calendar"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访人员" prop="visitorName" required>
|
||||
<u-input
|
||||
v-model="form.visitorName"
|
||||
placeholder="请输入走访人员姓名"
|
||||
border="none"
|
||||
clearable
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访人职务" prop="visitorPosition">
|
||||
<u-input
|
||||
v-model="form.visitorPosition"
|
||||
placeholder="请输入走访人员职务"
|
||||
border="none"
|
||||
clearable
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访目的" prop="visitPurpose" required>
|
||||
<u-textarea
|
||||
v-model="form.visitPurpose"
|
||||
placeholder="请输入走访目的"
|
||||
:maxlength="500"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
|
||||
<!-- 走访内容 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">走访内容</view>
|
||||
|
||||
<u-form-item label="企业情况" prop="companySituation">
|
||||
<u-textarea
|
||||
v-model="form.companySituation"
|
||||
placeholder="请描述企业基本情况"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="学生表现" prop="studentPerformance">
|
||||
<u-textarea
|
||||
v-model="form.studentPerformance"
|
||||
placeholder="请描述学生实习表现"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="发现问题" prop="problemsFound">
|
||||
<u-textarea
|
||||
v-model="form.problemsFound"
|
||||
placeholder="请描述发现的问题"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="改进建议" prop="suggestions">
|
||||
<u-textarea
|
||||
v-model="form.suggestions"
|
||||
placeholder="请提出改进建议"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="走访总结" prop="visitSummary">
|
||||
<u-textarea
|
||||
v-model="form.visitSummary"
|
||||
placeholder="请输入走访总结"
|
||||
:maxlength="1000"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
|
||||
<!-- 现场照片 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">现场照片</view>
|
||||
|
||||
<view class="upload-section">
|
||||
<view class="image-list">
|
||||
<view
|
||||
class="image-item"
|
||||
v-for="(image, index) in imageList"
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
:src="image.url"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(index)"
|
||||
/>
|
||||
<view class="delete-btn" @click="removeImage(index)">
|
||||
<u-icon name="close" size="12" color="#ffffff" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="upload-btn"
|
||||
@click="chooseImage"
|
||||
v-if="imageList.length < maxImageCount"
|
||||
>
|
||||
<u-icon name="camera" size="24" color="#c8c9cc" />
|
||||
<text class="upload-text">添加照片</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="upload-tip">最多上传{{ maxImageCount }}张照片</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">备注信息</view>
|
||||
|
||||
<u-form-item label="备注" prop="remark">
|
||||
<u-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注信息(可选)"
|
||||
:maxlength="500"
|
||||
show-confirm-bar
|
||||
height="120"
|
||||
auto-height
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
</u-form>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<u-button
|
||||
type="default"
|
||||
@click="saveDraft"
|
||||
:loading="draftLoading"
|
||||
style="margin-right: 20rpx;"
|
||||
>
|
||||
保存草稿
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
:loading="submitLoading"
|
||||
style="flex: 1;"
|
||||
>
|
||||
更新走访记录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<u-datetime-picker
|
||||
:show="showVisitDatePicker"
|
||||
v-model="visitDateValue"
|
||||
mode="date"
|
||||
:maxDate="maxDate"
|
||||
@confirm="handleVisitDateConfirm"
|
||||
@cancel="showVisitDatePicker = false"
|
||||
/>
|
||||
|
||||
<!-- 加载遮罩 -->
|
||||
<u-loading-page
|
||||
:loading="pageLoading"
|
||||
loading-text="加载中..."
|
||||
loading-mode="circle"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getVisit, updateVisit } from '@/api/visit.js';
|
||||
|
||||
export default {
|
||||
name: 'VisitEdit',
|
||||
data() {
|
||||
return {
|
||||
// 页面参数
|
||||
visitId: '',
|
||||
|
||||
// 页面状态
|
||||
pageLoading: true,
|
||||
|
||||
// 走访信息
|
||||
visitInfo: {},
|
||||
|
||||
// 表单数据
|
||||
form: {
|
||||
visitId: '',
|
||||
studentId: '',
|
||||
internshipId: '',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
visitDate: '',
|
||||
visitorName: '',
|
||||
visitorPosition: '',
|
||||
visitPurpose: '',
|
||||
companySituation: '',
|
||||
studentPerformance: '',
|
||||
problemsFound: '',
|
||||
suggestions: '',
|
||||
visitSummary: '',
|
||||
visitImages: '',
|
||||
remark: '',
|
||||
status: ''
|
||||
},
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
visitDate: [
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] }
|
||||
],
|
||||
visitorName: [
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] }
|
||||
],
|
||||
visitPurpose: [
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] }
|
||||
]
|
||||
},
|
||||
|
||||
// 日期选择
|
||||
showVisitDatePicker: false,
|
||||
visitDateValue: Date.now(),
|
||||
maxDate: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30天后
|
||||
|
||||
// 图片上传
|
||||
imageList: [],
|
||||
maxImageCount: 10,
|
||||
|
||||
// 加载状态
|
||||
submitLoading: false,
|
||||
draftLoading: false
|
||||
};
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.visitId = options.id;
|
||||
this.loadVisitDetail();
|
||||
} else {
|
||||
uni.showToast({ title: '参数错误', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载走访记录详情
|
||||
async loadVisitDetail() {
|
||||
this.pageLoading = true;
|
||||
try {
|
||||
const response = await getVisit(this.visitId);
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
this.visitInfo = response.data;
|
||||
|
||||
// 填充表单数据
|
||||
this.form = {
|
||||
visitId: response.data.visitId,
|
||||
studentId: response.data.studentId,
|
||||
internshipId: response.data.internshipId,
|
||||
companyName: response.data.companyName,
|
||||
companyAddress: response.data.companyAddress,
|
||||
visitDate: response.data.visitDate,
|
||||
visitorName: response.data.visitorName,
|
||||
visitorPosition: response.data.visitorPosition,
|
||||
visitPurpose: response.data.visitPurpose,
|
||||
companySituation: response.data.companySituation,
|
||||
studentPerformance: response.data.studentPerformance,
|
||||
problemsFound: response.data.problemsFound,
|
||||
suggestions: response.data.suggestions,
|
||||
visitSummary: response.data.visitSummary,
|
||||
visitImages: response.data.visitImages,
|
||||
remark: response.data.remark,
|
||||
status: response.data.status
|
||||
};
|
||||
|
||||
// 处理图片数据
|
||||
if (response.data.visitImages) {
|
||||
try {
|
||||
this.imageList = JSON.parse(response.data.visitImages) || [];
|
||||
} catch (e) {
|
||||
console.warn('解析图片数据失败:', e);
|
||||
this.imageList = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置日期选择器的值
|
||||
if (response.data.visitDate) {
|
||||
this.visitDateValue = new Date(response.data.visitDate).getTime();
|
||||
}
|
||||
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取数据失败', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载走访记录详情失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
} finally {
|
||||
this.pageLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 走访日期确认
|
||||
handleVisitDateConfirm(value) {
|
||||
const date = new Date(value);
|
||||
this.form.visitDate = this.formatDate(date);
|
||||
this.showVisitDatePicker = false;
|
||||
},
|
||||
|
||||
// 格式化日期
|
||||
formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
const remainCount = this.maxImageCount - this.imageList.length;
|
||||
uni.chooseImage({
|
||||
count: remainCount,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.handleImageUpload(res.tempFilePaths);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 处理图片上传
|
||||
async handleImageUpload(filePaths) {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
for (const filePath of filePaths) {
|
||||
// 这里应该调用具体的文件上传接口
|
||||
// 暂时使用本地路径作为示例
|
||||
this.imageList.push({
|
||||
url: filePath,
|
||||
path: filePath
|
||||
});
|
||||
}
|
||||
|
||||
// 更新表单中的图片数据
|
||||
this.form.visitImages = JSON.stringify(this.imageList);
|
||||
} catch (error) {
|
||||
console.error('上传图片失败:', error);
|
||||
uni.showToast({ title: '上传图片失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
});
|
||||
},
|
||||
|
||||
// 删除图片
|
||||
removeImage(index) {
|
||||
this.imageList.splice(index, 1);
|
||||
this.form.visitImages = JSON.stringify(this.imageList);
|
||||
},
|
||||
|
||||
// 保存草稿
|
||||
async saveDraft() {
|
||||
this.draftLoading = true;
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: 'planned' // 草稿状态
|
||||
};
|
||||
|
||||
const response = await updateVisit(formData);
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({ title: '草稿保存成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '保存失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存草稿失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
} finally {
|
||||
this.draftLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
async submitForm() {
|
||||
// 表单验证
|
||||
try {
|
||||
await this.$refs.formRef.validate();
|
||||
} catch (error) {
|
||||
console.log('表单验证失败:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitLoading = true;
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: 'completed' // 已完成状态
|
||||
};
|
||||
|
||||
const response = await updateVisit(formData);
|
||||
|
||||
if (response.code === 200) {
|
||||
uni.showToast({ title: '更新成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '更新失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新走访记录失败:', error);
|
||||
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||||
} finally {
|
||||
this.submitLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visit-edit-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
padding-bottom: 120rpx; // 为底部按钮留出空间
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 0 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.student-info-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-top: 16rpx;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border: 2rpx dashed #c8c9cc;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.upload-text {
|
||||
font-size: 24rpx;
|
||||
color: #c8c9cc;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #ebeef5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,590 @@
|
||||
<template>
|
||||
<view class="visit-container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学生姓名、学号或企业"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
height="72"
|
||||
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"
|
||||
size="small"
|
||||
@click="handleFilter"
|
||||
:loading="loading"
|
||||
>
|
||||
筛选
|
||||
</u-button>
|
||||
<u-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="resetFilter"
|
||||
style="margin-left: 10rpx;"
|
||||
>
|
||||
重置
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选按钮 -->
|
||||
<view class="action-bar">
|
||||
<u-button
|
||||
type="default"
|
||||
size="small"
|
||||
:icon="showFilter ? 'arrow-up' : 'arrow-down'"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
{{ showFilter ? '收起筛选' : '展开筛选' }}
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="plus"
|
||||
@click="handleAdd"
|
||||
>
|
||||
新增走访
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 走访记录列表 -->
|
||||
<view class="list-container">
|
||||
<view class="list-content" v-if="visitList.length > 0">
|
||||
<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="item.statusName || 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="default"
|
||||
size="mini"
|
||||
@click.stop="handleEdit(item)"
|
||||
v-if="item.status === 'planned' || item.status === '0'"
|
||||
>
|
||||
编辑
|
||||
</u-button>
|
||||
<u-button
|
||||
type="error"
|
||||
size="mini"
|
||||
@click.stop="handleDelete(item)"
|
||||
style="margin-left: 10rpx;"
|
||||
v-if="item.status === 'planned' || item.status === '0'"
|
||||
>
|
||||
删除
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-else-if="!loading">
|
||||
<u-empty
|
||||
mode="data"
|
||||
text="暂无走访记录"
|
||||
textColor="#c8c9cc"
|
||||
iconColor="#c8c9cc"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<u-loading-icon mode="circle" />
|
||||
<text style="margin-left: 10rpx; color: #909399;">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上拉加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && visitList.length > 0">
|
||||
<u-loadmore
|
||||
:status="loadMoreStatus"
|
||||
@loadmore="loadMore"
|
||||
load-text="加载更多"
|
||||
loading-text="正在加载..."
|
||||
no-more-text="没有更多了"
|
||||
/>
|
||||
</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: {
|
||||
// 初始化页面
|
||||
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;
|
||||
},
|
||||
|
||||
// 获取状态类型
|
||||
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;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.visit-card {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.student-info {
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 80rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 20rpx;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user