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.

818 lines
18 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-container">
<!-- 头部导航 -->
<view class="gate-header">
<view class="gate-left">
<view class="today-visitors" @click="openList">访</view>
</view>
<view class="gate-right">
<view class="gate-info">
<text class="gate-name">{{ gateName }}</text>
<text class="switch-gate" @click="showGateSelector"></text>
</view>
<!-- #ifdef H5 -->
<view class="fullscreen-btn" @click="toggleFullscreen">
{{ fullscreen ? '' : '' }}
</view>
<!-- #endif -->
</view>
</view>
<!-- 主要内容区域 -->
<view class="gate-content">
<view class="form-container">
<!-- 拜访日期选择 -->
<view class="form-item">
<text class="form-label">拜访日期</text>
<u-datetime-picker
v-model="datePickerShow"
mode="range"
:start-year="2020"
:end-year="2030"
@confirm="onDateConfirm"
@cancel="datePickerShow = false">
</u-datetime-picker>
<u-input
v-model="dateRangeText"
placeholder="选择日期范围"
@click="datePickerShow = true"
readonly
class="date-input">
<u-icon name="calendar-fill" slot="suffix" color="#c0c4cc"></u-icon>
</u-input>
</view>
<!-- 核验销码输入 -->
<view class="form-item">
<text class="form-label">核验销码:</text>
<u-input
v-model="select.code"
placeholder="请输入核销码或扫码"
clearable
@change="onCodeChange"
class="form-input">
</u-input>
</view>
<!-- 身份证输入 -->
<view class="form-item">
<text class="form-label">身份证件:</text>
<view class="id-input-group">
<u-input
v-model="select.idcard"
placeholder="请输入身份证"
clearable
class="id-input">
</u-input>
<u-button type="primary" @click="getIdcard" class="id-btn">查询身份证</u-button>
</view>
</view>
<!-- 查询按钮 -->
<view class="form-item query-btn-container">
<u-button type="primary" @click="getList" class="query-btn" :loading="loading"></u-button>
</view>
</view>
</view>
</view>
<!-- 门岗人员选择弹窗 -->
<u-modal v-model="gateShow" title="请先选择门岗人员" :show-cancel-button="false" :mask-close-able="false">
<view class="gate-selector">
<u-radio-group v-model="gateAdminId" @change="onGateChange">
<u-radio
v-for="item in gateData"
:key="item.id"
:name="item.id"
class="gate-radio">
{{ item.name }}
</u-radio>
</u-radio-group>
</view>
<view slot="confirm-button">
<u-button type="primary" @click="confirmGate">确定</u-button>
</view>
</u-modal>
<!-- 访客信息弹窗 -->
<u-modal v-model="visitShow" title="访客信息" width="90%" :show-cancel-button="false">
<view class="visit-info" v-if="visitData">
<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.audit_status_text }}</text>
</view>
<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">
<text class="info-label">联系电话:</text>
<text class="info-value">{{ visitData.mobile }}</text>
</view>
</view>
<view slot="confirm-button">
<u-button type="primary" @click="closeVisitModal">关闭</u-button>
</view>
</u-modal>
<!-- 今日访客列表弹窗 -->
<u-modal v-model="listShow" title="今日访客" width="90%">
<view class="visitor-list">
<view v-if="todayVisitors.length === 0" class="empty-state">
<text>今日暂无访客</text>
</view>
<view v-else>
<view v-for="visitor in todayVisitors" :key="visitor.id" class="visitor-item">
<view class="visitor-name">{{ visitor.name }}</view>
<view class="visitor-status">{{ visitor.audit_status_text }}</view>
</view>
</view>
</view>
</u-modal>
</view>
</template>
<script>
export default {
data() {
return {
fullscreen: false,
gateShow: false,
gateAdminId: '',
gateName: '',
gateData: [],
gateUser: {},
loading: false,
datePickerShow: false,
dateRangeText: '',
select: {
page: 1,
rows: 10,
keyword: '',
audit_status: '',
start_date: '',
end_date: '',
is_export: 0,
code: '',
idcard: ''
},
visitShow: false,
visitData: null,
listShow: false,
todayVisitors: []
}
},
onLoad() {
this.getUserList()
this.getToday()
},
onReachBottom() {
// 上拉加载更多
},
onPullDownRefresh() {
// 下拉刷新
this.getToday()
uni.stopPullDownRefresh()
},
methods: {
// 打开今日访客列表
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
this.dateRangeText = `${today}${today}`
},
// 日期选择确认
onDateConfirm(data) {
this.select.start_date = data.startDate
this.select.end_date = data.endDate
this.dateRangeText = `${data.startDate}${data.endDate}`
this.datePickerShow = false
},
// 核销码变化
onCodeChange() {
if (this.select.code) {
this.getList()
}
},
// 清空输入
clearInputs() {
this.select.code = ''
this.select.idcard = ''
},
// 查询访客信息
async getList() {
if (!this.select.code && !this.select.idcard) {
uni.showToast({
title: '请输入核销码或身份证件',
icon: 'none'
})
return
}
this.loading = true
try {
// 这里需要根据实际情况替换API调用
const res = await this.apiGetList(this.select)
if (res && res.data && res.data.length > 0) {
const visitor = res.data[0]
if (visitor.audit_status == 1 || visitor.audit_status == 3) {
this.visitData = visitor
this.visitShow = true
} else {
uni.showToast({
title: visitor.audit_status_text,
icon: 'none'
})
}
} else {
uni.showToast({
title: '未查询到记录',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '查询失败',
icon: 'none'
})
} finally {
this.loading = false
this.clearInputs()
}
},
// 获取门岗人员列表
async getUserList() {
try {
// 从存储中获取门岗用户信息
const gateUser = uni.getStorageSync('gateUser')
if (gateUser) {
this.gateUser = gateUser
this.gateAdminId = gateUser.gateAdminId
this.gateName = gateUser.gateName
}
// 获取门岗人员列表
const res = await this.apiGetUserList()
this.gateData = res || []
if (!this.gateAdminId && this.gateData.length > 0) {
this.gateShow = true
}
} catch (error) {
console.error('获取门岗人员列表失败:', error)
}
},
// 门岗人员选择变化
onGateChange(value) {
this.gateAdminId = value
},
// 显示门岗选择器
showGateSelector() {
this.gateShow = true
},
// 确认门岗选择
confirmGate() {
if (!this.gateAdminId) {
uni.showToast({
title: '请先选择门岗',
icon: 'none'
})
return
}
const selectedGate = this.gateData.find(item => item.id == this.gateAdminId)
if (selectedGate) {
this.gateName = selectedGate.name
// 保存到存储
const gateUser = {
gateName: this.gateName,
gateAdminId: this.gateAdminId
}
uni.setStorageSync('gateUser', gateUser)
}
this.gateShow = false
},
// 读取身份证
getIdcard() {
// #ifdef H5
// H5环境下的身份证读取逻辑
uni.request({
url: 'https://127.0.0.1:24011/ZKIDROnline/ScanReadIdCardInfo?OP-DEV=1&CMD-URL=4&REPEAT=1&READTYPE=1',
method: 'GET',
header: {
'Content-Type': 'application/json'
},
success: (res) => {
if (res.data) {
try {
const data1 = res.data.split('"IDNumber"')
if (data1.length > 1) {
const data2 = data1[1].split(',')
const idNumber = data2[0].replace(/[^\d]/g, '')
this.select.idcard = idNumber
this.getList()
}
} catch (error) {
console.error('解析身份证信息失败:', error)
}
}
},
fail: (error) => {
console.error('读取身份证失败:', error)
uni.showToast({
title: '读取身份证失败',
icon: 'none'
})
}
})
// #endif
// #ifndef H5
uni.showToast({
title: '该功能仅支持H5环境',
icon: 'none'
})
// #endif
},
// 切换全屏
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
},
// 获取今日访客列表
async getTodayVisitors() {
try {
// 这里需要根据实际情况替换API调用
const todayParam = {
start_date: this.select.start_date,
end_date: this.select.end_date
}
const res = await this.apiGetTodayVisitors(todayParam)
this.todayVisitors = res?.data || []
} catch (error) {
console.error('获取今日访客列表失败:', error)
this.todayVisitors = []
}
},
// API调用方法需要根据实际情况调整
async apiGetList(params) {
return new Promise((resolve, reject) => {
uni.request({
url: '/api/gate/list', // 替换为实际API地址
method: 'GET',
data: params,
success: (res) => {
resolve(res.data)
},
fail: (error) => {
reject(error)
}
})
})
},
async apiGetUserList() {
return new Promise((resolve, reject) => {
uni.request({
url: '/api/gate/users', // 替换为实际API地址
method: 'GET',
success: (res) => {
resolve(res.data)
},
fail: (error) => {
reject(error)
}
})
})
},
async apiGetTodayVisitors(params) {
return new Promise((resolve, reject) => {
uni.request({
url: '/api/gate/today-visitors', // 替换为实际API地址
method: 'GET',
data: params,
success: (res) => {
resolve(res.data)
},
fail: (error) => {
reject(error)
}
})
})
}
},
};
</script>
<style lang="scss" scoped>
.page {
background: linear-gradient(135deg, #e4eafa, #f1f7fa);
min-height: 100vh;
padding: 20rpx;
box-sizing: border-box;
}
/* 门岗容器样式 */
.gate-container {
background-color: #fff;
min-height: calc(100vh - 40rpx);
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* 头部导航样式 */
.gate-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 2rpx solid #f0f0f0;
background: #fafafa;
/* 移动端样式 */
@media (max-width: 750px) {
padding: 20rpx 30rpx;
flex-direction: column;
gap: 20rpx;
align-items: stretch;
}
}
.gate-left {
.today-visitors {
color: #004593;
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;
}
}
.gate-info {
display: flex;
align-items: center;
gap: 20rpx;
.gate-name {
font-size: 32rpx;
color: #333;
@media (max-width: 750px) {
font-size: 28rpx;
}
}
.switch-gate {
color: #004593;
text-decoration: underline;
cursor: pointer;
font-size: 28rpx;
@media (max-width: 750px) {
font-size: 24rpx;
}
&:active {
opacity: 0.7;
}
}
}
.fullscreen-btn {
color: #666;
cursor: pointer;
font-size: 28rpx;
@media (max-width: 750px) {
font-size: 24rpx;
}
&:active {
color: #004593;
}
}
/* 主要内容区域 */
.gate-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
@media (max-width: 750px) {
align-items: flex-start;
padding: 40rpx 30rpx;
}
}
.form-container {
width: 100%;
max-width: 1000rpx;
@media (min-width: 768px) {
max-width: 1200rpx;
}
}
.form-item {
margin-bottom: 50rpx;
@media (max-width: 750px) {
margin-bottom: 40rpx;
}
&:last-child {
margin-bottom: 0;
}
}
.form-label {
display: block;
font-size: 32rpx;
color: #333;
margin-bottom: 20rpx;
font-weight: 500;
@media (max-width: 750px) {
font-size: 28rpx;
margin-bottom: 16rpx;
}
@media (min-width: 768px) {
font-size: 40rpx;
margin-bottom: 24rpx;
}
}
.form-input, .date-input {
width: 100%;
}
.id-input-group {
display: flex;
gap: 20rpx;
@media (max-width: 750px) {
flex-direction: column;
gap: 16rpx;
}
}
.id-input {
flex: 1;
}
.id-btn {
@media (max-width: 750px) {
width: 100%;
}
@media (min-width: 768px) {
min-width: 240rpx;
}
}
.query-btn-container {
text-align: center;
margin-top: 60rpx;
@media (max-width: 750px) {
margin-top: 50rpx;
}
}
.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;
}
}
/* 弹窗样式 */
.gate-selector {
padding: 20rpx 0;
}
.gate-radio {
margin-bottom: 20rpx;
padding: 20rpx;
border: 2rpx solid #e4e7ed;
border-radius: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
.visit-info {
padding: 20rpx 0;
}
.info-item {
display: flex;
margin-bottom: 20rpx;
align-items: center;
&:last-child {
margin-bottom: 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;
}
.visitor-list {
max-height: 60vh;
overflow-y: auto;
}
.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 {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.visitor-status {
font-size: 24rpx;
color: #666;
padding: 8rpx 16rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
}
/* uView组件样式覆盖 */
::v-deep .u-input {
.u-input__input {
height: 88rpx !important;
font-size: 32rpx !important;
@media (max-width: 750px) {
height: 80rpx !important;
font-size: 28rpx !important;
}
@media (min-width: 768px) {
height: 100rpx !important;
font-size: 36rpx !important;
}
}
}
::v-deep .u-button {
height: 88rpx !important;
font-size: 28rpx !important;
@media (max-width: 750px) {
height: 80rpx !important;
font-size: 26rpx !important;
}
@media (min-width: 768px) {
height: 100rpx !important;
font-size: 32rpx !important;
}
}
::v-deep .u-modal {
@media (max-width: 750px) {
.u-modal__content {
width: 90% !important;
margin-top: 15vh !important;
}
}
}
::v-deep .u-radio-group {
.u-radio {
margin-bottom: 20rpx !important;
@media (max-width: 750px) {
width: 100% !important;
}
}
}
</style>