Merge branch 'develop_wsy' into 'develop'

Develop wsy required merge

See merge request StarlockTeam/starwork-uniapp!16
This commit is contained in:
魏少阳 2025-01-24 06:02:18 +00:00
commit f8f65fdb77
36 changed files with 4969 additions and 3779 deletions

View File

@ -122,8 +122,6 @@
"@uni-helper/vite-plugin-uni-pages": "0.2.20",
"@uni-helper/vite-plugin-uni-platform": "^0.0.4",
"@unocss/preset-legacy-compat": "^0.59.4",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/runtime-core": "^3.5.13",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.20",

3394
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -279,8 +279,7 @@
"path": "pages/mine/mine",
"type": "page",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "首页"
"navigationStyle": "custom"
}
},
{
@ -362,6 +361,151 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/access-permission-inquiry",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/access-right-detection",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/add-permission-group",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/delivery-status",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/permission-query",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/release-record",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/send-permission",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/viewing-delivery-records",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom",
"disableScroll": false
}
},
{
"path": "pages/personnel-passage/access-authority/access-authority",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/one-click-open-door/one-click-open-door",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/password-open-door/password-open-door",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/traffic-record/traffic-record",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/visitor-invitation/visitor-invitation",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/visitor-manage/visitor-manage-tab",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/time-planning-allocation/add-daily-traffic-plan",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/time-planning-allocation/add-time-schedule",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-detail",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-list",
"type": "page",
"layout": "default",
"style": {
"navigationStyle": "custom"
}
}
],
"subPackages": []

View File

@ -52,7 +52,7 @@
</view>
</template>
<script lang="ts" setup>
<script setup>
import CommonItem from '@/components/CommonItemItem/CommonItem.vue'
const times = ref([

View File

@ -121,47 +121,52 @@
name: '人员通行',
list: [
{
id: 106,
icon: 'https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png',
name: '通行权限',
id: 230,
icon: 'https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png',
name: '门禁管理',
url: '/pages/personnel-passage/traffic-correlation'
},
{
id: 230,
icon: 'https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png',
name: '门禁控制',
url: '/pages/personnel-passage/traffic-correlation'
id: 106,
icon: 'https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png',
name: '门禁授权',
url: '/pages/personnel-passage/access-authority/access-authority'
},
{
id: 302,
icon: 'https://file.hikmall.com/prod/image/885eb6ac104e4a76941e67eb738142ec.png',
name: '一键开门'
name: '一键开门',
url: '/pages/personnel-passage/one-click-open-door/one-click-open-door'
},
{
id: 400,
icon: 'https://file.hikmall.com/prod/image/701d5a7031fa4b4290838c4562f99a0b.png',
name: '密码开门'
name: '密码开门',
url: '/pages/personnel-passage/password-open-door/password-open-door'
},
{
id: 299,
icon: 'https://file.hikmall.com/prod/image/771a717eb8d74f5bba71db68c9605a0f.png',
name: '通行记录',
url: '/pages/personnel-passage/traffic-correlation'
url: '/pages/personnel-passage/traffic-record/traffic-record'
},
{
id: 387,
icon: 'https://file.hikmall.com/prod/image/9752516f054342d3ac310a0fdb32b654.png',
name: '访客管理'
name: '访客管理',
url: '/pages/personnel-passage/visitor-manage/visitor-manage-tab'
},
{
id: 399,
icon: 'https://file.hikmall.com/prod/image/4dbe971d6fac465cb4fed956defd678c.png',
name: '我的访客'
name: '我的访客',
url: '/pages/personnel-passage/visitor-manage/visitor-manage-tab'
},
{
id: 388,
icon: 'https://file.hikmall.com/prod/image/624a69276df1480088661e20cbc0e189.png',
name: '访客邀约'
name: '访客邀约',
url: '/pages/personnel-passage/visitor-invitation/visitor-invitation'
}
]
},

View File

@ -1,8 +1,7 @@
<route lang="json5">
{
style: {
navigationStyle: 'custom',
navigationBarTitleText: '首页'
navigationStyle: 'custom'
}
}
</route>

View File

@ -1,6 +1,6 @@
<route lang="json5">
{
type: 'page',
layout: 'default',
style: {
navigationStyle: 'custom'
}
@ -10,7 +10,7 @@
<template>
<view class="control-container">
<!-- 快捷功能区 -->
<view class="quick-actions">
<!-- <view class="quick-actions">
<view class="quick-item" style="background-color: #fff7f2">
<view class="quick-content">
<text class="quick-title">门自动常开</text>
@ -25,7 +25,7 @@
<image class="quick-icon" src="/static/images/icon_phone_unlock.png" mode="aspectFit" />
</view>
</view>
</view>
</view> -->
<!-- 搜索框 -->
<view class="search-box">
@ -73,31 +73,6 @@
</view>
</view>
</view>
<!-- 底部导航 -->
<view class="tab-bar">
<view class="tab-item">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png"
/>
<text>通行权限</text>
</view>
<view class="tab-item active">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png"
/>
<text>门禁控制</text>
</view>
<view class="tab-item">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/885eb6ac104e4a76941e67eb738142ec.png"
/>
<text>通行记录</text>
</view>
</view>
</view>
</template>
@ -131,75 +106,74 @@
}
}
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 12px 0;
// .quick-actions {
// display: grid;
// grid-template-columns: 1fr 1fr;
// gap: 12px;
// margin: 12px 0;
.quick-item {
position: relative;
height: 50px;
padding: 12px;
border-radius: 8px;
// .quick-item {
// position: relative;
// height: 50px;
// padding: 12px;
// border-radius: 8px;
&:first-child {
background-color: #eef4ff;
// background: red;
.quick-icon {
background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
}
}
// &:first-child {
// background-color: #eef4ff;
// // background: red;
// .quick-icon {
// background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
// }
// }
&:last-child {
background-color: #fff7f2;
.quick-icon {
background: linear-gradient(135deg, #ff9853 0%, #ff7b30 100%);
}
}
// &:last-child {
// background-color: #fff7f2;
// .quick-icon {
// background: linear-gradient(135deg, #ff9853 0%, #ff7b30 100%);
// }
// }
.quick-content {
position: relative;
height: 100%;
// .quick-content {
// position: relative;
// height: 100%;
.quick-title {
font-size: 15px;
font-weight: 500;
color: #333;
}
// .quick-title {
// font-size: 15px;
// font-weight: 500;
// color: #333;
// }
.quick-desc {
display: block;
margin-top: 8px;
font-size: 11px;
color: #666;
}
// .quick-desc {
// display: block;
// margin-top: 8px;
// font-size: 11px;
// color: #666;
// }
.quick-icon {
position: absolute;
top: 50%;
right: 0;
width: 45px;
height: 45px;
// padding: 8px;
border-radius: 50%;
transform: translateY(-50%);
// .quick-icon {
// position: absolute;
// top: 50%;
// right: 0;
// width: 45px;
// height: 45px;
// // padding: 8px;
// border-radius: 50%;
// transform: translateY(-50%);
image {
width: 100%;
height: 100%;
filter: brightness(0) invert(1); //
}
}
}
}
}
// image {
// width: 100%;
// height: 100%;
// filter: brightness(0) invert(1); //
// }
// }
// }
// }
// }
.search-box {
display: flex;
align-items: center;
padding: 10px;
margin: 16px 0;
background-color: #fff;
border-radius: 8px;
@ -263,6 +237,7 @@
position: relative;
display: flex;
flex-direction: column;
height: 80px;
padding: 12px;
background-color: #fff;
border-radius: 10px;
@ -281,7 +256,7 @@
.unlock-btn {
position: absolute;
top: 50%; /* 改为50% */
top: 50%;
right: 8px;
z-index: 1;
display: flex;
@ -291,11 +266,11 @@
height: 35px;
background-color: #f5f5f5;
border-radius: 50%;
transform: translateY(-50%); /* 添加这行来实现精确居中 */
transform: translateY(-50%);
.unlock-icon {
width: 30px;
height: 30px;
width: 20px;
height: 20px;
}
}
@ -307,49 +282,16 @@
padding-top: 16px;
.device-icon {
width: 50px;
height: 50px;
//background-color: red;
width: 40px;
height: 40px;
margin-right: 8px;
}
.device-name {
margin-top: 4px;
font-size: 14px;
color: #333;
}
}
}
}
.tab-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: space-around;
padding: 8px 0;
background-color: #fff;
border-top: 1px solid #f5f5f5;
.tab-item {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
.tab-icon {
width: 24px;
height: 24px;
}
text {
font-size: 12px;
}
&.active {
color: #2b5cff;
}
}
}
</style>

View File

@ -0,0 +1,301 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<!-- 门禁管理 -->
<template>
<view class="container">
<!-- 开局引导 -->
<view class="guide-section">
<view class="guide-title">门禁开局引导</view>
<view class="guide-link">点击查看</view>
</view>
<!-- 数据统计 -->
<view class="stats-section" @click="handleStatsClick">
<view class="stats-item">
<text class="number">0</text>
<text class="label">团队人员</text>
</view>
<view class="stats-item">
<text class="number">0</text>
<text class="label">陌生人</text>
</view>
<view class="stats-item">
<text class="number">0</text>
<text class="label">访客</text>
<text class="sub-label">今日开门次数统计</text>
</view>
</view>
<!-- 同步提示 -->
<view class="sync-tip" @click="handleSyncClick">
<view class="dot"></view>
<text class="text">您有6个人员15条信息未同步到设备</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
<!-- 功能列表 -->
<view class="feature-grid">
<view class="feature-item" v-for="item in features" :key="item.name">
<image class="icon" :src="item.icon" mode="aspectFit" />
<text class="name">{{ item.name }}</text>
</view>
</view>
<!-- 更多应用 -->
<view class="more-section">
<text class="section-title">更多应用</text>
<view class="more-grid">
<view class="more-item" v-for="item in moreApps" :key="item.name">
<view class="content">
<view class="name">
<text>{{ item.name }}</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
<text class="desc">{{ item.desc }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const features = [
{
icon: 'https://file.hikmall.com/prod/image/e75144eac8984ee198ef533c2f9d3558.png',
name: '人员管理'
},
{
icon: 'https://file.hikmall.com/prod/image/cf46656ec73b404891e2b46b57ff5fc2.png',
name: '门禁授权'
},
{
icon: 'https://file.hikmall.com/prod/image/0526d084da4a49579f832dd4588dcb16.png',
name: '通行记录'
},
{
icon: 'https://file.hikmall.com/prod/image/7ea5f88d404442eeb958fbe0904777d1.png',
name: '消息订阅'
},
{
icon: 'https://file.hikmall.com/prod/image/2887bb1c453244a2a872ec98c0360478.png',
name: '门自动常开'
}
]
const moreApps = [
{
name: '访客',
desc: '访客人员,临时通行'
},
{
name: '信息发布',
desc: '广告、欢迎词发布到屏幕'
}
]
const handleStatsClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/traffic-record/traffic-record'
})
}
const handleSyncClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/permission-query'
})
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
padding: 12px 16px;
background-color: #f5f6fa;
}
.guide-section {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
.guide-title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.guide-link {
font-size: 14px;
color: #2b5cff;
}
}
.stats-section {
display: flex;
justify-content: space-between;
padding: 20px;
margin-right: 32px;
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
.stats-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.number {
margin-bottom: 8px;
font-size: 18px;
font-weight: 500;
line-height: 1;
color: #333;
}
.label {
font-size: 14px;
color: #666;
}
.sub-label {
position: absolute;
right: -15px;
bottom: -20px;
font-size: 12px;
color: #999;
white-space: nowrap;
}
}
}
.sync-tip {
display: flex;
align-items: center;
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
.dot {
width: 6px;
height: 6px;
margin-right: 8px;
background: #ff4d4f;
border-radius: 50%;
}
.text {
flex: 1;
font-size: 14px;
color: #333;
}
.arrow {
width: 16px;
height: 16px;
}
}
.feature-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
.feature-item {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
.icon {
width: 40px;
height: 40px;
}
.name {
font-size: 12px;
color: #333;
}
}
}
.more-section {
padding-bottom: 16px;
margin-right: 32px;
overflow: hidden;
background: #fff;
border-radius: 12px;
.section-title {
display: block;
padding: 16px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.more-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
padding: 0 10px;
.more-item {
display: flex;
align-items: center;
padding: 16px 10px;
background: #f8f9fc;
border-radius: 8px;
.content {
display: flex;
flex: 1;
flex-direction: column;
.name {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
font-size: 16px;
color: #333;
.arrow {
flex-shrink: 0;
width: 16px;
height: 16px;
}
}
.desc {
display: block;
font-size: 12px;
line-height: 1.4;
color: #999;
}
}
}
}
}
</style>

View File

@ -0,0 +1,168 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="权限查询" />
<view class="inquiry-container">
<!-- 查询表单 -->
<view class="inquiry-form">
<!-- 查询方式 -->
<view class="form-item">
<text class="label">人员查询方式</text>
<view class="select" @click="handleSelectType">
<text>按姓名</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 姓名输入 -->
<view class="form-item">
<text class="label">人员姓名</text>
<input class="input" type="text" placeholder="请输入" placeholder-class="placeholder" />
</view>
<!-- 门禁范围 -->
<view class="form-item">
<text class="label">门禁点范围</text>
<view class="select" @click="handleSelectDoor">
<text class="placeholder">全选</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 下发状态 -->
<view class="form-item">
<text class="label">下发状态</text>
<view class="select" @click="handleSelectStatus">
<text class="placeholder">全选</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-buttons">
<button class="reset-btn">重置</button>
<button class="query-btn">查询</button>
</view>
</view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const handleSelectType = () => {
//
}
const handleSelectDoor = () => {
//
}
const handleSelectStatus = () => {
//
}
</script>
<style lang="scss" scoped>
.inquiry-container {
min-height: 100vh;
padding: 0 16px;
background-color: #f5f6fa;
}
.inquiry-form {
margin-top: 12px;
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-bottom: 1px;
background-color: #fff;
&:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
&:last-child {
margin-bottom: 0;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
.label {
font-size: 14px;
color: #333;
}
.input {
flex: 1;
height: 20px;
padding: 0 12px;
font-size: 14px;
text-align: right;
}
.select {
display: flex;
gap: 4px;
align-items: center;
text {
font-size: 14px;
color: #333;
}
.placeholder {
color: #999;
}
.arrow {
width: 16px;
height: 16px;
}
}
}
}
.bottom-buttons {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 12px;
padding: 16px;
background-color: #fff;
button {
flex: 1;
height: 44px;
font-size: 16px;
border-radius: 22px;
&::after {
border: none;
}
}
.reset-btn {
color: #666;
background-color: #f5f6fa;
}
.query-btn {
color: #fff;
background-color: #2b5cff;
}
}
</style>

View File

@ -0,0 +1,180 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="通行权限检测" />
<view class="detection-container">
<!-- 检测说明 -->
<view class="detection-banner">
<image class="banner-img" src="/static/images/bg_no_device.webp" mode="aspectFit" />
<view class="detection-info">
<image class="info-icon" src="/static/images/icon_white_tip.png" mode="aspectFit" />
<view class="info-content">
<text class="info-title">可检测以下两类问题</text>
<text class="info-item">1. 人员无法通行</text>
<text class="info-item">2.通行记录识别为陌生人抓拍图不是该人员</text>
</view>
</view>
</view>
<!-- 检测表单 -->
<view class="detection-form">
<!-- 姓名输入 -->
<view class="form-item">
<text class="label">姓名</text>
<input
class="input"
type="text"
placeholder="请输入完整姓名"
placeholder-class="placeholder"
/>
</view>
<!-- 门禁选择 -->
<view class="form-item">
<text class="label">门禁</text>
<view class="select" @click="handleSelectDoor">
<text class="placeholder">请选择</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
</view>
<!-- 开始检测按钮 -->
<button class="start-btn" @click="handleStartDetection">开始检测</button>
</view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const handleSelectDoor = () => {
//
}
const handleStartDetection = () => {
//
}
</script>
<style lang="scss" scoped>
.detection-container {
min-height: 100vh;
padding: 0 16px;
background-color: #f5f6fa;
}
.detection-banner {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
margin-top: 16px;
background-color: #fff;
border-radius: 8px;
.banner-img {
width: 100%;
aspect-ratio: 1/1;
object-fit: contain;
}
.detection-info {
display: flex;
// width: 100%;
padding: 10px;
background-color: #f8f9fc;
border-radius: 8px;
.info-icon {
width: 16px;
height: 16px;
margin-top: 2px;
margin-right: 8px;
}
.info-content {
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
.info-title {
font-size: 14px;
color: #333;
}
.info-item {
font-size: 14px;
color: #666;
}
}
}
}
.detection-form {
margin-top: 12px;
.form-item {
display: flex;
align-items: center;
padding: 10px 16px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.label {
min-width: 60px;
font-size: 14px;
color: #333;
}
.input {
flex: 1;
height: 40px;
font-size: 14px;
text-align: right;
}
.select {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
height: 40px;
.placeholder {
margin-right: 8px;
margin-left: auto;
font-size: 14px;
color: #999;
}
.arrow {
width: 16px;
height: 16px;
}
}
}
}
.start-btn {
width: 100%;
height: 44px;
margin-top: 24px;
font-size: 16px;
color: #fff;
background: #2b5cff;
border-radius: 22px;
&::after {
border: none;
}
}
</style>

View File

@ -0,0 +1,310 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="添加权限组" />
<scroll-view
class="group-container"
:scroll-y="true"
:style="{ height: 'calc(100vh - var(--status-bar-height) - 44px)' }"
>
<!-- 配置名称 -->
<view class="form-item input-item">
<view class="form-header">
<view class="form-label">
<text class="required">*</text>
<text>配置名称</text>
</view>
</view>
<input
class="input"
type="text"
v-model="name"
@input="nameLength = name.length"
placeholder="必填"
placeholder-class="placeholder"
maxlength="20"
/>
<text class="count">{{ nameLength }}/20</text>
</view>
<!-- 配置描述 -->
<view class="form-item input-item">
<view class="form-header">
<text class="form-label">配置描述</text>
</view>
<textarea
class="textarea"
v-model="desc"
@input="descLength = desc.length"
placeholder="描述权限配置的范围例如1幢1单元门口机出入口"
placeholder-class="placeholder"
maxlength="50"
/>
<text class="count">{{ descLength }}/50</text>
</view>
<!-- 人员配置方式 -->
<view class="form-item select-item">
<view class="form-label">
<text class="required">*</text>
<text>人员配置方式</text>
</view>
<view class="form-value">
<text>按人员/组织</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 人员范围 -->
<view class="form-item select-item">
<view class="form-label">
<text class="required">*</text>
<text>人员范围</text>
</view>
<view class="form-value">
<text class="placeholder">必选</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 门禁范围 -->
<view class="form-item select-item">
<view class="form-label">
<text class="required">*</text>
<text>门禁范围</text>
</view>
<view class="form-value">
<text class="placeholder">必选</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 门禁通行时间 -->
<view class="form-item select-item" @click="handleTimePlanningAllocationClick">
<view class="form-label">
<text class="required">*</text>
<text>门禁通行时间</text>
</view>
<view class="form-value">
<text>全天时段有效</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 提示说明 -->
<view class="tips">
<view class="tips-header">
<image class="icon" src="/static/images/icon_gray_tip.png" mode="aspectFit" />
<text class="title">提示</text>
</view>
<view class="tips-content">
<view class="tips-list">
<view class="tip-item">
<text>1.</text>
<text>
通行时间仅型号为DS-K1TD1或HST-AU开头的门禁设备有效其余型号仅支持全天时段有效
</text>
</view>
<view class="tip-item">
<text>2.</text>
<text>
通过角色配置[一键开门][密码开门]菜单权限可远程/蓝牙/密码打开此配置范围内的门禁
</text>
</view>
<view class="tip-item">
<text>3.</text>
<text>一键开门不受通行时间门禁状态的限制可全天开门</text>
</view>
</view>
</view>
</view>
<!-- 底部占位防止保存按钮遮挡内容 -->
<view style="height: 76px"></view>
</scroll-view>
<!-- 底部按钮 -->
<button class="save-btn">保存</button>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const nameLength = ref(0)
const descLength = ref(0)
//
const handleTimePlanningAllocationClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-list'
})
}
</script>
<style lang="scss" scoped>
.group-container {
box-sizing: border-box;
padding: 12px 16px;
background-color: #f5f6fa;
}
.form-item {
position: relative;
padding: 16px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
}
.input-item {
.form-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.form-label {
display: flex;
align-items: center;
font-size: 14px;
color: #333;
.required {
margin-right: 4px;
color: #ff4d4f;
}
.placeholder {
margin-left: 4px;
color: #999;
}
}
.input,
.textarea {
display: block; //
width: auto; //
padding: 8px;
// margin-right: 10px;
font-size: 14px;
color: #333;
background-color: #f8f9fc;
border-radius: 4px;
}
.textarea {
height: 80px;
}
.count {
position: absolute;
right: 26px;
bottom: 26px;
font-size: 12px;
color: #999;
}
}
.select-item {
display: flex;
align-items: center;
justify-content: space-between;
.form-label {
display: flex;
align-items: center;
font-size: 14px;
color: #333;
.required {
margin-right: 4px;
color: #ff4d4f;
}
}
.form-value {
display: flex;
gap: 4px;
align-items: center;
font-size: 14px;
color: #999;
.arrow {
width: 16px;
height: 16px;
}
}
}
.tips {
padding: 16px;
margin-top: 12px;
background-color: #fff;
border-radius: 8px;
.tips-header {
display: flex;
align-items: center;
margin-bottom: 8px;
.icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
.title {
font-size: 16px;
font-weight: 500;
color: #333;
}
}
.tips-content {
.tips-list {
padding-left: 4px;
clear: both;
.tip-item {
display: flex;
gap: 4px;
margin-bottom: 8px;
font-size: 14px;
line-height: 1.4;
color: #666;
&:last-child {
margin-bottom: 0;
}
text:last-child {
flex: 1;
}
}
}
}
}
.save-btn {
position: fixed;
right: 16px;
bottom: 16px;
left: 16px;
height: 44px;
font-size: 16px;
color: #fff;
background-color: #2b5cff;
border-radius: 8px;
&::after {
border: none;
}
}
</style>

View File

@ -0,0 +1,278 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="下发状态" />
<view class="status-container">
<!-- 人员信息 -->
<view class="person-info">
<text class="name">HikMall_30013234</text>
<text class="company">19104656的互联</text>
<!-- 门禁信息 -->
<view class="door-info">
<text class="label">门禁</text>
<text class="value">DS-K(L40959329)</text>
</view>
</view>
<!-- 下发状态列表 -->
<view class="status-list">
<!-- 人员信息 -->
<view class="status-item">
<checkbox class="checkbox" :checked="false" />
<view class="item-content">
<text class="title">人员信息</text>
</view>
<text class="status success">已下发</text>
</view>
<!-- 人脸 -->
<view class="status-item">
<checkbox class="checkbox" :checked="false" />
<view class="item-content">
<text class="title">人脸</text>
<image class="icon" src="/static/images/icon_face.png" mode="aspectFit" />
</view>
<text class="status success">已下发</text>
</view>
<!-- 指纹 -->
<view class="status-item">
<checkbox class="checkbox" :checked="false" />
<view class="item-content">
<text class="title">指纹1</text>
<image class="icon" src="/static/images/icon_fingerprint.png" mode="aspectFit" />
<text class="error-msg">设备离线请稍后重试</text>
</view>
<text class="status error">下发失败</text>
</view>
<!-- 密码 -->
<view class="status-item">
<checkbox class="checkbox" :checked="false" />
<view class="item-content">
<text class="title">密码</text>
<image class="icon" src="/static/images/icon_password.png" mode="aspectFit" />
<text class="password">7****9</text>
</view>
<text class="status success">已下发</text>
</view>
</view>
<!-- 状态说明 -->
<view class="status-desc">
<view class="desc-header">
<image class="info-icon" src="/static/images/icon_info.png" mode="aspectFit" />
<text class="title">状态说明</text>
</view>
<view class="desc-list">
<text class="desc-item">1下发中已配置权限正在同步到门禁点</text>
<text class="desc-item">2删除中已删除权限正在同步到门禁点</text>
<text class="desc-item">3待下发已配置权限未同步到门禁点不可通行</text>
<text class="desc-item">4已下发已配置权限并已同步到门禁点可通行</text>
<text class="desc-item">5下发失败已配置权限同步到门禁点失败不可通行</text>
<text class="desc-item">6待重新下发更新信息待将信息同步到门禁点可通行</text>
<text class="desc-item">7待删除已删除权限未同步到门禁点可通行</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-buttons">
<button class="record-btn" @click="handleRecordBtnClick">查看记录</button>
<button class="publish-btn">立即下发</button>
</view>
</view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const handleRecordBtnClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/viewing-delivery-records'
})
}
</script>
<style lang="scss" scoped>
.status-container {
min-height: 100vh;
padding: 12px 16px;
background-color: #f5f6fa;
}
.person-info {
padding: 16px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 8px;
.name {
display: block;
margin-bottom: 4px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.company {
font-size: 14px;
color: #666;
}
}
.door-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
margin-top: 12px;
background-color: #f8f9fc;
border-radius: 8px;
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
}
}
.status-list {
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.status-item {
display: flex;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f5f6fa;
&:last-child {
border-bottom: none;
}
.checkbox {
margin-right: 8px;
transform: scale(0.8);
}
.item-content {
display: flex;
flex: 1;
gap: 4px;
align-items: center;
.title {
font-size: 14px;
color: #333;
}
.icon {
width: 16px;
height: 16px;
}
.error-msg {
font-size: 12px;
color: #999;
}
.password {
font-size: 14px;
color: #666;
}
}
.status {
font-size: 14px;
&.success {
color: #333;
}
&.error {
color: #ff4d4f;
}
}
}
}
.status-desc {
padding: 16px;
background-color: #fff;
border-radius: 8px;
.desc-header {
display: flex;
gap: 4px;
align-items: center;
margin-bottom: 12px;
.info-icon {
width: 16px;
height: 16px;
}
title {
font-size: 14px;
color: #333;
}
}
.desc-list {
display: flex;
flex-direction: column;
gap: 8px;
.desc-item {
font-size: 14px;
line-height: 1.4;
color: #666;
}
}
}
.bottom-buttons {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 12px;
padding: 12px;
background-color: #fff;
button {
flex: 1;
height: 44px;
font-size: 16px;
border-radius: 8px;
&::after {
border: none;
}
}
.record-btn {
color: #666;
background-color: #f5f6fa;
}
.publish-btn {
color: #fff;
background-color: #2b5cff;
}
}
</style>

View File

@ -0,0 +1,364 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="权限查询" />
<scroll-view
class="query-container"
:scroll-y="true"
:style="{ height: 'calc(100vh - var(--status-bar-height) - 44px)' }"
>
<!-- 搜索提示卡片 -->
<view class="search-tip">
<text>人员无法通行?</text>
<text class="check-btn" @click="handleCheckClick">去检测</text>
</view>
<!-- 权限查询入口 -->
<view class="query-entry" @click="handleQueryClick">
<text class="entry-title">通行权限查询</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
<!-- 人员列表 -->
<view class="person-list">
<view class="person-item" v-for="person in personList" :key="person.id">
<view class="person-info">
<text class="person-name">{{ person.name }}</text>
<text class="person-id">{{ person.id }}</text>
</view>
<text class="company">{{ person.company }}</text>
<view class="validity">
<text class="label">有效时间</text>
<text class="value">{{ person.validityPeriod }}</text>
</view>
<view class="access-info">
<view class="door-info">
<image class="icon" src="/static/images/icon_door.png" mode="aspectFit" />
<text>门禁点{{ person.doorPoint }}</text>
</view>
<view class="time-info">
<image class="icon" src="/static/images/icon_time.png" mode="aspectFit" />
<text>通行时间</text>
<view class="time-tag">{{ person.accessTime }}</view>
</view>
</view>
<view class="auth-types" @click="() => handlePersonItemClick(person)">
<view class="type-item" v-for="type in person.authTypes" :key="type.name">
<image class="type-icon" :src="type.icon" mode="aspectFit" />
<text>{{ type.name }}</text>
</view>
</view>
<image class="item-arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-buttons">
<button class="btn add-btn" @click="handleAddBtnClick">添加权限组</button>
<button class="btn publish-btn" @click="handlePublishBtnClick">下发权限</button>
<button class="btn record-btn" @click="handleRecordBtnClick">下发记录</button>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
interface AuthType {
name: string
icon: string
}
interface Person {
id: string
name: string
company: string
validityPeriod: string
doorPoint: string
accessTime: string
authTypes: AuthType[]
}
const personList = ref<Person[]>([
{
id: 'CY01068968',
name: 'HikMall_30013234',
company: '19104656的互联',
validityPeriod: '永久有效',
doorPoint: 'DS-K(L40959329)',
accessTime: '全天时段有效',
authTypes: [
{ name: '人员', icon: '/static/images/icon_person.png' },
{ name: '人脸', icon: '/static/images/icon_face.png' },
{ name: '指纹', icon: '/static/images/icon_fingerprint.png' },
{ name: '密码', icon: '/static/images/icon_password.png' }
]
},
{
id: 'CY01069559',
name: '4',
company: '19104656的互联/test',
validityPeriod: '2000-01-01至2037-12-31',
doorPoint: 'DS-K(L40959329)',
accessTime: '全天时段有效',
authTypes: [
{ name: '人员', icon: '/static/images/icon_person.png' },
{ name: '指纹', icon: '/static/images/icon_fingerprint.png' }
]
},
{
id: 'CY01069559',
name: '4',
company: '19104656的互联/test',
validityPeriod: '2000-01-01至2037-12-31',
doorPoint: 'DS-K(L40959329)',
accessTime: '全天时段有效',
authTypes: [
{ name: '人员', icon: '/static/images/icon_person.png' },
{ name: '指纹', icon: '/static/images/icon_fingerprint.png' }
]
}
])
const handleCheckClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/access-right-detection'
})
}
const handleQueryClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/access-permission-inquiry'
})
}
const handlePersonItemClick = (person: Person) => {
uni.navigateTo({
url: `/pages/personnel-passage/AccessManage/delivery-status?id=${person.id}`
})
}
//
const handleAddBtnClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/add-permission-group'
})
}
//
const handlePublishBtnClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/send-permission'
})
}
//
const handleRecordBtnClick = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/release-record'
})
}
</script>
<style lang="scss" scoped>
.query-container {
box-sizing: border-box;
padding: 0 16px;
padding-bottom: 63px;
background-color: #f5f6fa;
}
.search-tip {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
margin-top: 12px;
background-color: #fff7f2;
border-radius: 8px;
.check-btn {
padding: 2px 8px;
font-size: 14px;
color: #333;
background-color: #fff;
border-radius: 4px;
}
}
.query-entry {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-top: 12px;
background-color: #fff;
border-radius: 8px;
.entry-title {
font-size: 16px;
color: #333;
}
.arrow {
width: 16px;
height: 16px;
}
}
.person-list {
margin-top: 12px;
.person-item {
position: relative;
padding: 16px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.person-info {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 4px;
.person-name {
font-size: 16px;
font-weight: 500;
color: #333;
}
.person-id {
font-size: 14px;
color: #999;
}
}
.company {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #666;
}
.validity {
margin-bottom: 12px;
font-size: 14px;
.label {
color: #666;
}
.value {
color: #333;
}
}
.access-info {
padding: 12px;
margin-bottom: 12px;
background-color: #f8f9fc;
border-radius: 8px;
.door-info,
.time-info {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 14px;
color: #666;
&:last-child {
margin-bottom: 0;
}
.icon {
width: 16px;
height: 16px;
margin-right: 4px;
}
.time-tag {
padding: 2px 8px;
margin-left: 4px;
font-size: 12px;
color: #2b5cff;
background-color: #eef4ff;
border-radius: 4px;
}
}
}
.auth-types {
display: flex;
gap: 16px;
.type-item {
display: flex;
gap: 4px;
align-items: center;
font-size: 14px;
color: #666;
.type-icon {
width: 16px;
height: 16px;
}
}
}
.item-arrow {
position: absolute;
right: 16px;
bottom: 16px;
width: 16px;
height: 16px;
}
}
}
.bottom-buttons {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
padding-top: 12px;
background-color: #fff;
border-top: 1px solid #eee;
.btn {
flex: 1;
height: 44px;
font-size: 14px;
// &::after {
// border: 1px solid #eee;
// border-radius: 0;
// }
}
.add-btn {
color: #2b5cff;
background-color: #fff;
}
.publish-btn {
color: #2b5cff;
background-color: #fff;
}
.record-btn {
color: #2b5cff;
background-color: #fff;
}
}
</style>

View File

@ -0,0 +1,186 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="下发记录" />
<scroll-view
class="record-container"
:scroll-y="true"
:style="{ height: 'calc(100vh - var(--status-bar-height) - 44px)' }"
>
<view class="record-list">
<view class="record-item" v-for="record in records" :key="record.time">
<view class="record-header">
<view class="title-wrap">
<text class="title">{{ record.title }}</text>
<text class="time">{{ record.time }}</text>
</view>
<view class="status-wrap" @click="handleRecordClick(record)">
<text class="status">{{ record.status }}</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<view class="record-stats">
<view class="stat-item">
<text class="value">{{ record.success }}</text>
<text class="label">成功</text>
</view>
<view class="stat-item">
<text class="value">{{ record.fail }}</text>
<text class="label">失败</text>
</view>
<view class="stat-item">
<text class="value">{{ record.total }}</text>
<text class="label">总数</text>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
interface Record {
title: string
time: string
status: string
success: number
fail: number
total: number
}
const records = ref<Record[]>([
{
title: '系统自动操作下发',
time: '2025-01-16 14:01:41',
status: '已完成',
success: 0,
fail: 4,
total: 4
},
{
title: '杨操作下发',
time: '2025-01-16 09:47:06',
status: '已完成',
success: 0,
fail: 4,
total: 4
},
{
title: '系统自动操作下发',
time: '2025-01-16 06:01:13',
status: '已完成',
success: 0,
fail: 8,
total: 8
},
{
title: '系统自动操作下发',
time: '2025-01-15 06:01:12',
status: '已完成',
success: 0,
fail: 8,
total: 8
},
{
title: '系统自动操作下发',
time: '2025-01-14 15:42:37',
status: '已完成',
success: 0,
fail: 4,
total: 4
}
])
const handleRecordClick = (record: Record) => {
//
}
</script>
<style lang="scss" scoped>
.record-container {
padding: 12px 16px;
background-color: #f5f6fa;
}
.record-list {
.record-item {
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.title-wrap {
.title {
display: block;
margin-bottom: 4px;
font-size: 16px;
color: #333;
}
.time {
font-size: 14px;
color: #999;
}
}
.status-wrap {
display: flex;
gap: 4px;
align-items: center;
.status {
font-size: 14px;
color: #2b5cff;
}
.arrow {
width: 16px;
height: 16px;
}
}
}
.record-stats {
display: flex;
justify-content: space-around;
padding: 12px;
background-color: #f8f9fc;
border-radius: 8px;
.stat-item {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
.value {
font-size: 20px;
font-weight: 500;
color: #333;
}
.label {
font-size: 14px;
color: #666;
}
}
}
}
}
</style>

View File

@ -0,0 +1,214 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="下发权限">
<template #right>
<view class="timing-btn" @click="handleTimingClick">
<image class="icon" src="/static/images/icon_timing.png" mode="aspectFit" />
<text>定时下发</text>
</view>
</template>
</TopNavigation>
<view class="send-container">
<!-- 下发选项 -->
<view class="form-item">
<text class="label">下发方式</text>
<view class="select">
<text>普通下发</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<view class="form-item">
<text class="label">门禁范围</text>
<view class="select">
<text>全选</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 提示说明 -->
<view class="tips">
<view class="tips-header">
<image class="icon" src="/static/images/icon_gray_tip.png" mode="aspectFit" />
<text class="title">提示</text>
</view>
<view class="tips-content">
<view class="tips-list">
<view class="tip-item">
<text>1.</text>
<text>仅可选择已配置过通行权限的门禁进行下发</text>
</view>
<view class="tip-item">
<text>2.</text>
<text>下发信息过多会影响人员通行体验建议避开人流量较大时间错峰下发</text>
</view>
<view class="tip-item">
<text>3.</text>
<text>
设备数据与平台数据不一致时可选择[全量下发]将删除设备本地数据后重新下发平台数据
</text>
</view>
<view class="tip-item">
<text>4.</text>
<text>
通行时间仅型号为DS-K1TD1或HST-AU开头的门禁设备有效其余型号仅支持全天时段有效
</text>
</view>
</view>
<view class="extra-tips">
<text>
新添加或修改权限组需点击[立即下发]将配置的通行凭证下发到对应门禁点成功后可生效通行权限
</text>
<text>
若权限组已按照组织/分组/楼幢进行人员配置则后续在对应的组织/分组/楼幢中加入人员将自动下发新增的通行凭证无需手动下发
</text>
</view>
</view>
</view>
</view>
<!-- 底部按钮 -->
<button class="send-btn">立即下发</button>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const handleTimingClick = () => {
//
}
</script>
<style lang="scss" scoped>
.send-container {
min-height: 100vh;
padding: 12px 16px;
background-color: #f5f6fa;
}
.timing-btn {
display: flex;
gap: 4px;
align-items: center;
padding: 4px 8px;
font-size: 14px;
color: #333;
.icon {
width: 16px;
height: 16px;
}
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.label {
font-size: 14px;
color: #333;
}
.select {
display: flex;
gap: 4px;
align-items: center;
font-size: 14px;
color: #333;
.arrow {
width: 16px;
height: 16px;
}
}
}
.tips {
padding: 16px;
background-color: #fff;
border-radius: 8px;
.tips-header {
display: flex;
align-items: center;
margin-bottom: 8px;
.icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
.title {
font-size: 16px;
font-weight: 500;
color: #333;
}
}
.tips-content {
.tips-list {
padding-left: 4px;
.tip-item {
display: flex;
gap: 4px;
margin-bottom: 8px;
font-size: 14px;
line-height: 1.4;
color: #666;
&:last-child {
margin-bottom: 12px;
}
text:last-child {
flex: 1;
}
}
}
.extra-tips {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
font-size: 14px;
line-height: 1.4;
color: #666;
background-color: #f5f6fa;
border-radius: 8px;
}
}
}
.send-btn {
position: fixed;
right: 16px;
bottom: 16px;
left: 16px;
height: 44px;
font-size: 16px;
color: #fff;
background-color: #2b5cff;
border-radius: 8px;
&::after {
border: none;
}
}
</style>

View File

@ -0,0 +1,231 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="日常通行计划" />
<view class="plan-container">
<!-- 周时间选择 -->
<view class="week-section">
<text class="required">*</text>
<text class="title">周时间选择</text>
<view class="week-list">
<view
v-for="(day, index) in weekDays"
:key="index"
:class="['week-item', { active: day.active }]"
>
{{ day.label }}
</view>
</view>
</view>
<!-- 通行时段 -->
<view class="time-section">
<view class="section-header">
<text>通行时段</text>
<text class="sub-text">未添加通行时段表示全天不可通行</text>
</view>
<!-- 时段列表 -->
<view class="time-list">
<view class="time-item" v-for="(period, index) in timePeriods" :key="index">
<view class="time-info">
<text class="period-label">时段{{ index + 1 }}</text>
<text class="time-value">{{ period.time }}</text>
</view>
<view class="action-icons">
<image
class="gray-delet-icon"
src="/static/images/icon_gray_delet.png"
mode="scaleToFill"
@click="deletAction"
/>
</view>
</view>
</view>
<!-- 添加时段按钮 -->
<view class="add-time">
<image class="add-icon" src="/static/images/icon_blue_add.png" mode="aspectFit" />
<text>增加通行时段</text>
</view>
</view>
</view>
<!-- 底部保存按钮 -->
<button class="save-btn">保存</button>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const weekDays = ref([
{ label: '一', active: true },
{ label: '二', active: true },
{ label: '三', active: true },
{ label: '四', active: true },
{ label: '五', active: true },
{ label: '六', active: false },
{ label: '日', active: false }
])
const timePeriods = ref([{ time: '08:00:00-12:00:00' }, { time: '14:00:00-18:00:00' }])
</script>
<style lang="scss" scoped>
.plan-container {
padding: 12px 0;
// margin-right: 16px;
background-color: #f5f6fa;
}
.week-section {
padding: 12px;
margin-bottom: 12px;
background-color: #fff;
.required {
margin-right: 4px;
font-size: 14px;
color: #ff4d4f;
}
.title {
font-size: 14px;
color: #333;
}
.week-list {
display: flex;
gap: 8px;
justify-content: space-between;
width: 100%;
margin-top: 12px;
.week-item {
display: flex;
align-items: center;
justify-content: center;
width: calc((100% - 48px) / 7);
aspect-ratio: 1;
font-size: 16px;
color: #333;
background-color: #fff;
border: 1px dashed #ddd;
border-radius: 50%;
&.active {
color: #fff;
background-color: #2b5cff;
border-style: solid;
}
}
}
}
.time-section {
padding: 12px 16px;
background-color: #fff;
.section-header {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: #333;
.sub-text {
margin-left: 8px;
font-size: 12px;
color: #999;
}
}
.time-list {
.time-item {
display: flex;
align-items: stretch;
justify-content: space-between;
margin-bottom: 12px;
.time-info {
display: flex;
flex: 1;
gap: 16px;
align-items: center;
height: 44px;
padding: 0 16px;
margin-bottom: 0;
background-color: #f8f9fc;
border-radius: 8px;
.period-label {
font-size: 14px;
color: #999;
}
.time-value {
font-size: 14px;
color: #333;
}
}
.action-icons {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin-left: 12px;
.gray-delet-icon {
width: 25px;
height: 25px;
}
}
}
}
.add-time {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
padding: 16px;
background-color: #f8f9fc;
border-radius: 8px;
.add-icon {
width: 16px;
height: 16px;
}
text {
font-size: 14px;
color: #2b5cff;
}
}
}
.save-btn {
position: fixed;
right: 16px;
bottom: 16px;
left: 16px;
height: 44px;
font-size: 16px;
color: #fff;
background-color: #2b5cff;
border-radius: 8px;
&::after {
border: none;
}
}
</style>

View File

@ -0,0 +1,313 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="增加时间计划" />
<scroll-view scroll-y class="time-schedule">
<view class="content">
<!-- 计划名称 -->
<view class="form-item">
<view class="label">
<text class="required">*</text>
<text>计划名称</text>
</view>
<input
class="input"
type="text"
placeholder="请填写计划名称"
placeholder-style="color: #999;"
/>
</view>
<!-- 计划描述 -->
<view class="form-item">
<view class="label">计划描述</view>
<textarea
class="textarea"
placeholder="非必填"
placeholder-style="color: #999;"
maxlength="50"
/>
<view class="word-count">0/50</view>
</view>
<!-- 日常通行计划 -->
<view class="form-item">
<view class="label">
<text class="required">*</text>
<text>日常通行计划</text>
</view>
<view class="time-block-main">
<view class="time-block">
<view class="week-select">
<text>周一至周五</text>
<!-- <uni-icons type="right" size="16" color="white"></uni-icons> -->
<image
class="right-icon"
src="/static/images/icon_black_right.png"
mode="scaleToFill"
/>
</view>
<view class="time-periods">
<view class="time-item">
<text>时段1</text>
<text class="time">08:00:00-12:00:00</text>
<view class="delete-icon">
<uni-icons type="trash" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="time-item">
<text>时段2</text>
<text class="time">14:00:00-16:00:00</text>
<view class="delete-icon">
<uni-icons type="trash" size="20" color="#999"></uni-icons>
</view>
</view>
</view>
</view>
<image
class="gray_delet-icon"
src="/static/images/icon_gray_delet.png"
mode="scaleToFill"
@click="deletAction"
/>
</view>
<view class="add-time" @click="addTimeSlot">
<!-- <uni-icons type="plusempty" size="16" color="#2979ff"></uni-icons> -->
<image class="add-icon" src="/static/images/icon_blue_add.png" mode="scaleToFill" />
<text>添加日常通行计划</text>
</view>
</view>
<!-- 节假日通行计划 -->
<view class="form-item">
<view class="holiday-header">
<text>节假日通行计划</text>
<text class="sub-text">遇节假日将按假日计划执行</text>
</view>
<view class="holiday-select" @click="selectHoliday">
<text>法定节假日</text>
<image
class="holiday-select-right-icon"
src="/static/images/icon_black_right.png"
mode="scaleToFill"
/>
</view>
</view>
<!-- 底部占位为fixed按钮留出空间 -->
<view class="footer-placeholder"></view>
</view>
</scroll-view>
<!-- 底部保存按钮 -->
<view class="footer">
<button class="save-btn" type="primary">保存</button>
</view>
</template>
<style lang="scss" scoped>
.time-schedule {
box-sizing: border-box;
height: calc(100vh - 44px); //
background-color: #f5f6fa;
.content {
padding: 12px 16px;
.form-item {
padding: 16px;
margin-bottom: 16px;
background-color: #fff;
border-radius: 8px;
.label {
margin-bottom: 8px;
font-size: 14px;
.required {
margin-right: 4px;
color: #ff4d4f;
}
}
.input {
// width: 100%;
height: 40px;
padding: 0 12px;
font-size: 14px;
background-color: #f8f9fc;
border-radius: 4px;
}
.textarea {
box-sizing: border-box;
height: 80px;
padding: 8px 12px;
margin-right: 32px;
font-size: 14px;
background-color: #f8f9fc;
border-radius: 4px;
}
.word-count {
margin-top: 4px;
font-size: 12px;
color: #999;
text-align: right;
}
}
.time-block-main {
display: flex;
gap: 12px;
align-items: center;
margin-top: 8px;
background-color: #fff;
border-radius: 8px;
.time-block {
flex: 1;
padding: 16px;
background-color: #f5f6fa;
border-radius: 8px;
.week-select {
display: flex;
align-items: center;
justify-content: space-between;
width: fit-content;
min-width: 120px;
padding: 12px 16px;
background-color: #f5f6fa;
border-radius: 8px;
.right-icon {
width: 16px;
height: 16px;
margin-left: 8px;
}
}
.time-periods {
padding: 12px 0;
.time-item {
display: flex;
align-items: center;
margin-bottom: 16px;
color: #666;
&:last-child {
margin-bottom: 0;
}
.time {
flex: 1;
margin-left: 16px;
}
}
}
}
.gray_delet-icon {
width: 30px;
height: 30px;
cursor: pointer;
}
}
.add-time {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
padding: 12px;
margin-top: 12px;
font-size: 14px;
color: #2979ff;
background-color: #f8f9fc;
border-radius: 4px;
.add-icon {
width: 16px;
height: 16px;
}
text {
font-size: 16px;
color: #2979ff;
}
}
.holiday-header {
display: flex;
align-items: center;
margin-bottom: 12px;
.sub-text {
margin-left: 8px;
font-size: 12px;
color: #999;
}
}
.holiday-select {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
background-color: #f8f9fc;
border-radius: 4px;
.holiday-select-right-icon {
width: 16px;
height: 16px;
}
}
}
.footer-placeholder {
height: 76px; //
}
}
.footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
padding: 16px;
background-color: #f5f6fa;
.save-btn {
width: 100%;
height: 44px;
font-size: 16px;
line-height: 44px;
color: #fff;
background-color: #2979ff;
border-radius: 22px;
}
}
</style>
<script setup>
const addTimeSlot = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/time-planning-allocation/add-daily-traffic-plan'
})
}
const deletAction = () => {
console.log('删除')
}
</script>

View File

@ -0,0 +1,136 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="计划1" />
<scroll-view
class="plan-container"
:scroll-y="true"
:style="{ height: 'calc(100vh - var(--status-bar-height) - 44px - 76px)' }"
>
<view class="tip-box">
<text>如需修改请在通行权限-通行时间计划功能内修改</text>
</view>
<!-- 计划名称 -->
<view class="form-item">
<input
class="input"
type="text"
v-model="planName"
placeholder="我路走里呀我路走里哇路在"
placeholder-class="placeholder"
/>
</view>
<!-- 日常通行时间 -->
<view class="time-section">
<text class="section-title">日常通行时间</text>
<view class="time-card">
<text class="week-text">周一至周五</text>
<view class="time-row">
<text class="time-label">时段1</text>
<text class="time-value">08:00:00-18:00:00</text>
</view>
<view class="time-row">
<text class="time-label">时段2</text>
<text class="time-value">20:00:00-23:59:00</text>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const planName = ref('')
</script>
<style lang="scss" scoped>
.plan-container {
padding: 12px 16px;
background-color: #f5f6fa;
}
.tip-box {
padding: 12px 16px;
margin-right: 32px;
margin-bottom: 12px;
font-size: 14px;
color: #ff6b00;
background-color: #fff6f0;
border-radius: 8px;
}
.form-item {
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
.input {
width: 100%;
height: 24px;
font-size: 14px;
color: #333;
}
.placeholder {
color: #999;
}
}
.time-section {
margin-right: 32px;
.section-title {
display: block;
margin-bottom: 12px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.time-card {
padding: 16px;
background-color: #fff;
border-radius: 8px;
.week-text {
display: block;
margin-bottom: 12px;
font-size: 14px;
color: #333;
}
.time-row {
display: flex;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.time-label {
width: 60px;
font-size: 14px;
color: #999;
}
.time-value {
flex: 1;
font-size: 14px;
color: #333;
}
}
}
}
</style>

View File

@ -0,0 +1,157 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="节假日计划配置" />
<scroll-view
class="plan-container"
:scroll-y="true"
:style="{ height: 'calc(100vh - var(--status-bar-height) - 44px - 76px)' }"
>
<!-- 添加计划 -->
<view class="plan-item add-plan" @click="handleAddPlan">
<text class="title">增加时间计划</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
<!-- 计划列表 -->
<view class="plan-list">
<!-- 法定节假日 -->
<view class="plan-item" @click="handlePlanClickDetail">
<view class="checkbox-wrap">
<checkbox class="checkbox" :checked="false" color="#2B5CFF" />
</view>
<view class="content" @click="handlePlanClick">
<text class="title">法定节假日</text>
<text class="desc">每年自动同步国家法定节假日通行时段可自定义</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<!-- 浓的 -->
<view class="plan-item" @click="handlePlanClickDetail">
<view class="checkbox-wrap">
<checkbox class="checkbox" :checked="false" color="#2B5CFF" />
</view>
<view class="content" @click="handlePlanClick">
<text class="title">浓的</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
</view>
</scroll-view>
<!-- 底部按钮 -->
<button class="save-btn">保存</button>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const handleAddPlan = () => {
//
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/time-planning-allocation/add-time-schedule'
})
}
const handlePlanClick = () => {
//
}
//
const handlePlanClickDetail = () => {
uni.navigateTo({
url: '/pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-detail'
})
}
</script>
<style lang="scss" scoped>
.plan-container {
padding: 12px 16px;
background-color: #f5f6fa;
}
.plan-item {
display: flex;
align-items: center;
padding: 16px;
margin-right: 32px;
margin-bottom: 12px;
background-color: #fff;
border-radius: 8px;
&.add-plan {
justify-content: space-between;
.title {
font-size: 16px;
color: #333;
}
.arrow {
width: 16px;
height: 16px;
}
}
.checkbox-wrap {
margin-right: 8px;
.checkbox {
transform: scale(0.8);
}
}
.content {
position: relative;
flex: 1;
.title {
display: block;
margin-bottom: 4px;
font-size: 16px;
color: #333;
}
.desc {
display: block;
margin-right: 16px;
font-size: 14px;
color: #999;
}
.arrow {
position: absolute;
top: 50%;
right: 0;
width: 16px;
height: 16px;
transform: translateY(-50%);
}
}
}
.save-btn {
position: fixed;
right: 16px;
bottom: 16px;
left: 16px;
height: 44px;
font-size: 16px;
color: #fff;
background-color: #2b5cff;
border-radius: 8px;
&::after {
border: none;
}
}
</style>

View File

@ -0,0 +1,220 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom',
disableScroll: false
}
}
</route>
<template>
<TopNavigation title="查看记录" />
<view class="records-container">
<!-- 人员信息 -->
<view class="person-info">
<text class="name">HikMall_30013234</text>
<text class="company">19104656的互联</text>
<!-- 门禁信息 -->
<view class="door-info">
<text class="label">门禁</text>
<text class="value">DS-K(L40959329)</text>
</view>
</view>
<!-- 记录列表 -->
<view class="records-list">
<!-- 记录项 -->
<view class="record-item" v-for="(item, index) in records" :key="index">
<text class="time">{{ item.time }}</text>
<view class="record-content">
<view class="content-header">
<text class="title">{{ item.title }}</text>
<text :class="['status', item.status === '下发失败' ? 'error' : 'success']">
{{ item.status }}
</text>
</view>
<view class="operation-info">
<image class="icon" src="/static/images/icon_operation.png" mode="aspectFit" />
<text class="label">权限操作</text>
<text class="value">{{ item.operation }}</text>
</view>
<view v-if="item.error" class="error-info">
<image class="icon" src="/static/images/icon_info.png" mode="aspectFit" />
<text class="label">失败原因</text>
<text class="value">{{ item.error }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
const records = [
{
time: '2025-01-16 06:01:13',
title: '指纹1',
status: '下发失败',
operation: '下发权限',
error: '设备离线,请稍后重试'
},
{
time: '2025-01-15 06:01:12',
title: '指纹1',
status: '下发失败',
operation: '下发权限',
error: '设备离线,请稍后重试'
},
{
time: '2025-01-14 06:01:17',
title: '指纹1',
status: '下发失败',
operation: '下发权限',
error: '设备离线,请稍后重试'
},
{
time: '2025-01-13 11:22:18',
title: '人员信息',
status: '下发成功',
operation: '下发权限'
},
{
time: '2025-01-15 06:01:12',
title: '指纹1',
status: '下发失败',
operation: '下发权限',
error: '设备离线,请稍后重试'
},
{
time: '2025-01-15 06:01:12',
title: '指纹1',
status: '下发失败',
operation: '下发权限',
error: '设备离线,请稍后重试'
}
]
</script>
<style lang="scss" scoped>
.records-container {
min-height: 100vh;
padding: 12px 16px;
background-color: #f5f6fa;
}
.person-info {
padding: 16px;
margin-bottom: 10px;
background-color: #fff;
border-radius: 8px;
.name {
display: block;
margin-bottom: 4px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.company {
font-size: 14px;
color: #666;
}
}
.door-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
margin-top: 12px;
background-color: #f8f9fc;
border-radius: 8px;
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
}
}
.records-list {
.record-item {
margin-bottom: 12px;
.time {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #666;
}
.record-content {
padding: 16px;
background-color: #fff;
border-radius: 8px;
.content-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.title {
font-size: 14px;
color: #333;
}
.status {
font-size: 14px;
&.success {
color: #52c41a;
}
&.error {
color: #ff4d4f;
}
}
}
.operation-info,
.error-info {
display: flex;
gap: 4px;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.icon {
width: 16px;
height: 16px;
}
.label {
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
}
}
}
}
}
</style>

View File

@ -9,8 +9,19 @@
<template>
<view class="passage-container">
<!-- 替换原来的 banner 部分为 AccessSwiper -->
<AccessSwiper class="mt-4" />
<TopNavigation title="门禁授权" />
<view class="visitor-card">
<view class="card-content">
<view class="card-left">
<text class="card-title">访客人员临时通行</text>
<text class="card-desc">使用访客管理来访轻松搞定</text>
</view>
<!-- <view class="card-right">
<image src="/static/images/visitor-illustration.png" mode="aspectFit" />
</view> -->
</view>
</view>
<!-- 权限组快捷入口 -->
<view class="quick-actions">
@ -30,6 +41,38 @@
</view>
</view>
<view class="remind-card">
<view class="card-header">
<view class="header-left">
<image class="user-icon" src="/static/images/icon_user.png" mode="aspectFit" />
<text class="user-count">6</text>
</view>
<text class="check-text">查看未同步信息 ></text>
</view>
<view class="stats-container">
<view class="stat-item">
<text class="stat-num">4</text>
<text class="stat-label">人员/通行规则</text>
</view>
<view class="stat-item">
<text class="stat-num">0</text>
<text class="stat-label">人脸</text>
</view>
<view class="stat-item">
<text class="stat-num">7</text>
<text class="stat-label">指纹</text>
</view>
<view class="stat-item">
<text class="stat-num">0</text>
<text class="stat-label">卡片</text>
</view>
<view class="stat-item">
<text class="stat-num">4</text>
<text class="stat-label">密码</text>
</view>
</view>
</view>
<!-- 人员管理 -->
<view class="feature-item">
<view class="feature-left">
@ -38,46 +81,26 @@
src="https://file.hikmall.com/prod/image/e75144eac8984ee198ef533c2f9d3558.png"
/>
<view class="feature-text">
<text class="feature-title">人员管理</text>
<text class="feature-desc">团队管理内录入人员信息/人脸/指纹/</text>
<text class="feature-title">人员信息录入</text>
<text class="feature-desc">团队管理录入人员信息及通行凭证信息</text>
</view>
</view>
<image class="arrow" src="/static/images/icon_black_right.png" />
</view>
<!-- 通行权限组 -->
<view class="access-authority-group">
<view class="feature-item">
<view class="feature-left">
<image
class="feature-icon"
src="https://file.hikmall.com/prod/image/cf46656ec73b404891e2b46b57ff5fc2.png"
/>
<view class="feature-text">
<text class="feature-title">通行权限组</text>
<text class="feature-desc">配置人员在某段时间能开某个门</text>
</view>
</view>
<image class="arrow" src="/static/images/icon_black_right.png" />
</view>
<!-- 时间计划和门禁点分组 -->
<view class="sub-features">
<view class="sub-item">
<image
class="sub-icon"
src="https://file.hikmall.com/prod/image/0526d084da4a49579f832dd4588dcb16.png"
/>
<text>通行时间计划</text>
</view>
<view class="sub-item">
<image
class="sub-icon"
src="https://file.hikmall.com/prod/image/7ea5f88d404442eeb958fbe0904777d1.png"
/>
<text>门禁点分组</text>
<view class="feature-item">
<view class="feature-left">
<image
class="feature-icon"
src="https://file.hikmall.com/prod/image/2887bb1c453244a2a872ec98c0360478.png"
/>
<view class="feature-text">
<text class="feature-title">门禁权限组管理</text>
<text class="feature-desc">配置人员通行的门与时间规则</text>
</view>
</view>
<image class="arrow" src="/static/images/icon_black_right.png" />
</view>
<!-- 权限查询 -->
@ -111,31 +134,6 @@
</view>
</view>
<!-- 底部导航 -->
<view class="tab-bar">
<view class="tab-item active">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png"
/>
<text>通行权限</text>
</view>
<view class="tab-item" @click="navigateTo('access-control')">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png"
/>
<text>门禁控制</text>
</view>
<view class="tab-item" @click="navigateTo('traffic-record')">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/885eb6ac104e4a76941e67eb738142ec.png"
/>
<text>通行记录</text>
</view>
</view>
<!-- 根据当前组件加载 -->
<component :is="currentComponent" />
</template>
@ -170,40 +168,35 @@
}
}
.banner {
padding: 20px;
margin-bottom: 16px;
.visitor-card {
height: 100%;
padding: 16px 16px;
margin-top: 12px;
background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
border-radius: 12px;
.banner-content {
.card-content {
display: flex;
align-items: center;
align-items: start;
justify-content: space-between;
height: 100%;
color: #fff;
.banner-text {
.card-left {
display: flex;
flex-direction: column;
gap: 8px;
gap: 4px;
.banner-title {
.card-title {
font-size: 18px;
font-weight: bold;
font-weight: 500;
}
.banner-desc {
font-size: 14px;
.card-desc {
font-size: 10px;
opacity: 0.8;
}
}
.banner-btn {
padding: 6px 12px;
font-size: 14px;
background: rgba(255, 255, 255, 0.2);
border-radius: 16px;
}
}
}
@ -271,6 +264,73 @@
}
}
.remind-card {
height: 100%;
padding: 12px 16px;
margin-bottom: 12px;
color: #fff;
background-color: white;
border-radius: 12px;
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.header-left {
display: flex;
gap: 4px;
align-items: center;
padding: 4px 8px;
background-color: #f5f6fa;
border-radius: 5px;
.user-icon {
width: 16px;
height: 16px;
}
.user-count {
font-size: 16px;
font-weight: 500;
color: #333;
}
}
.check-text {
font-size: 14px;
color: #333;
opacity: 0.8;
}
}
.stats-container {
display: flex;
justify-content: space-between;
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
.stat-num {
margin-bottom: 4px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.stat-label {
font-size: 11px;
color: #333;
white-space: nowrap;
opacity: 0.8;
}
}
}
}
//.feature-list {
// padding: 4px 0;
// background-color: #fff;
@ -352,36 +412,4 @@
}
}
}
.tab-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: space-around;
padding: 8px 0;
background-color: #fff;
border-top: 1px solid #f5f5f5;
.tab-item {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
.tab-icon {
width: 24px;
height: 24px;
}
text {
font-size: 12px;
}
&.active {
color: #2b5cff;
}
}
}
</style>

View File

@ -0,0 +1,147 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="一键开门" />
<view class="container">
<view class="search-box">
<image class="search-icon" src="/static/images/icon_search.png" mode="aspectFit" />
<input type="text" placeholder="输入门禁点名称搜索" placeholder-class="placeholder" />
</view>
<view class="device-list">
<view class="device-item">
<view class="device-left">
<text class="device-name">DS-K(FU6004429)</text>
<text class="favorite"> 收藏</text>
</view>
<view class="device-actions">
<view class="action-btn">
<image class="action-icon" src="/static/images/icon_table_menu.png" mode="aspectFit" />
</view>
<view class="action-btn">
<image class="action-icon" src="/static/images/icon_table_menu.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="device-item">
<view class="device-left">
<text class="device-name">DS-K(L40959329)</text>
<text class="favorite"> 收藏</text>
</view>
<view class="device-actions">
<view class="action-btn">
<image class="action-icon" src="/static/images/icon_table_menu.png" mode="aspectFit" />
</view>
<view class="action-btn">
<image class="action-icon" src="/static/images/icon_table_menu.png" mode="aspectFit" />
</view>
</view>
</view>
</view>
<view class="bottom-tip">
<text>设备离线无法开门可使用蓝牙开门</text>
</view>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
padding: 0;
background-color: #f5f6fa;
}
.search-box {
display: flex;
align-items: center;
padding: 8px 12px;
margin: 16px;
background-color: #fff;
border-radius: 8px;
.search-icon {
width: 16px;
height: 16px;
margin-right: 8px;
}
input {
flex: 1;
font-size: 14px;
}
.placeholder {
color: #999;
}
}
.device-list {
padding: 0 12px;
.device-item {
display: flex;
justify-content: space-between;
padding: 16px 20px;
margin: 0 4px 12px;
background-color: #fff;
border-radius: 8px;
.device-left {
display: flex;
flex-direction: column;
gap: 4px;
.device-name {
font-size: 16px;
color: #333;
}
.favorite {
font-size: 14px;
color: #999;
}
}
.device-actions {
display: flex;
gap: 12px;
margin-right: -4px;
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background-color: #f5f5f5;
border-radius: 50%;
.action-icon {
width: 20px;
height: 20px;
}
}
}
}
}
.bottom-tip {
padding: 16px;
font-size: 12px;
color: #999;
text-align: center;
}
</style>

View File

@ -0,0 +1,146 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="密码开门" />
<view class="container">
<view class="password-list">
<view class="password-card">
<view class="help-icon">
<text>通行帮助</text>
<image src="/static/images/icon_white_tip.png" mode="aspectFit" />
</view>
<view class="password-display">
<text class="password">742523</text>
<image class="eye-icon" src="/static/images/icon_eye.png" mode="aspectFit" />
</view>
<view class="password-tip">*请妥善保管密码勿泄露给他人</view>
<view class="door-range">
<text>门禁范围</text>
<image src="/static/images/icon_white_right.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="bottom-button">
<button class="refresh-btn" @click="refreshPassword">重新生成密码</button>
</view>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
</script>
<style lang="scss" scoped>
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #f5f6fa;
}
.password-list {
padding: 16px;
}
.password-card {
position: relative;
margin-right: 0;
color: #fff;
background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
border-radius: 12px;
.help-icon {
display: flex;
gap: 4px;
align-items: center;
justify-content: flex-end;
padding: 15px 15px 15px 0;
font-size: 14px;
text {
font-size: 12px;
opacity: 0.8;
}
image {
width: 16px;
height: 16px;
}
}
.password-display {
display: flex;
align-items: center;
justify-content: center;
margin-top: 25px;
margin-bottom: 5px;
.password {
font-size: 40px;
font-weight: bold;
letter-spacing: 4px;
}
.eye-icon {
width: 24px;
height: 24px;
}
}
.password-tip {
margin-bottom: 32px;
font-size: 12px;
text-align: center;
opacity: 0.8;
}
.door-range {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
font-size: 14px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
text {
font-size: 12px;
}
image {
width: 16px;
height: 16px;
}
}
}
.bottom-button {
position: fixed;
right: 0;
bottom: 0;
left: 0;
padding: 16px;
.refresh-btn {
width: 100%;
height: 44px;
font-size: 16px;
line-height: 44px;
color: #fff;
background-color: #2b5cff;
border: none;
border-radius: 8px;
}
}
</style>

View File

@ -12,14 +12,11 @@
<TopNavigation :title="titleTab[curIndex]"></TopNavigation>
<scroll-view class="flex-1 box-border" scroll-y>
<view v-if="curIndex == 0">
<AccessAuthority />
<AccessManage />
</view>
<view v-else-if="curIndex == 1">
<AccessControl />
</view>
<view v-else-if="curIndex == 2">
<TrafficRecord />
</view>
</scroll-view>
</view>
<CustomTabBar :list="list" :default-index="0" @change="change"></CustomTabBar>
@ -27,24 +24,20 @@
<script lang="ts" setup>
// import { onLoad } from '@dcloudio/uni-app'
import AccessAuthority from './AccessAuthority/AccessAuthority.vue'
// import AccessAuthority from './AccessAuthority/AccessAuthority.vue'
import AccessManage from './AccessManage/AccessManage.vue'
import AccessControl from './AccessControl/AccessControl.vue'
import TrafficRecord from './TrafficRecord/TrafficRecord.vue'
import { TabBarItem } from '@/typings'
const pages = [AccessAuthority, AccessControl, TrafficRecord]
const titleTab = ['通行权限', '门禁控制', '通行记录']
// const pages = [AccessAuthority, AccessControl]
const titleTab = ['门禁管理', '门禁控制']
const list = ref<Array<TabBarItem>>([
{
title: '通行权限',
title: '门禁管理',
icon: 'home'
},
{
title: '门禁控制',
icon: 'notification'
},
{
title: '通行记录',
icon: 'setting'
}
])

View File

@ -1,6 +1,6 @@
<route lang="json5">
{
type: 'page',
layout: 'default',
style: {
navigationStyle: 'custom'
}
@ -8,9 +8,10 @@
</route>
<template>
<TopNavigation title="通行记录"></TopNavigation>
<view class="record-container">
<!-- 快捷功能区 -->
<view class="quick-actions">
<!-- <view class="quick-actions">
<view class="quick-item" style="background-color: #fff7f2">
<view class="quick-content">
<text class="quick-title">通行消息提醒</text>
@ -25,7 +26,7 @@
<image class="quick-icon" src="/static/images/icon_phone_unlock.png" mode="aspectFit" />
</view>
</view>
</view>
</view> -->
<!-- 时间筛选 -->
<view class="time-filter">
@ -80,32 +81,6 @@
</view>
</view>
</view>
<!-- 底部导航 -->
<view class="tab-bar">
<view class="tab-item">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png"
/>
<text>通行权限</text>
</view>
<view class="tab-item">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png"
/>
<text>门禁控制</text>
</view>
<view class="tab-item active">
<image
class="tab-icon"
src="https://file.hikmall.com/prod/image/885eb6ac104e4a76941e67eb738142ec.png"
/>
<text>通行记录</text>
</view>
</view>
<!-- </view>-->
</view>
</template>
@ -143,74 +118,74 @@
}
}
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 12px 0;
// .quick-actions {
// display: grid;
// grid-template-columns: 1fr 1fr;
// gap: 12px;
// margin: 12px 0;
.quick-item {
position: relative;
height: 60px;
padding: 12px;
border-radius: 8px;
// .quick-item {
// position: relative;
// height: 60px;
// padding: 12px;
// border-radius: 8px;
&:first-child {
background-color: #eef4ff;
// background: red;
.quick-icon {
background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
}
}
// &:first-child {
// background-color: #eef4ff;
// // background: red;
// .quick-icon {
// background: linear-gradient(135deg, #4080ff 0%, #2b5cff 100%);
// }
// }
&:last-child {
background-color: #fff7f2;
.quick-icon {
background: linear-gradient(135deg, #ff9853 0%, #ff7b30 100%);
}
}
// &:last-child {
// background-color: #fff7f2;
// .quick-icon {
// background: linear-gradient(135deg, #ff9853 0%, #ff7b30 100%);
// }
// }
.quick-content {
position: relative;
height: 100%;
// .quick-content {
// position: relative;
// height: 100%;
.quick-title {
position: relative;
z-index: 2;
font-size: 15px;
font-weight: 500;
color: #333;
}
// .quick-title {
// position: relative;
// z-index: 2;
// font-size: 15px;
// font-weight: 500;
// color: #333;
// }
.quick-desc {
position: relative;
z-index: 2;
display: block;
margin-top: 8px;
margin-right: 20px;
font-size: 11px;
color: #666;
}
// .quick-desc {
// position: relative;
// z-index: 2;
// display: block;
// margin-top: 8px;
// margin-right: 20px;
// font-size: 11px;
// color: #666;
// }
.quick-icon {
position: absolute;
top: 50%;
right: 0;
z-index: 1;
width: 45px;
height: 45px;
border-radius: 50%;
transform: translateY(-50%);
// .quick-icon {
// position: absolute;
// top: 50%;
// right: 0;
// z-index: 1;
// width: 45px;
// height: 45px;
// border-radius: 50%;
// transform: translateY(-50%);
image {
width: 100%;
height: 100%;
filter: brightness(0) invert(1);
}
}
}
}
}
// image {
// width: 100%;
// height: 100%;
// filter: brightness(0) invert(1);
// }
// }
// }
// }
// }
.time-filter {
display: flex;
@ -318,36 +293,4 @@
}
}
}
.tab-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: space-around;
padding: 8px 0;
background-color: #fff;
border-top: 1px solid #f5f5f5;
.tab-item {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
.tab-icon {
width: 24px;
height: 24px;
}
text {
font-size: 12px;
}
&.active {
color: #2b5cff;
}
}
}
</style>

View File

@ -0,0 +1,259 @@
<route lang="json5">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<TopNavigation title="访客邀约">
<text class="nav-right-text">生成记录</text>
</TopNavigation>
<view class="container">
<!-- 功能区域 -->
<view class="function-area">
<view class="function-item">
<text class="title">访客码</text>
<text class="desc">一键生成分享权限扫码通行</text>
</view>
<view class="function-item">
<text class="title">新建邀约</text>
<text class="desc">填写表单短信邀约灵活通行</text>
</view>
</view>
<!-- 访客通行凭证 -->
<view class="visitor-pass">
<view class="pass-header">
<image class="pass-icon" src="/static/images/visitor/icon_pass.png" mode="aspectFit" />
<text class="title">访客通行凭证</text>
<text class="help" @click="showHelp">如何生成访客密码</text>
</view>
<!-- 表单内容 -->
<view class="form-content">
<view class="form-item">
<text class="label">到访地址</text>
<view class="value">
<text>19104656的互联</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<view class="form-item">
<text class="label">通行方式</text>
<view class="value">
<text>二维码</text>
</view>
</view>
<view class="form-item required">
<text class="label">通行有效时段</text>
<view class="value">
<text>24小时内</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<view class="form-item required">
<text class="label">通行次数</text>
<view class="value">
<text>不限制</text>
<image class="arrow" src="/static/images/icon_black_right.png" mode="aspectFit" />
</view>
</view>
<text class="tip-text">限制访客在每个门禁点的通行次数</text>
<view class="form-item">
<text class="label">访客姓名</text>
<input class="input" type="text" placeholder="请输入" />
</view>
<view class="form-item">
<text class="label">访客手机号</text>
<input class="input" type="number" placeholder="请输入" />
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-bar">
<button class="submit-btn" @click="handleSubmit">新建邀约</button>
<button class="submit-btn" @click="handleSubmit">立即生成</button>
</view>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
import { ref } from 'vue'
const showHelp = () => {
//
}
const handleSubmit = () => {
//
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
padding: 12px 16px;
background-color: #f5f6fa;
}
.function-area {
display: flex;
gap: 12px;
margin: 0 32px 12px 0;
.function-item {
flex: 1;
padding: 16px;
background: #fff;
border-radius: 12px;
.title {
display: block;
margin-bottom: 4px;
font-size: 16px;
font-weight: 500;
color: #333;
}
.desc {
font-size: 12px;
color: #999;
}
}
}
.visitor-pass {
margin: 0 32px 0 0;
background: #fff;
border-radius: 10px;
.pass-header {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #f5f5f5;
.pass-icon {
width: 24px;
height: 24px;
margin-right: 5px;
}
.title {
flex: 1;
font-size: 16px;
font-weight: 500;
color: #333;
}
.help {
font-size: 14px;
color: #2b5cff;
}
}
.form-content {
padding: 0 16px;
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid #f5f5f5;
&.required .label::before {
margin-right: 4px;
color: #ff4d4f;
content: '*';
}
.label {
font-size: 16px;
color: #333;
}
.value {
display: flex;
gap: 8px;
align-items: center;
text {
font-size: 14px;
color: #999;
}
.arrow {
flex-shrink: 0;
width: 16px;
height: 16px;
}
}
.input {
flex: 1;
padding: 0 12px;
margin-right: 4px;
font-size: 14px;
color: #333;
text-align: right;
&::placeholder {
color: #999;
}
}
}
.tip-text {
display: block;
padding: 8px 0;
font-size: 12px;
color: #999;
}
}
}
.bottom-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 12px;
align-items: center;
padding: 8px 16px;
padding-bottom: calc(8px + constant(safe-area-inset-bottom));
padding-bottom: calc(8px + env(safe-area-inset-bottom));
background: #fff;
border-top: 1px solid #f5f5f5;
.submit-btn {
flex: 1;
height: 44px;
font-size: 16px;
line-height: 44px;
color: #fff;
text-align: center;
background: #2b5cff;
border-radius: 22px;
}
}
.nav-right-text {
padding: 4px 16px 4px 0;
margin-right: 16px;
font-size: 14px;
color: #333;
}
</style>

View File

@ -0,0 +1,239 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<view class="container">
<!-- 功能区域 -->
<view class="function-area">
<view class="function-item">
<image
class="icon"
src="https://file.hikmall.com/prod/image/635392a7f5e04e75bb657b6cc6e2abc8.png"
mode="aspectFit"
/>
<view class="text-wrapper">
<text class="title">访客码</text>
<text class="desc">分享刷码通行</text>
</view>
</view>
<view class="function-item">
<image
class="icon"
src="https://file.hikmall.com/prod/image/d1d07b3125f841848c3c3eb94509b2ae.png"
mode="aspectFit"
/>
<view class="text-wrapper">
<text class="title">新建邀约</text>
<text class="desc">短信邀约访客</text>
</view>
</view>
</view>
<!-- 访客列表筛选 -->
<view class="visitor-filter">
<text
v-for="(filter, index) in filters"
:key="index"
:class="['filter-item', { active: currentFilter === index }]"
@click="switchFilter(index)"
>
{{ filter }}
</text>
</view>
<!-- 记录列表 -->
<view class="record-list">
<view v-for="(record, index) in currentRecords" :key="index" class="record-item">
<view class="record-number-wrapper">
<text class="record-number">{{ record.number }}</text>
<text class="visit-time">预约到访时间{{ record.time }}</text>
</view>
<text class="status">{{ record.status }}</text>
</view>
</view>
<text class="data-tip">仅展示近6个月数据</text>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
import { ref, computed } from 'vue'
const filters = ref(['全部', '已到访', '待来访', '更多筛选'])
const currentFilter = ref(0)
// 访
const records = ref([
{
number: '3',
time: '今天 10:57',
status: '已失效',
type: 'all'
},
{
number: '12',
time: '昨天 18:07',
status: '已失效',
type: 'error'
},
{
number: '123',
time: '2024/12/17 10:17',
status: '已失效',
type: 'all'
}
])
//
const currentRecords = computed(() => {
switch (currentFilter.value) {
case 0: //
return records.value
case 1: // 访
return records.value.filter(record => record.type === 'visited')
case 2: // 访
return records.value.filter(record => record.type === 'pending')
case 3: //
return records.value
default:
return records.value
}
})
const switchFilter = index => {
currentFilter.value = index
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
// padding: 0 16px;
background-color: #f5f6fa;
}
.function-area {
display: flex;
gap: 12px;
padding: 16px;
margin: 12px 0;
background: #fff;
border-radius: 12px;
.function-item {
display: flex;
flex: 1;
gap: 12px;
align-items: center;
margin-right: 16px;
margin-left: 16px;
// padding: 8px 12px;
// background: #f8f9fc;
// border-radius: 8px;
.icon {
flex-shrink: 0;
width: 32px;
height: 32px;
}
.text-wrapper {
display: flex;
flex-direction: column;
gap: 2px;
.title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.desc {
font-size: 12px;
color: #999;
}
}
}
}
.visitor-filter {
display: flex;
gap: 16px;
padding: 12px 16px;
margin-right: 16px;
margin-left: 16px;
background: #fff;
border-radius: 12px 12px 0 0;
.filter-item {
padding-bottom: 4px;
font-size: 14px;
color: #666;
&.active {
color: #2b5cff;
border-bottom: 2px solid #2b5cff;
}
}
}
.record-list {
margin-right: 16px;
margin-left: 16px;
background: #fff;
border-radius: 0 0 12px 12px;
.record-item {
position: relative;
display: flex;
padding: 16px;
border-bottom: 1px solid #f5f5f5;
.record-number-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
.record-number {
font-size: 24px;
font-weight: 500;
line-height: 1;
color: #333;
}
.visit-time {
font-size: 12px;
color: #999;
white-space: nowrap;
}
}
.status {
position: absolute;
top: 16px;
right: 16px;
padding: 2px 8px;
font-size: 12px;
color: #999;
background: #f5f5f5;
border-radius: 10px;
}
}
}
.data-tip {
display: block;
padding-bottom: 16px;
margin-top: 16px;
font-size: 12px;
color: #999;
text-align: center;
}
</style>

View File

@ -0,0 +1,348 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<view class="container">
<!-- 顶部标签页 -->
<view class="tab-header">
<text
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-item', { active: currentTab === index }]"
@click="switchTab(index)"
>
{{ tab.name }}
</text>
</view>
<!-- 主要内容区域 -->
<view class="content">
<image class="illustration" :src="tabs[currentTab].image" mode="aspectFit" />
<text class="tip-text">{{ tabs[currentTab].tipText }}</text>
<button class="invite-btn">
<image src="/static/images/icon_arrow_right_white.png" mode="aspectFit" />
{{ tabs[currentTab].buttonText }}
</button>
</view>
<!-- 访客记录 -->
<view class="visitor-record">
<view class="record-header">
<view class="header-left">
<image src="/static/images/icon_record.png" mode="aspectFit" />
<text>访客记录</text>
</view>
<view class="header-right">
<text>一键下发</text>
<image src="/static/images/icon_send.png" mode="aspectFit" />
</view>
</view>
<!-- 记录筛选 -->
<view class="record-filter">
<text
v-for="(filter, index) in filters"
:key="index"
:class="['filter-item', { active: currentFilter === index }]"
@click="switchFilter(index)"
>
{{ filter.name }}
</text>
</view>
<!-- 记录列表 -->
<view class="record-list">
<view v-for="(record, index) in currentRecords" :key="index" class="record-item">
<view class="record-number-wrapper">
<text class="record-number">{{ record.number }}</text>
<text class="visit-time">预约到访时间{{ record.time }}</text>
</view>
<text class="status">{{ record.status }}</text>
</view>
</view>
<text class="data-tip">仅展示近6个月数据</text>
</view>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
import { ref, computed } from 'vue'
const currentTab = ref(0)
const tabs = ref([
{
name: '访客自助预约',
image: '/static/images/bg_no_device.webp',
tipText: '邀请访客自助填写预约信息',
buttonText: '去邀请'
},
{
name: '我要邀约访客',
image: '/static/images/bg_no_device.webp',
tipText: '填写访客信息,短信邀请访客来访',
buttonText: '去填写'
},
{
name: '访客签到',
image: '/static/images/bg_no_device.webp',
tipText: '扫描访客二维码,人工核验签到',
buttonText: '扫码核验'
}
])
const switchTab = index => {
currentTab.value = index
}
//
const filters = ref([
{ name: '全部', type: 'all' },
{ name: '下发异常', type: 'error' },
{ name: '更多筛选', type: 'more' }
])
const currentFilter = ref(0)
// 访
const records = ref([
{
number: '3',
time: '今天 10:57',
status: '已失效',
type: 'all'
},
{
number: '12',
time: '昨天 18:07',
status: '已失效',
type: 'error'
},
{
number: '123',
time: '2024/12/17 10:17',
status: '已失效',
type: 'all'
}
])
//
const currentRecords = computed(() => {
const filterType = filters.value[currentFilter.value].type
if (filterType === 'all') {
return records.value
}
return records.value.filter(record => record.type === filterType)
})
//
const switchFilter = index => {
currentFilter.value = index
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
// padding: 0 16px;
background-color: #f5f6fa;
}
.tab-header {
display: flex;
padding: 12px 0px;
margin-right: 16px;
margin-left: 16px;
background: #fff;
border-radius: 12px 12px 0 0;
.tab-item {
flex: 1;
padding: 8px 0;
font-size: 14px;
color: #666;
text-align: center;
&.active {
position: relative;
font-weight: 500;
color: #333;
&::after {
position: absolute;
bottom: -4px;
left: 50%;
width: 20px;
height: 2px;
content: '';
background-color: #2b5cff;
border-radius: 1px;
transform: translateX(-50%);
}
}
}
}
.content {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
margin-right: 16px;
margin-left: 16px;
background: #fff;
border-radius: 0 0 12px 12px;
.illustration {
width: 240px;
height: 160px;
margin-bottom: 16px;
// background-color: red;
}
.tip-text {
margin-bottom: 24px;
font-size: 14px;
color: #666;
}
.invite-btn {
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
width: calc(100% - 32px);
font-size: 16px;
color: #fff;
background: linear-gradient(90deg, #4080ff, #2b5cff);
border: none;
border-radius: 24px;
image {
width: 16px;
height: 16px;
}
}
}
.visitor-record {
// padding: 0 0 16px;
margin: 12px 16px 16px 16px;
background: #fff;
border-radius: 12px;
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
margin-top: 12px;
margin-bottom: 16px;
background-color: #efeefe;
border-radius: 12px 12px 0 0;
.header-left {
display: flex;
gap: 4px;
align-items: center;
image {
width: 20px;
height: 20px;
}
text {
font-size: 16px;
font-weight: 500;
}
}
.header-right {
display: flex;
gap: 4px;
align-items: center;
font-size: 14px;
color: #666;
image {
width: 16px;
height: 16px;
}
}
}
.record-filter {
display: flex;
gap: 16px;
padding: 0 16px;
margin-bottom: 16px;
.filter-item {
padding-bottom: 4px;
font-size: 14px;
color: #666;
&.active {
color: #2b5cff;
border-bottom: 2px solid #2b5cff;
}
}
}
.record-list {
.record-item {
position: relative;
display: flex;
padding: 16px;
border-bottom: 1px solid #f5f5f5;
.record-number-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
.record-number {
font-size: 24px;
font-weight: 500;
line-height: 1;
color: #333;
}
.visit-time {
font-size: 12px;
color: #999;
white-space: nowrap;
}
}
.status {
position: absolute;
top: 16px;
right: 16px;
padding: 2px 8px;
font-size: 12px;
color: #999;
background: #f5f5f5;
border-radius: 10px;
}
}
}
.data-tip {
display: block;
padding-bottom: 16px;
margin-top: 16px;
font-size: 12px;
color: #999;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,246 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<view class="container">
<!-- 顶部标签页 -->
<view class="tab-header">
<text
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-item', { active: currentTab === index }]"
@click="switchTab(index)"
>
{{ tab }}
</text>
</view>
<!-- 设置列表 -->
<view class="settings-list">
<!-- 访客自助预约设置 -->
<view class="setting-group">
<view class="setting-item">
<text class="label">访客自助预约</text>
<switch
:checked="settings.selfBooking"
@change="e => updateSetting('selfBooking', e.detail.value)"
color="#2b5cff"
/>
</view>
<view class="setting-item with-arrow">
<text class="label">预约设置</text>
<image class="arrow" src="/static/images/icon_arrow_right.png" mode="aspectFit" />
</view>
<view class="setting-item with-arrow">
<text class="label">被访人</text>
<view class="value">
<text>全部成员</text>
<image class="arrow" src="/static/images/icon_arrow_right.png" mode="aspectFit" />
</view>
</view>
<text class="tip-text">开启后您可提供预约码给访客访客微信扫码自助预约</text>
</view>
<!-- 其他设置项 -->
<view class="setting-group">
<view class="setting-item">
<text class="label">成员邀约访客</text>
<switch
:checked="settings.memberInvite"
@change="e => updateSetting('memberInvite', e.detail.value)"
color="#2b5cff"
/>
</view>
<view class="setting-item">
<text class="label">访客补录信息</text>
<switch
:checked="settings.infoSupply"
@change="e => updateSetting('infoSupply', e.detail.value)"
color="#2b5cff"
/>
</view>
<view class="setting-item with-arrow">
<text class="label">访问时长</text>
<view class="value">
<text>最大30天/默认2小时</text>
<image class="arrow" src="/static/images/icon_arrow_right.png" mode="aspectFit" />
</view>
</view>
<text class="bottom-tip">
开启后允许成员预约访客生效后系统将发送短信 自定义表单模板登录PC端
<text class="link">hikiot.com</text>
<text class="copy-icon">复制</text>
</text>
</view>
<view class="setting-group">
<view class="setting-item">
<text class="label">访客码</text>
<view class="label-with-icon">
<image class="info-icon" src="/static/images/icon_info.png" mode="aspectFit" />
<switch
:checked="settings.visitorCode"
@change="e => updateSetting('visitorCode', e.detail.value)"
color="#2b5cff"
/>
</view>
</view>
<text class="tip-text">开启后允许团队成员快速生成访客码提供访客通行</text>
</view>
</view>
</view>
</template>
<script setup>
import TopNavigation from '@/components/TopNavigation/TopNavigation.vue'
import { ref } from 'vue'
const tabs = ref(['预约方式', '到访通行', '审批设置'])
const currentTab = ref(0)
const settings = ref({
selfBooking: false,
memberInvite: false,
infoSupply: false,
visitorCode: false
})
const switchTab = index => {
currentTab.value = index
}
const updateSetting = (key, value) => {
settings.value[key] = value
}
</script>
<style scoped lang="scss">
.container {
min-height: 100vh;
background-color: #f5f6fa;
}
.tab-header {
display: flex;
padding: 12px 0;
margin: 12px 16px;
background: #fff;
border-radius: 12px;
.tab-item {
flex: 1;
padding: 8px 0;
font-size: 14px;
color: #666;
text-align: center;
&.active {
position: relative;
font-weight: 500;
color: #333;
&::after {
position: absolute;
bottom: -4px;
left: 50%;
width: 20px;
height: 2px;
content: '';
background-color: #2b5cff;
border-radius: 1px;
transform: translateX(-50%);
}
}
}
}
.settings-list {
margin: 0 16px;
.setting-group {
padding: 0 16px 12px 12px;
margin-bottom: 12px;
background: #fff;
border-radius: 12px;
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.label {
font-size: 16px;
color: #333;
}
.value {
display: flex;
gap: 4px;
align-items: center;
font-size: 14px;
color: #999;
}
.label-with-icon {
display: flex;
gap: 8px;
align-items: center;
.info-icon {
width: 16px;
height: 16px;
}
}
.arrow {
width: 16px;
height: 16px;
}
&.with-arrow {
cursor: pointer;
}
}
.bottom-tip {
display: block;
padding: 12px 0;
font-size: 12px;
color: #999;
text-align: center;
.link {
color: #2b5cff;
text-decoration: underline;
}
.copy-icon {
display: inline-block;
padding: 2px 4px;
margin-left: 4px;
color: #2b5cff;
background: rgba(43, 92, 255, 0.1);
border-radius: 4px;
}
}
.tip-text {
padding: 12px 0;
font-size: 12px;
color: #999;
}
}
}
</style>

View File

@ -0,0 +1,65 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<view class="h-[calc(100vh-50px)] flex flex-col">
<TopNavigation :title="titleTab[curIndex]"></TopNavigation>
<scroll-view class="flex-1 box-border" scroll-y>
<view v-if="curIndex == 0">
<VisitorManage />
</view>
<view v-else-if="curIndex == 1">
<MyVisitor />
</view>
<view v-else-if="curIndex == 2">
<VisitorSet />
</view>
</scroll-view>
</view>
<CustomTabBar :list="list" :default-index="0" @change="change"></CustomTabBar>
</template>
<script lang="ts" setup>
// import { onLoad } from '@dcloudio/uni-app'
import VisitorManage from './VisitorManage/VisitorManage.vue'
import MyVisitor from './MyVisitor/MyVisitor.vue'
import VisitorSet from './VisitorSet/VisitorSet.vue'
import { TabBarItem } from '@/typings'
const titleTab = ['访客管理', '我的访客', '访客设置']
const list = ref<Array<TabBarItem>>([
{
title: '首页',
icon: 'home'
},
{
title: '我的访客',
icon: 'notification'
},
{
title: '访客设置',
icon: 'setting'
}
])
const curIndex = ref(0)
// onLoad(options => {
// console.log(`11111${Number(options.curIndex)}`)
// curIndex.value = Number(options.curIndex)
// })
const change = (data: { value: number }) => {
curIndex.value = data.value
}
</script>
<style lang="scss" scoped>
page {
background-color: #f6f8fc;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -43,7 +43,25 @@ interface NavigateToOptions {
"/pages/attendance/attendance-add-group/attendance-staff" |
"/pages/attendance/attendance-add-group/attendance-time" |
"/pages/attendance/attendance-add-group/outside-rules" |
"/pages/attendance/attendance-add-group/special-date-set";
"/pages/attendance/attendance-add-group/special-date-set" |
"/pages/personnel-passage/AccessManage/access-permission-inquiry" |
"/pages/personnel-passage/AccessManage/access-right-detection" |
"/pages/personnel-passage/AccessManage/add-permission-group" |
"/pages/personnel-passage/AccessManage/delivery-status" |
"/pages/personnel-passage/AccessManage/permission-query" |
"/pages/personnel-passage/AccessManage/release-record" |
"/pages/personnel-passage/AccessManage/send-permission" |
"/pages/personnel-passage/AccessManage/viewing-delivery-records" |
"/pages/personnel-passage/access-authority/access-authority" |
"/pages/personnel-passage/one-click-open-door/one-click-open-door" |
"/pages/personnel-passage/password-open-door/password-open-door" |
"/pages/personnel-passage/traffic-record/traffic-record" |
"/pages/personnel-passage/visitor-invitation/visitor-invitation" |
"/pages/personnel-passage/visitor-manage/visitor-manage-tab" |
"/pages/personnel-passage/AccessManage/time-planning-allocation/add-daily-traffic-plan" |
"/pages/personnel-passage/AccessManage/time-planning-allocation/add-time-schedule" |
"/pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-detail" |
"/pages/personnel-passage/AccessManage/time-planning-allocation/time-planning-allocation-list";
}
interface RedirectToOptions extends NavigateToOptions {}