You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1981 lines
47 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="page">
<!-- 门岗访客管理页面 -->
<!-- 头部导航 -->
<view class="gate-header">
<view class="gate-left">
<view class="today-visitors" @click="openList">访</view>
</view>
</view>
<!-- -->
<view class="gate-content">
<view class="form-container">
<view class="form-title">填写信息</view>
<!-- 拜访日期选择 - 时间段选择 -->
<view class="form-item">
<text class="form-label"> 拜访日期</text>
<view class="date-range-container">
<picker
mode="date"
:value="select.start_date"
@change="onStartDateChange"
class="date-picker"
>
<view class="form-input date-input">
<text>{{ select.start_date || "开始日期" }}</text>
</view>
</picker>
<view class="date-separator"></view>
<picker
mode="date"
:value="select.end_date"
@change="onEndDateChange"
class="date-picker"
>
<view class="form-input date-input">
<text>{{ select.end_date || "结束日期" }}</text>
</view>
</picker>
</view>
</view>
<!-- 核验销码输入 -->
<view class="form-item">
<text class="form-label"> 核验销码</text>
<input
v-model="select.code"
placeholder="请扫描访客二维码"
@input="onCodeInput"
class="form-input input-field"
/>
</view>
<!-- 身份证输入 -->
<view class="form-item">
<text class="form-label">※ 身份证件:</text>
<view class="id-input-group idcard-group">
<input
v-model="select.idcard"
placeholder="请输入身份证"
class="form-input input-field"
/>
<!-- #ifdef H5 -->
<view class="id-action" @click="getIdcard">查询身份证</view>
<!-- #endif -->
</view>
</view>
<!-- ID卡输入 -->
<view class="form-item">
<text class="form-label">※ ID卡</text>
<view class="id-input-group">
<input
v-model="select.person_no"
placeholder="请输入ID卡"
class="form-input input-field"
/>
<!-- <button @click="getIdcard" class="btn btn-primary id-btn">查询身份证</button> -->
</view>
</view>
<!-- 查询按钮 -->
<view class="form-item query-btn-container">
<button
@click="getList"
class="btn btn-primary query-btn"
:disabled="loading"
>
{{ loading ? "查询中..." : "查询" }}
</button>
</view>
</view>
</view>
<!-- 访客信息弹窗 -->
<view v-if="visitShow" class="modal-overlay" @click="closeVisitModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">访客信息</text>
</view>
<view class="modal-body">
<view class="visit-info" v-if="visitData">
<!-- 拜访信息 -->
<view class="info-section">
<text class="section-title">拜访信息</text>
<view class="info-item">
<text class="info-label">拜访类型:</text>
<text class="info-value">{{
getVisitTypeText(visitData.type)
}}</text>
</view>
<view class="info-item">
<text class="info-label">拜访日期:</text>
<text class="info-value">{{
getVisitDateRange(visitData)
}}</text>
</view>
<view class="info-item">
<text class="info-label">到访时间:</text>
<text class="info-value">{{
visitData.time ? visitData.time : ""
}}</text>
</view>
<view class="info-item" v-if="visitData.visit_time_detail">
<text class="info-label">到访时段:</text>
<text class="info-value"
>{{ visitData.visit_time_detail.start_time }} -
{{ visitData.visit_time_detail.end_time }}</text
>
</view>
<view class="info-item" v-if="visitData.visit_area_detail">
<text class="info-label">前往区域:</text>
<text class="info-value">{{
visitData.visit_area_detail.name
}}</text>
</view>
<view class="info-item" v-if="visitData.reason">
<text class="info-label">到访事由:</text>
<text class="info-value">{{ visitData.reason }}</text>
</view>
<view
class="info-item"
v-if="visitData.cars && visitData.cars.length > 0"
>
<text class="info-label">到访车辆:</text>
<text class="info-value">{{ visitData.cars.join(", ") }}</text>
</view>
<view class="info-item" v-if="visitData.plate">
<text class="info-label">车辆类型:</text>
<text class="info-value">{{ visitData.plate }}</text>
</view>
</view>
<!-- 被访人信息 -->
<view class="info-section">
<text class="section-title">被访人信息</text>
<view class="info-item">
<text class="info-label">被访人:</text>
<text class="info-value">{{
visitData.accept_admin ? visitData.accept_admin.name : "-"
}}</text>
</view>
<!-- <view class="info-item" v-if="visitData.accompanyName">
<text class="info-label">陪同人:</text>
<text class="info-value">{{ visitData.accompanyName }}</text>
</view>
<view class="info-item" v-if="visitData.goodsName">
<text class="info-label">收货人:</text>
<text class="info-value">{{ visitData.goodsName }}</text>
</view> -->
</view>
<!-- 拜访人信息 -->
<view class="info-section">
<text class="section-title">拜访人信息</text>
<view class="info-item">
<text class="info-label">姓名:</text>
<text class="info-value">{{ visitData.name }}</text>
</view>
<view class="info-item">
<text class="info-label">联系电话:</text>
<text class="info-value">{{ visitData.mobile }}</text>
</view>
<view class="info-item" v-if="visitData.credent">
<text class="info-label">证件类型:</text>
<text class="info-value">{{
visitData.credent == 1 ? "身份证" : "护照"
}}</text>
</view>
<view class="info-item" v-if="visitData.idcard">
<text class="info-label">{{
visitData.credent == 1 ? "身份证号:" : "护照号码:"
}}</text>
<text class="info-value">{{ visitData.idcard }}</text>
</view>
<view class="info-item" v-if="visitData.company_name">
<text class="info-label">单位名称:</text>
<text class="info-value">{{ visitData.company_name }}</text>
</view>
<view class="info-item">
<text class="info-label">状态:</text>
<text class="info-value status-badge"
:class="getStatusInfo(visitData.audit_status).class">
{{ getStatusInfo(visitData.audit_status).text }}</text>
</view>
<!-- 人员编号输入 -->
<view class="info-item">
<text class="info-label">ID卡</text>
<view class="info-input-wrapper">
<input
v-model="personNoValue"
placeholder="请输入ID卡"
class="info-input"
/>
</view>
</view>
</view>
<!-- 随访人员信息 -->
<view
class="info-section"
v-if="visitData.follw_people && visitData.follw_people.length > 0"
>
<text class="section-title">随访人员</text>
<view
v-for="(person, index) in visitData.follw_people"
:key="index"
class="follow-person-item"
>
<view class="info-item">
<text class="info-label">姓名:</text>
<text class="info-value">{{ person.name }}</text>
</view>
<view class="info-item">
<text class="info-label">联系电话:</text>
<text class="info-value">{{ person.mobile }}</text>
</view>
<view class="info-item" v-if="person.credent">
<text class="info-label">证件类型:</text>
<text class="info-value">{{
person.credent == 1 ? "身份证" : "护照"
}}</text>
</view>
<view class="info-item" v-if="person.idcard">
<text class="info-label">{{
person.credent == 1 ? "身份证号:" : "护照号码:"
}}</text>
<text class="info-value">{{ person.idcard }}</text>
</view>
<!-- ID卡输入框 -->
<view class="info-item">
<text class="info-label">ID卡</text>
<view class="info-input-wrapper">
<input
v-model="person.follw_people_person_no"
placeholder="请输入ID卡"
class="info-input"
/>
</view>
</view>
<view
class="separator"
v-if="index < visitData.follw_people.length - 1"
></view>
</view>
</view>
<!-- 备注输入 -->
<view class="info-item">
<text class="info-label">备注:</text>
<view class="info-input-wrapper">
<textarea
rows="3"
v-model="remarkValue"
placeholder="请输入备注信息"
class="info-textarea"
></textarea>
</view>
</view>
<!-- 车辆照片上传 (仅type==3时显示) -->
<view
v-if="visitData.type == 3"
class="info-item photo-upload-section"
>
<text class="info-label">车辆照片:</text>
<view class="photo-upload-container">
<!-- 照片列表 -->
<view class="uploaded-photos">
<!-- 已上传的照片 -->
<view
v-for="(image, index) in vehicleImages"
:key="index"
class="photo-item"
>
<image
:src="image.url"
class="photo-preview"
mode="aspectFill"
@click="previewImage(image.url)"
></image>
<view class="photo-delete" @click="removeImage(index)"
>×</view
>
</view>
<!-- 添加照片按钮最多9张 -->
<view
v-if="vehicleImages.length < 9"
class="photo-item add-photo-btn"
@click="showPhotoOptions"
>
<view class="add-photo-icon">+</view>
<view class="add-photo-text">添加照片</view>
</view>
</view>
<!-- 底部选择弹窗 -->
<view v-if="showPhotoActionSheet" class="photo-action-sheet">
<view
class="action-sheet-overlay"
@click="hidePhotoOptions"
></view>
<view class="action-sheet-content">
<view class="action-sheet-item" @click="chooseImage">
<text class="action-sheet-text">选择照片</text>
</view>
<view class="action-sheet-item" @click="takePhoto">
<text class="action-sheet-text">拍照</text>
</view>
<view class="action-sheet-cancel" @click="hidePhotoOptions">
<text class="action-sheet-cancel-text">取消</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button @click="closeVisitModal" class="btn btn-secondary">
关闭
</button>
<button
@click="updateVisitInfo"
class="btn btn-primary"
:disabled="updating"
>
{{ updating ? "更新并核销中..." : "更新并核销" }}
</button>
</view>
</view>
</view>
<!-- 今日访客列表弹窗 -->
<view v-if="listShow" class="modal-overlay" @click="closeListModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">访客列表</text>
</view>
<view class="modal-body">
<view class="visitor-list">
<view v-if="todayVisitors.length === 0" class="empty-state">
<text>暂无访客记录</text>
</view>
<view v-else>
<view class="visitor-header">
<view class="visitor-col visitor-col-name">姓名</view>
<view class="visitor-col visitor-col-visited">被访人</view>
<view class="visitor-col visitor-col-status">状态</view>
</view>
<view
v-for="visitor in todayVisitors"
:key="visitor.id"
class="visitor-item"
>
<view class="visitor-name">{{ visitor.name }}</view>
<view class="visitor-visited">{{
visitor.accept_admin ? visitor.accept_admin.name : "-"
}}</view>
<view
class="visitor-status status-badge"
:class="getStatusInfo(visitor.audit_status).class"
>
{{ getStatusInfo(visitor.audit_status).text }}
</view>
</view>
</view>
</view>
</view>
<view class="modal-footer">
<button @click="closeListModal" size="mini" class="btn btn-primary">
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import { openDevice, readIdCard, closeDevice } from "@/common/reader.js";
export default {
data() {
return {
fullscreen: false,
loading: false,
dateRangeText: "",
select: {
page: 1,
rows: 999,
keyword: "",
audit_status: "",
start_date: "",
end_date: "",
is_export: 0,
code: "",
idcard: "",
person_no: "", // ID卡
},
visitShow: false,
visitData: null,
vehicleImages: [], // 车辆照片数组
remarkValue: "", // 备注值
personNoValue: "", // 人员编号
uploading: false, // 上传状态
updating: false, // 更新状态
showPhotoActionSheet: false, // 控制照片选择弹窗显示
listShow: false,
todayVisitors: [],
// 提交的核销数据
codeForm: {
code: "",
type: "",
},
statusList: [
{
id: -1,
value: "待学习",
},
{
id: 0,
value: "待审核",
},
{
id: 1,
value: "通过(待进厂)",
},
{
id: 2,
value: "驳回",
},
{
id: 3,
value: "已进厂",
},
{
id: 4,
value: "已离厂",
},
{
id: 5,
value: "已取消",
},
],
};
},
onLoad() {
this.checkAuth();
},
onReachBottom() {
// 上拉加载更多
},
onPullDownRefresh() {
// 下拉刷新
this.getToday();
uni.stopPullDownRefresh();
},
methods: {
// 链接读卡器
connectReader() {
openDevice().then((res) => {
console.log("链接读卡器", res);
if (res.data.resultFlag == 0) {
uni.showToast({
title: "身份证读卡器链接成功",
icon: "none",
});
} else {
uni.showToast({
title: "身份证读卡器链接失败",
icon: "none",
});
}
});
},
// 检查认证状态
async checkAuth() {
try {
// 检查是否有token
const lifeData = uni.getStorageSync("mkwcancel_lifeData");
const token = lifeData?.vuex_token || this.$store.state.vuex_token;
if (!token) {
// 没有token直接跳转到登录页
uni.reLaunch({
url: "/pages/login/index",
});
return;
}
// 链接读卡器
this.connectReader();
// 有token验证token有效性
const userRes = await this.$u.api.user();
console.log("用户信息验证:", userRes);
if (userRes) {
// token有效存储用户信息
this.$u.vuex("vuex_user", userRes);
this.$u.vuex("vuex_token", token); // 确保在vuex中
// 初始化页面数据
this.getToday();
} else {
// token无效但这种情况应该在http.interceptor.js中处理
this.redirectToLogin();
}
} catch (error) {
console.error("认证检查失败:", error);
// 错误情况下也跳转到登录页通常由interceptor处理
// 这里不需要手动跳转因为40001错误会在interceptor中自动跳转
}
},
// 根据audit_status获取状态文本和样式类
getStatusInfo(auditStatus) {
const status = this.statusList.find((item) => item.id === auditStatus);
return {
text: status ? status.value : "未知状态",
class: this.getStatusClass(auditStatus),
};
},
// 根据audit_status获取样式类名
getStatusClass(auditStatus) {
const statusClasses = {
"-1": "status-study", // 待学习 - 橙色
0: "status-pending", // 待审核 - 蓝色
1: "status-approved", // 通过(待进厂) - 绿色
2: "status-rejected", // 驳回 - 红色
3: "status-entered", // 已进厂 - 深绿色
4: "status-left", // 已离厂 - 灰色
5: "status-cancelled", // 已取消 - 深灰色
};
return statusClasses[auditStatus.toString()] || "status-unknown";
},
// 根据type获取拜访类型文本
getVisitTypeText(type) {
const typeMap = {
1: "普通访客",
2: "施工访客",
3: "物流车辆",
};
return typeMap[type] || "未知类型";
},
// 获取拜访日期范围显示文本
getVisitDateRange(visitData) {
// 如果开始日期或结束日期为空使用date字段
if (!visitData.start_date || !visitData.end_date) {
const date =
visitData.date || visitData.start_date || visitData.end_date;
return date ? `${date}${date}` : "-";
}
// 如果开始日期和结束日期相同,只显示一个日期
if (visitData.start_date === visitData.end_date) {
return visitData.start_date;
}
// 正常显示日期范围
return `${visitData.start_date}${visitData.end_date}`;
},
// 跳转到登录页
redirectToLogin() {
uni.reLaunch({
url: "/pages/login/index",
});
},
// 打开今日访客列表
openList() {
this.getTodayVisitors();
this.listShow = true;
},
// 获取今日日期
getToday() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
const today = `${year}-${month}-${day}`;
this.select.start_date = today;
this.select.end_date = today;
},
// 开始日期选择
onStartDateChange(e) {
const selectedDate = e.detail.value;
this.select.start_date = selectedDate;
// 如果结束日期早于开始日期,自动设置为开始日期
if (this.select.end_date && this.select.end_date < selectedDate) {
this.select.end_date = selectedDate;
}
},
// 结束日期选择
onEndDateChange(e) {
const selectedDate = e.detail.value;
// 如果结束日期早于开始日期,提示用户
if (this.select.start_date && selectedDate < this.select.start_date) {
uni.showToast({
title: "结束日期不能早于开始日期",
icon: "none",
});
return;
}
this.select.end_date = selectedDate;
},
// 核销码输入变化
onCodeInput(e) {
this.select.code = e.detail.value;
if (this.select.code) {
this.getList();
}
},
// 清空输入
clearInputs() {
this.select.code = "";
this.select.idcard = "";
this.select.person_no = "";
},
// 查询访客信息
async getList() {
if (!this.select.code && !this.select.idcard && !this.select.person_no) {
uni.showToast({
title: "请输入核销码或身份证件或ID卡",
icon: "none",
});
return;
}
this.loading = true;
try {
// 这里需要根据实际情况替换API调用
// 使用visitList接口查询访客
const searchParams = {
start_date: this.select.start_date,
end_date: this.select.end_date,
};
// 添加搜索条件
if (this.select.code) {
searchParams.code = this.select.code;
}
if (this.select.idcard) {
searchParams.idcard = this.select.idcard;
}
if (this.select.person_no) {
searchParams.person_no = this.select.person_no;
}
const res = await this.$u.api.visitList(searchParams);
if (res && res.data && res.data.length > 0) {
const visitor = res.data[0];
if (visitor.audit_status == 1 || visitor.audit_status == 3) {
// 设置访客数据,包含新增的拜访信息字段:
// type, start_date, end_date, visit_time_detail, visit_area_detail,
// reason, cars, plate, credent, idcard, company_name, cda,
// follw_people, accompanyName, goodsName
this.visitData = visitor;
// 确保每个随访人员都有follw_people_person_no字段
if (
this.visitData.follw_people &&
this.visitData.follw_people.length > 0
) {
this.visitData.follw_people.forEach((person) => {
if (!person.follw_people_person_no) {
person.follw_people_person_no = "";
}
});
}
this.personNoValue = visitor.person_no;
this.remarkValue = visitor.remark;
if (
visitor.vehicle_images_detail &&
visitor.vehicle_images_detail.length > 0
) {
let arr = [];
visitor.vehicle_images_detail.map((item) => {
arr.push({
id: item.id,
url: item.url,
});
});
this.vehicleImages = arr;
}
// 核销的值
this.codeForm.code = visitor.code;
this.codeForm.type = visitor.audit_status == 1 ? 1 : 2; //(visitor.audit_status == 3 && visitor.accept_admin_sign ? 2 : 0) 离厂陪同人员签字
this.visitShow = true;
} else {
const statusInfo = this.getStatusInfo(visitor.audit_status);
uni.showToast({
title: statusInfo.text,
icon: "none",
});
}
} else {
uni.showToast({
title: "未查询到记录",
icon: "none",
});
}
} catch (error) {
uni.showToast({
title: "查询失败",
icon: "none",
});
} finally {
this.loading = false;
}
},
// 核销
cancelCode() {
console.log(this.codeForm);
if (this.codeForm.type == 0) {
uni.showToast({
title: "请提醒陪同人签字",
icon: "none",
});
return;
}
this.$u.api.visitCancel(this.codeForm).then((res) => {
// if (res.code == 200) {
// this.$successMessage(res.msg, '', 'success')
// }
console.log(res);
uni.showToast({
title: "核销成功",
icon: "none",
});
});
},
// 读取身份证
getIdcard() {
this.select.idcard = "";
readIdCard().then((res) => {
console.log("读取身份证", res);
if (res.data.resultFlag == 0) {
this.select.idcard = res.data.certNumber;
this.getList();
} else {
uni.showToast({
title: "读取身份证失败",
icon: "none",
});
}
});
},
// 切换全屏
toggleFullscreen() {
// #ifdef H5
const element = document.documentElement;
if (this.fullscreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
this.fullscreen = !this.fullscreen;
// #endif
},
// 关闭访客信息弹窗
closeVisitModal() {
this.visitShow = false;
this.visitData = null;
// 清空输入数据
this.vehicleImages = [];
this.remarkValue = "";
this.personNoValue = "";
this.uploading = false;
this.updating = false;
this.showPhotoActionSheet = false;
},
// 显示照片选择选项
showPhotoOptions() {
this.showPhotoActionSheet = true;
},
// 隐藏照片选择选项
hidePhotoOptions() {
this.showPhotoActionSheet = false;
},
// 选择照片
chooseImage() {
this.hidePhotoOptions();
const remainingCount = 9 - this.vehicleImages.length;
uni.chooseImage({
count: remainingCount, // 根据已上传数量动态计算
sizeType: ["original", "compressed"],
sourceType: ["album"],
success: (res) => {
this.uploadImages(res.tempFilePaths);
},
});
},
// 拍照
takePhoto() {
this.hidePhotoOptions();
uni.chooseImage({
count: 1,
sizeType: ["original", "compressed"],
sourceType: ["camera"],
success: (res) => {
this.uploadImages(res.tempFilePaths);
},
});
},
// 上传图片
async uploadImages(filePaths) {
if (!filePaths || filePaths.length === 0) return;
this.uploading = true;
try {
for (const filePath of filePaths) {
const uploadRes = await this.uploadSingleImage(filePath);
console.log("uploadRes123", uploadRes);
if (uploadRes && uploadRes.id) {
this.vehicleImages.push({
id: uploadRes.id,
url: uploadRes.url || filePath, // 使用返回的URL或本地路径
});
}
console.log("this.vehicleImages", this.vehicleImages);
}
uni.showToast({
title: "上传成功",
icon: "success",
});
} catch (error) {
console.error("上传失败:", error);
uni.showToast({
title: "上传失败",
icon: "none",
});
} finally {
this.uploading = false;
}
},
// 上传单张图片
uploadSingleImage(filePath) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.$u.http.config.baseUrl + "/api/admin/upload-file",
filePath: filePath,
name: "file",
header: {
Authorization: `Bearer ${this.$store.state.vuex_token}`,
},
success: (uploadRes) => {
try {
console.log("uploadRes", uploadRes);
const data = JSON.parse(uploadRes.data);
if (data && data.id) {
resolve(data);
} else {
reject("上传响应格式错误");
}
} catch (error) {
reject("解析上传响应失败");
}
},
fail: (error) => {
reject(error);
},
});
});
},
// 删除图片
removeImage(index) {
uni.showModal({
title: "确认删除",
content: "确定要删除这张照片吗?",
success: (res) => {
if (res.confirm) {
this.vehicleImages.splice(index, 1);
}
},
});
},
// 预览图片
previewImage(url) {
const urls = this.vehicleImages.map((img) => img.url);
uni.previewImage({
urls: urls,
current: url,
});
},
// 更新访客信息
async updateVisitInfo() {
if (this.updating) return;
// 验证必填字段 - 由于去除action暂无必填验证
this.updating = true;
console.log("this.visitData", this.visitData);
// return
try {
const updateParams = {
...this.visitData,
id: this.visitData.id,
remark: this.remarkValue,
person_no: this.personNoValue,
// follw_people 中包含了每个人员的 follw_people_person_no 字段
};
// 如果type==3添加车辆照片数据
if (this.visitData.type == 3) {
updateParams.vehicle_images = this.vehicleImages.map((img) => img.id);
}
const res = await this.$u.api.visitUpdate(updateParams);
uni.showToast({
title: "更新成功",
icon: "success",
});
// 更新成功后直接核销并关闭弹窗
this.cancelCode();
setTimeout(() => {
this.closeVisitModal();
}, 800);
} catch (error) {
console.error("更新失败:", error);
uni.showToast({
title: "更新失败",
icon: "none",
});
} finally {
this.updating = false;
}
},
// 关闭访客列表弹窗
closeListModal() {
this.listShow = false;
},
// 获取今日访客列表
async getTodayVisitors() {
try {
// 这里需要根据实际情况替换API调用
const todayParam = {
start_date: this.select.start_date,
end_date: this.select.end_date,
};
const res = await this.$u.api.visitList(todayParam);
this.todayVisitors = res?.data || [];
} catch (error) {
console.error("获取今日访客列表失败:", error);
this.todayVisitors = [];
}
},
},
};
</script>
<style lang="scss" scoped>
.page {
background: url("../../static/cancel_bg.png") no-repeat center center;
background-size: cover;
min-height: 100vh;
padding: 0;
box-sizing: border-box;
}
/* 头部导航样式 */
.gate-header {
display: flex;
justify-content: center;
align-items: center;
padding: 4% 5%;
margin-bottom: 6%;
background: #18284b;
min-height: 12vh;
/* 移动端样式 */
@media (max-width: 750px) {
padding: 3% 4%;
margin-bottom: 5%;
min-height: 10vh;
}
}
.gate-left {
.today-visitors {
color: #ffffff;
font-size: 36rpx;
font-weight: 600;
cursor: pointer;
@media (max-width: 750px) {
font-size: 32rpx;
}
&:active {
opacity: 0.7;
}
}
}
.gate-right {
display: flex;
align-items: center;
gap: 40rpx;
@media (max-width: 750px) {
justify-content: space-between;
gap: 20rpx;
}
}
.fullscreen-btn {
color: #ffffff;
cursor: pointer;
font-size: 28rpx;
@media (max-width: 750px) {
font-size: 24rpx;
}
&:active {
opacity: 0.7;
}
}
/* 主要内容区域 */
.gate-content {
width: 100%;
max-width: 1800rpx;
margin: 0 auto;
padding: 0 5%;
@media (max-width: 750px) {
max-width: 100%;
padding: 0 4%;
}
}
.form-container {
width: 100%;
}
.form-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 4%;
@media (max-width: 750px) {
font-size: 28rpx;
margin-bottom: 3%;
color: #fff;
}
}
.form-item {
margin-bottom: 5%;
@media (max-width: 750px) {
margin-bottom: 4%;
}
&:last-child {
margin-bottom: 0;
}
}
.form-label {
display: block;
font-size: 32rpx;
color: #333;
margin-bottom: 2%;
font-weight: 500;
@media (max-width: 750px) {
font-size: 28rpx;
margin-bottom: 1.5%;
color: #fff;
}
@media (min-width: 768px) {
font-size: 40rpx;
margin-bottom: 2.5%;
}
}
/* 统一输入框样式 */
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
border: 2rpx solid #e4e7ed;
border-radius: 0;
font-size: 32rpx;
background-color: #fff;
box-sizing: border-box;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
@media (max-width: 750px) {
height: 76rpx;
font-size: 28rpx;
padding: 0 16rpx;
}
@media (min-width: 768px) {
height: 100rpx;
font-size: 36rpx;
padding: 0 28rpx;
}
&:focus {
border-color: #004593;
box-shadow: 0 0 0 2rpx rgba(0, 69, 147, 0.2);
outline: none;
}
}
/* 输入框类型的元素统一样式 */
.input-field {
color: #333;
&::placeholder {
color: #c0c4cc;
}
}
/* 日期输入框样式 */
.date-input {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-align: center;
text {
font-size: 32rpx;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media (max-width: 750px) {
font-size: 26rpx;
}
@media (min-width: 768px) {
font-size: 36rpx;
}
&.icon {
font-size: 24rpx;
color: #c0c4cc;
margin-left: 8rpx;
@media (max-width: 750px) {
font-size: 20rpx;
margin-left: 6rpx;
}
@media (min-width: 768px) {
font-size: 28rpx;
margin-left: 10rpx;
}
}
}
&:active {
border-color: #004593;
box-shadow: 0 0 0 2rpx rgba(0, 69, 147, 0.2);
}
}
/* 日期范围选择器容器 */
.date-range-container {
display: flex;
align-items: center;
gap: 20rpx;
@media (max-width: 750px) {
gap: 12rpx;
}
.date-picker {
flex: 1;
min-width: 0; // 防止flex子项溢出
@media (max-width: 750px) {
flex: 1;
}
}
}
/* 日期分隔符 */
.date-separator {
font-size: 28rpx;
color: #666;
font-weight: 500;
min-width: 40rpx;
text-align: center;
white-space: nowrap;
flex-shrink: 0; // 防止压缩
@media (max-width: 750px) {
font-size: 24rpx;
min-width: 30rpx;
padding: 0 8rpx;
color: #fff;
}
@media (min-width: 768px) {
font-size: 32rpx;
min-width: 60rpx;
}
}
.id-input-group {
display: flex;
gap: 20rpx;
@media (max-width: 750px) {
flex-direction: row;
align-items: center;
gap: 16rpx;
}
width: 100%;
}
/* 身份证输入占比宽度设置 */
/* 默认含APP打包身份证输入宽度100% */
.idcard-group .form-input {
width: 100%;
flex: 1 1 auto;
max-width: 100%;
}
/* #ifdef H5 */
/* H5下身份证输入宽度为70%,右侧显示查询入口 */
@media all {
.idcard-group .form-input {
width: 70%;
flex: 0 0 70%;
max-width: 70%;
}
.idcard-group .id-action {
width: 30%;
flex: 0 0 30%;
max-width: 30%;
}
}
/* #endif */
.id-input {
flex: 1;
@media (max-width: 750px) {
width: 100%;
}
}
.id-btn {
@media (max-width: 750px) {
width: auto;
}
@media (min-width: 768px) {
min-width: 240rpx;
}
white-space: nowrap;
}
/* 身份证查询行为元素,等高于输入框 */
.id-action {
height: 88rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #18284b;
color: #fff;
font-size: 28rpx;
border-radius: 0;
white-space: nowrap;
cursor: pointer;
transition: background-color 0.2s ease;
@media (max-width: 750px) {
height: 76rpx;
font-size: 26rpx;
}
@media (min-width: 768px) {
height: 100rpx;
font-size: 32rpx;
}
&:active {
background-color: #0f1a3a;
}
}
.query-btn-container {
text-align: center;
margin-top: 10%;
@media (max-width: 750px) {
margin-top: 20%;
}
}
.query-btn {
width: 100%;
height: 100rpx;
font-size: 36rpx;
font-weight: 600;
@media (max-width: 750px) {
height: 88rpx;
font-size: 32rpx;
}
@media (min-width: 768px) {
height: 120rpx;
font-size: 48rpx;
}
}
/* 按钮样式 */
.btn {
padding: 20rpx 40rpx;
border: none;
border-radius: 0;
font-size: 28rpx;
cursor: pointer;
&.btn-primary {
background-color: #18284b;
color: #fff;
&:disabled {
background-color: #c0c4cc;
cursor: not-allowed;
}
&:active:not(:disabled) {
background-color: #0f1a3a;
}
}
@media (max-width: 750px) {
padding: 16rpx 32rpx;
font-size: 26rpx;
}
@media (min-width: 768px) {
padding: 24rpx;
font-size: 32rpx;
}
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background-color: #fff;
border-radius: 16rpx;
width: 95%;
max-width: 800rpx;
max-height: 85%;
overflow: hidden;
@media (max-width: 750px) {
width: 92%;
max-width: 700rpx;
}
@media (min-width: 768px) {
width: 75%;
max-width: 1000rpx;
}
@media (min-width: 1024px) {
width: 65%;
max-width: 1200rpx;
}
}
.modal-header {
padding: 40rpx 40rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
@media (max-width: 750px) {
font-size: 32rpx;
}
}
}
.modal-body {
padding: 40rpx;
max-height: 60vh;
overflow-y: auto;
}
.modal-footer {
padding: 20rpx 40rpx 40rpx;
display: flex;
gap: 20rpx;
justify-content: center;
border-top: 1rpx solid #f0f0f0;
.btn {
flex: 1;
max-width: 300rpx;
}
}
.visit-info {
padding: 20rpx 0;
}
/* 信息区块样式 */
.info-section {
margin-bottom: 40rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #004593;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 2rpx solid #004593;
position: relative;
&::after {
content: "";
position: absolute;
bottom: -2rpx;
left: 0;
width: 60rpx;
height: 2rpx;
background-color: #004593;
}
}
.info-item {
display: flex;
margin-bottom: 20rpx;
align-items: center;
&:last-child {
margin-bottom: 0;
}
/* 特殊布局处理 */
&.photo-upload-section {
align-items: flex-start;
}
}
/* 随访人员项样式 */
.follow-person-item {
background-color: #f8f9fa;
padding: 20rpx;
border-radius: 8rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.info-item {
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
}
}
.separator {
height: 1rpx;
background-color: #e8e8e8;
margin: 20rpx 0;
}
.info-label {
font-size: 28rpx;
color: #666;
min-width: 120rpx;
margin-right: 20rpx;
}
.info-value {
font-size: 28rpx;
color: #333;
flex: 1;
word-break: break-all;
/* 状态标签特殊处理 */
&.status-badge {
flex: none; // 取消flex伸缩
align-self: flex-start; // 左对齐
margin-left: 0;
}
}
.visitor-list {
max-height: 60vh;
overflow-y: auto;
}
.visitor-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fafafa;
color: #666;
font-size: 24rpx;
font-weight: 500;
position: sticky;
top: 0;
z-index: 1;
}
.visitor-col {
flex: 1;
}
.visitor-col-name {
flex: 1.2;
}
.visitor-col-visited {
flex: 1.2;
text-align: center;
}
.visitor-col-status {
flex: 0.8;
text-align: right;
}
.empty-state {
text-align: center;
padding: 60rpx 20rpx;
color: #999;
font-size: 28rpx;
}
.visitor-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.visitor-name {
flex: 1.2;
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.visitor-visited {
flex: 1.2;
font-size: 26rpx;
color: #555;
text-align: center;
}
.visitor-status {
flex: 0.8;
font-size: 24rpx;
padding: 8rpx 16rpx;
border-radius: 12rpx;
text-align: right;
}
/* 状态标签样式 */
.status-badge {
display: inline-block;
padding: 6rpx 16rpx;
border-radius: 16rpx;
font-size: 24rpx;
font-weight: 500;
text-align: center;
@media (max-width: 750px) {
padding: 4rpx 12rpx;
font-size: 22rpx;
}
}
/* 待学习 - 橙色 */
.status-study {
background-color: #fff7e6;
color: #fa8c16;
border: 1rpx solid #ffd591;
}
/* 待审核 - 蓝色 */
.status-pending {
background-color: #e6f7ff;
color: #1890ff;
border: 1rpx solid #91d5ff;
}
/* 通过(待进厂) - 绿色 */
.status-approved {
background-color: #f6ffed;
color: #52c41a;
border: 1rpx solid #b7eb8f;
}
/* 驳回 - 红色 */
.status-rejected {
background-color: #fff2f0;
color: #ff4d4f;
border: 1rpx solid #ffb3b3;
}
/* 已进厂 - 深绿色 */
.status-entered {
background-color: #f0f9f0;
color: #389e0d;
border: 1rpx solid #95de64;
}
/* 已离厂 - 灰色 */
.status-left {
background-color: #f5f5f5;
color: #8c8c8c;
border: 1rpx solid #d9d9d9;
}
/* 已取消 - 深灰色 */
.status-cancelled {
background-color: #f0f0f0;
color: #595959;
border: 1rpx solid #bfbfbf;
}
/* 未知状态 - 默认样式 */
.status-unknown {
background-color: #fafafa;
color: #666;
border: 1rpx solid #e8e8e8;
}
/* 输入框相关样式 */
.info-input-wrapper {
flex: 1;
margin-left: 20rpx;
}
.info-input {
width: 100%;
height: 60rpx;
padding: 0 16rpx;
border: 1rpx solid #e4e7ed;
border-radius: 6rpx;
font-size: 26rpx;
background-color: #fff;
box-sizing: border-box;
}
.info-textarea {
width: 100%;
min-height: 80rpx;
padding: 12rpx 16rpx;
border: 1rpx solid #e4e7ed;
border-radius: 6rpx;
font-size: 26rpx;
background-color: #fff;
box-sizing: border-box;
resize: none;
}
/* 照片上传相关样式 */
.photo-upload-section {
flex-direction: column;
align-items: flex-start;
.info-label {
margin-bottom: 16rpx;
}
}
.photo-upload-container {
width: 100%;
}
.uploaded-photos {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 20rpx;
}
.photo-item {
position: relative;
width: 120rpx;
height: 120rpx;
}
.photo-preview {
width: 100%;
height: 100%;
border-radius: 8rpx;
border: 1rpx solid #e8e8e8;
}
.photo-delete {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
background-color: #ff4d4f;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
font-weight: bold;
cursor: pointer;
}
.upload-actions {
display: flex;
gap: 16rpx;
}
.upload-btn {
flex: 1;
height: 60rpx;
font-size: 26rpx;
/* 覆盖通用按钮的垂直选项,确保文字垂直居中 */
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 添加照片按钮样式 */
.add-photo-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border: 2rpx dashed #d0d0d0;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background-color: #e9ecef;
border-color: #004593;
}
}
.add-photo-icon {
font-size: 48rpx;
color: #666;
margin-bottom: 8rpx;
font-weight: 300;
}
.add-photo-text {
font-size: 24rpx;
color: #999;
}
/* 底部选择弹窗样式 */
.photo-action-sheet {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2000;
}
.action-sheet-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.action-sheet-content {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 0 0 40rpx 0;
animation: slideUp 0.3s ease-out;
}
.action-sheet-item {
padding: 40rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s ease;
&:active {
background-color: #f8f9fa;
}
&:last-of-type {
border-bottom: none;
}
}
.action-sheet-text {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.action-sheet-cancel {
padding: 40rpx;
text-align: center;
margin-top: 20rpx;
cursor: pointer;
transition: background-color 0.2s ease;
&:active {
background-color: #f8f9fa;
}
}
.action-sheet-cancel-text {
font-size: 32rpx;
color: #999;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* 按钮样式扩展 */
.btn-outline {
background-color: transparent;
color: #004593;
border: 1rpx solid #004593;
&:active:not(:disabled) {
background-color: rgba(0, 69, 147, 0.1);
}
&:disabled {
color: #c0c4cc;
border-color: #c0c4cc;
cursor: not-allowed;
}
}
.btn-secondary {
background-color: #f5f7fa;
color: #606266;
border: 1rpx solid #dcdfe6;
&:active:not(:disabled) {
background-color: #e4e7ed;
}
}
/* 响应式样式优化 */
@media (max-width: 750px) {
.form-container {
padding: 20rpx;
}
.modal-content {
margin: 20rpx;
width: calc(100% - 40rpx);
}
}
</style>