初始化

This commit is contained in:
hezhidong
2024-11-05 17:43:01 +08:00
parent 2a10e37c10
commit 4e1140419a
97 changed files with 8700 additions and 89 deletions
+51
View File
@@ -0,0 +1,51 @@
<template>
<router-view></router-view>
</template>
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
console.log(route)
</script>
<style lang="scss">
@import url('./assets/style/base.css');
html,body{
width: 100%;
height: 100%;
color: #222222;
}
#app {
width: 100%;
height: 100%;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
.box{
width: 100%;
height: 100%;
font-size: $size26;
}
.no-have{
line-height: 1.3333rem /* 100/75 */;
font-size: .4rem /* 30/75 */;
text-align: center;
}
}
.controls-fixed{
position: fixed;
right: 0;
bottom: 0;
width: 100%;
height: .8rem /* 60/75 */;
font-size: .4rem /* 30/75 */;
color: $main-color;
display: flex;
justify-content: center;
}
</style>
+32
View File
@@ -0,0 +1,32 @@
import request from "./adminRequest.js";
// 登录
export function getToken(authCode){
return request({
method: "get",
url: "/user/getToken?code="+authCode,
data: {
}
})
}
// 获取访客记录
export function getVisitorRecord(params ){
return request({
method: "get",
url: "/visitor/getVisitorRecordList",
params,
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 获取二维码
export function getWhiteCode( ){
return request({
method: "get",
url: "/qr/getCommonBlackWhiteCode",
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
+96
View File
@@ -0,0 +1,96 @@
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex';
import { Toast, Dialog } from 'vant';
// import { getItem, setItem, removeItem, dalert } from '@/com/index'
const errorCode = {
'401': '登录状态已过期,即将返回首页重新登录。',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}
const route = useRoute()
const router = useRouter()
const store = useStore()
const loadNoCloseArr = ['/upload/face', ]
// 判断当前的运行环境 process.env.NODE_ENV
// isDev 为真 开发环境 --- npm run serve
// isDev 为假 非开发环境(测试环境,生产环境)- npm run build
const isDev = process.env.NODE_ENV === 'development'
const request = axios.create({
// 根据环境 设置不同的baseURL
// 如果配置了单个的反向代理 --- 字符串模式
// 如果配置了多个的反向代理 --- 对象模式
// baseURL: isDev ? 'http://visitor.scdswj.cn/prod-api' : 'http://visitor.scdswj.cn/prod-api'
baseURL: isDev ? 'http://192.168.31.118:8195' : 'http://visitor.scdswj.cn/prod-api'
})
// 'http://rap2api.taobao.org/app/mock/297766/example/1643070528065'
// 请求拦截器 - 所有的请求开始之前先到此处
request.interceptors.request.use((config) => {
// 可以设置 加载的动画效果 的展示
// 在必要的路由设置一些额外的参数 ---- token信息携带放在此处
console.log(config)
return config
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器 --- 所有请求的相应先到此处
request.interceptors.response.use((response) => {
// 可以设置 加载的动画效果 的隐藏
const code = response.data.code || 200;
const message = errorCode[code] || response.data.msg || errorCode['default']
if(code == 200){
if(loadNoCloseArr.includes(response.config.url)){ // 设置请求不关闭加载动画接口
}else{
Toast.clear()
}
}else{
Toast.clear()
}
if (code === 401) {
Toast.fail(message)
setTimeout(()=>{
localStorage.removeItem('token');
localStorage.removeItem('user');
const redirectUrl = localStorage.getItem('redirect')
if(redirectUrl){
window.location.href = redirectUrl
}else{
window.location.href = '/userHome'
}
},2000)
return Promise.reject('登录状态已过期,即将返回首页重新登录。')
} else if (code === 500) {
// Message({ message: msg, type: 'error' })
Toast.fail(message)
return Promise.reject(response.data)
} else if (code === 601) {
// Message({ message: msg, type: 'warning' })
Toast.fail(message)
return Promise.reject('error')
} else if (code != 200) {
Toast.fail(message)
return Promise.reject('error')
} else {
return response.data
}
}, error => {
Toast.clear()
console.log(error.message)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
console.log(message)
Toast.fail(message)
return Promise.reject(error.response)
})
export default request
+97
View File
@@ -0,0 +1,97 @@
import request from "./adminRequest.js";
// 登录
export function getToken(authCode){
return request({
method: "get",
url: "/user/getToken?code="+authCode,
data: {
}
})
}
// 获取部门列表
export function getDept( ){
return request({
method: "get",
url: "/dept/getList",
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 获取部门员工
export function getUserListByDept( deptId){
return request({
method: "get",
url: "/user/getUserListByDept",
params:{
deptId
},
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 内部员工上传照片后,提交url
export function putUserId( data){
return request({
method: "put",
url: "/user/updateByUserId",
data,
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// http://localhost:8190/special/getList
// 获取特殊人员
export function getSpecial( params){
return request({
method: "get",
url: "/special/getList",
params,
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 删除特殊人员
export function putSpecial( id){
return request({
method: "put",
url: "/special/remove",
params: {
id
},
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 特殊人员新增
export function postSpecial( data){
return request({
method: "post",
url: "/special/add",
data,
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
// 修改特殊人员相片
export function putSpecialFace( data){
return request({
method: "put",
url: "/special/update",
data,
headers:{
'Authorization': localStorage.getItem('token')
}
})
}
+98
View File
@@ -0,0 +1,98 @@
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex';
import { Toast, Dialog } from 'vant';
// import { getItem, setItem, removeItem, dalert } from '@/com/index'
const errorCode = {
'401': '登录状态已过期,即将返回首页重新登录。',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}
const route = useRoute()
const router = useRouter()
const store = useStore()
const loadNoCloseArr = ['/upload/face', ]
// 判断当前的运行环境 process.env.NODE_ENV
// isDev 为真 开发环境 --- npm run serve
// isDev 为假 非开发环境(测试环境,生产环境)- npm run build
const isDev = process.env.NODE_ENV === 'development'
const request = axios.create({
// 根据环境 设置不同的baseURL
// 如果配置了单个的反向代理 --- 字符串模式
// 如果配置了多个的反向代理 --- 对象模式
// baseURL: isDev ? 'http://visitor.scdswj.cn/prod-api' : 'http://visitor.scdswj.cn/prod-api'
baseURL: isDev ? 'http://visitor.scdswj.cn/prod-api' : 'http://visitor.scdswj.cn/prod-api'
})
// 'http://rap2api.taobao.org/app/mock/297766/example/1643070528065'
// 请求拦截器 - 所有的请求开始之前先到此处
request.interceptors.request.use((config) => {
// 可以设置 加载的动画效果 的展示
// 在必要的路由设置一些额外的参数 ---- token信息携带放在此处
console.log(config)
return config
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器 --- 所有请求的相应先到此处
request.interceptors.response.use((response) => {
// 可以设置 加载的动画效果 的隐藏
const code = response.data.code || 200;
const message = errorCode[code] || response.data.msg || errorCode['default']
if(code == 200){
if(loadNoCloseArr.includes(response.config.url)){ // 设置请求不关闭加载动画接口
}else{
Toast.clear()
}
}else{
Toast.clear()
}
if (code === 401) {
Toast.fail(message)
setTimeout(()=>{
localStorage.removeItem('token');
localStorage.removeItem('user');
localStorage.removeItem('userToken');
const redirectUrl = localStorage.getItem('redirect')
if(redirectUrl){
window.location.href = redirectUrl
}else{
window.location.href = '/userHome'
}
},2000)
return Promise.reject('登录状态已过期,即将返回首页重新登录。')
} else if (code === 500) {
// Message({ message: msg, type: 'error' })
Toast.fail(message)
return Promise.reject(response.data)
} else if (code === 601) {
// Message({ message: msg, type: 'warning' })
Toast.fail(message)
return Promise.reject('error')
} else if (code != 200) {
Toast.fail(message)
return Promise.reject('error')
} else {
return response.data
}
}, error => {
Toast.clear()
console.log(error.message)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
console.log(message)
Toast.fail(message)
return Promise.reject(error.response)
})
export default request
+14
View File
@@ -0,0 +1,14 @@
import request from "./index";
export function getShop(){
return request({
url: '/shop',
method: 'get'
})
}
export function getKind(){
return request({
url: '/hotkind',
method: 'get'
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from "./index";
// 来访预约接口
export function uploadApi(data){
return request({
url: '/upload/face',
method: 'post',
data
})
}
+69
View File
@@ -0,0 +1,69 @@
import request from "./index";
// 来访预约接口
export function postApply(data){
return request({
url: '/visitor/add',
method: 'post',
data,
headers:{
'Authorization': localStorage.getItem('userToken')
}
})
}
// 拜访人列表
export function getVisitor(){
return request({
url: '/visitor/getVisitingInfo',
method: 'get',
headers:{
'Authorization': localStorage.getItem('userToken')
}
})
}
// 手机号查询预约记录
export function getByPhone(data){
return request({
url: '/visitor/getByPhone',
method: 'get',
params: data,
headers:{
'Authorization': localStorage.getItem('userToken')
}
})
}
// 手机号拜访人姓名
export function getUserNameByTel(phone){
return request({
url: '/user/getUserNameByTel',
method: 'get',
params: {
phone
},
headers:{
'Authorization': localStorage.getItem('userToken')
}
})
}
// 微信授权获得code
export function getWeChatCode(){
return request({
url: '/user/getWxLogin',
method: 'get'
})
}
// 微信登录
export function login(code){
return request({
url: '/user/getWxToken',
method: 'get',
params:{
code
}
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+62
View File
@@ -0,0 +1,62 @@
html,
body,
ul,
li,
ol,
dl,
dd,
dt,
p,
h1,
h2,
h3,
h4,
h5,
h6,
form,
fieldset,
legend,
img,input{
margin: 0;
padding: 0;
}
fieldset,
img {
border: none;
}
img {
display: block;
}
ul,
ol {
list-style: none;
}
body {
color: #333;
font: 12px/20px "SimSun", "宋体", "Arial Narrow", HELVETICA;
background: #fff;
/* overflow-y:scroll;*/
/* overflow-x: hidden; */
}
a {
color: #666;
text-decoration: none;
}
a:visited {
color: #666;
}
a:hover,
a:active,
a:focus {
color: #ff8400;
text-decoration: underline;
}
+10
View File
@@ -0,0 +1,10 @@
$main-color: #30BF78; // 用户端主题色
// 字号大小
$sm-size: .32rem /* 24/75 */;
$size26: .3467rem /* 26/75 */;
$md-size: .3733rem /* 28/75 */;
$size30: .4rem /* 30/75 */;
$lg-size: .4267rem /* 32/75 */;
// 颜色
$white: #ffffff;
Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+341
View File
@@ -0,0 +1,341 @@
import { Dialog } from 'vant';
/*
表单对象验证、 rulesForm
重置表单对象、resetForm
手机号验证、 rulesPhone
身份证验证、 rulesIdCard
计算器、 calculator
时间格式化、formatDate
获取星期几、weekDate
检查数据类型、checkType
表单去除空格、trimFrom
数组是否包含数组、isIncludeArray
根据id递归查询对象、recursion
计算两个时间戳相差的时间、timeDiff
判断开始时间是否大于结束时间 compareTime
*/
/*
表单验证
@param { Object } form(必填) 验证的表单
@param { Object } rules(选填) 需要验证的字段 试例rules:{ name: '请输入名称' } name为需要验证的字段
@return { Boolean } true 通过 false 失败
*/
export function rulesForm ( form, rules ) {
let flage = true
let JSONrules = JSON.stringify(rules)
if(JSONrules !== JSON.stringify({})){
for( let key in rules){
if(Array.isArray( form[key] ) && form[key].length == 0 ){
// uni.showToast({
// title: rules[key],
// })
Dialog.alert({
message: rules[key],
})
flage = false
return
break;
}else if( !form[key] && form[key] !== 0 ){
// uni.showToast({
// title: rules[key],
// })
// Dialog(rules[key])
Dialog.alert({
message: rules[key],
})
flage = false
break;
}
}
}
return flage
}
export function rulesFormFBoolean ( form ) {
let flage = true
let JSONform = JSON.stringify(form)
if(JSONform !== JSON.stringify({})){
for( let key in form){
if(Array.isArray( form[key] ) && form[key].length == 0 ){
flage = false
return
break;
}else if( !form[key] && form[key] !== 0 ){
flage = false
break;
}
}
}
return flage
}
/*
重置表单对象
@param { Object } form(必填) 需要重置的表单
@param { Object } rules(选填) 指定需要重置的值默认设为null 试例rules:{ name: 1 } name字段重置为1
@return { Object } 重置后的值
*/
export function resetForm(form, rules = {}) {
for( let key in form){
if(checkType(form[key]) == "Array"){
if(rules.hasOwnProperty(key)){ // 是否包含需要更改的
form[key] = rules[key]
}else{
form[key] = []
}
}else if(checkType(form[key]) == "Object"){
if(rules.hasOwnProperty(key)){ // 是否包含需要更改的
form[key] = rules[key]
}else{
form[key] = {}
}
}else if(checkType(form[key]) == "Boolean"){
if(rules.hasOwnProperty(key)){ // 是否包含需要更改的
form[key] = rules[key]
}else{
form[key] = false
}
}else{
if(rules.hasOwnProperty(key)){ // 是否包含需要更改的
form[key] = rules[key]
}else{
form[key] = null
}
}
}
return form
}
/* 手机号验证
@param { Number } IdcardVal(必填) 手机号
@return { Boolean } true 通过 false 失败
*/
export function rulesPhone ( phoneVal ) {
let phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/;
if(!phoneReg.test(phoneVal)) {
Dialog.alert({
message: '请输入正确的手机号!',
})
return false;
}else{
return true
}
}
/*
@param { Number } email(必填) 邮箱地址
@return { Boolean } true 通过 false 失败
*/
export function rulesEmail(email) {
var reg = /^([\w+\.])+@\w+([.]\w+)+$/
return reg.test(email)
}
/*
身份证验证
@param { String } IdcardVal(必填) 需要检查的参数
@return { Boolean } true 通过 false 失败
*/
export function rulesIdCard ( IdcardVal ) {
var idCardReg = /^(\d{15}$|^\d{18}$|^\d{17}(\d|X|x))$/;
if(!idCardReg.test(IdcardVal)) {
// uni.showToast({
// title: '身份证号码格式错误!',
// })
Dialog.alert({ message: '身份证号码格式错误!' })
return false;
} else{
return true
}
}
/*
计算器
startNum 类型 Number||String 开始的参数
endNum 类型 Number||String 结束参数
operator 类型 String 计算方式 + - * ÷
返回值 为数值类型计算结果
*/
export function calculator ( startNum, endNum, operator = '+'){
if(operator == '+'){
return (Number(startNum * 1000) + Number(endNum * 1000)) / 1000
}else if( operator == '-' ){
return (Number(startNum * 1000) - Number(endNum * 1000)) / 1000
}else if( operator == '/' ){
return (Number(startNum * 1000) / Number(endNum * 1000)) / 1000000
}else if( operator == '*' ){
return (Number(startNum * 1000) * Number(endNum * 1000)) / 1000000
}
}
/*
时间日期格式化
@param { Date||String } timeStr(必填) 默认值当前时间 传入时间
@param { String } separator(必填) 默认值 '-' 分隔符号 chinese返回中文汉字
@param { String } type(选填) 是否返回时分秒 yaer表示只返回年月日
@return { String } 日期
*/
export function formatDate (timeStr = (new Date()), separator = '-', type){
const date = new Date(timeStr)
let yaer = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hour = date.getHours()
let minute = date.getMinutes()
let second = date.getSeconds()
if(separator == '-'){
if(type == 'year'){
return yaer + separator + (month >= 10 ? month : ('0' + month)) + separator + (day >= 10 ? day : ('0' + day))
}else {
return yaer + separator + (month >= 10 ? month : ('0' + month)) + separator + (day >= 10 ? day : ('0' + day)) + ' ' + (hour >= 10 ? hour : ('0' + hour)) + ':' + (minute >= 10 ? minute : ('0' + minute)) + ':' + (second >= 10 ? second : ('0' + second))
}
}else if (separator == 'chinese') {
if(type == 'year'){
return yaer + '年' + month+ '月' + day+ '日'
}else {
return yaer + '年' + month+ '月' + day+ '日' + hour + '时' + minute + '分' + second + '秒'
}
}
}
/*
获取星期几
@param { Date||String } time(必填) 默认值当前时间 传入时间
@return { String } 星期几
*/
export function weekDate (time = (new Date())){
const date = new Date(time)
let arr = ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
return arr[date.getDay()]
}
/*
判断数据类型值
@param { any } value(必填) 需要检查的参数
@return { String } 'Number', 'String', 'Boolean', 'Array', 'Object', 'Function', 'Undefined', 'null'
*/
export function checkType(value) {
const toString = Object.prototype.toString;
if(toString.call(value) === '[object Number]'){
return 'Number'
}else if(toString.call(value) === '[object String]') {
return 'String'
}else if(toString.call(value) === '[object Boolean]') {
return 'Boolean'
}else if(toString.call(value) === '[object Array]') {
return 'Array'
}else if(toString.call(value) === '[object Object]') {
return 'Object'
}else if(toString.call(value) === '[object Function]') {
return 'Function'
}else if(toString.call(value) === '[object Undefined]') {
return 'Undefined'
}else if(toString.call(value) === '[object Null]') {
return 'null'
}else if(toString.call(value) === '[object Date]') {
return 'Date'
}else if(toString.call(value) === '[object Error]') {
return 'Error'
}
}
/*
表单去除二端空格
该方法需要依赖checkType方法
@param { Object || Array } form(必填) 需要去除空格的表单
@return { Object || Array } 为去空后的数据对象数组
*/
export function trimFrom ( form ) {
let newForm = null
if(checkType(form)== 'Object'){
newForm = { ...form }
}else if(checkType(form)== 'Array') {
newForm = [...form ]
}
for( let key in form){
if( checkType(form[key] ) == 'String' ){
console.log(newForm[key])
newForm[key] = form[key].trim()
}else if( checkType(form[key] ) == 'Object' ){
newForm[key] = trim(form[key])
}else if( checkType(form[key]) == 'Array' ) {
newForm[key] = trim(form[key])
}
}
return newForm
}
/*
数组是否包含数组
@param { Array } arr(必填) 包含数组
@param { Array } arr2(必填) 被包含数组
@return { Boolean } 包含true 不包含false
*/
export function isIncludeArray(arr1, arr2) {
return arr2.every((item) => {
return arr1.includes(item)
})
}
/*
根据id递归查询对象
@param { Array } arr(必填) 查询的数组
@param { Number||String } value(必填) 查询的数组
@param { Number||String } unique(选填) 默认值'id' 查询的字段名
@param { Array } children(选填) 默认值'children' 查询递归数组
@return { Object||null } 查询到的值
*/
export function recursion (arr, value, unique = 'id', children = 'children' ) {
let obj = null
arr.forEach(( item, index ) => {
if(item[unique] == value){
obj = item
}else {
if(item[children] && item[children].length != 0){
if(recursion(item[children], value, unique, children)){
obj = recursion(item[children], value, unique, children)
}
}
}
});
return obj
}
/*
计算两个时间戳相差的时间
@param { startTime, endTime } 一个时间,另一个时间
@return { Object } 比较后对象返回 天时分秒对象集合
*/
export function timeDiff (startTime, endTime) {
const time2 = new Date(endTime)
const time1 = new Date(startTime)
const diffTime = {
day: 0,
hour: 0,
min: 0,
sec: 0
}
const diff = Math.abs(time2.getTime() - time1.getTime());
const remain_hour = diff % (24 * 60 * 60 * 1000);
const remain_min = remain_hour % ( 60 * 60 * 1000);
diffTime.day = Math.floor(diff / (24 * 60 * 60 * 1000));
diffTime.hour = Math.floor(remain_hour / (60 * 60 * 1000));
diffTime.min = Math.floor(remain_min / (60* 1000));
diffTime.sec = remain_min % (60 * 1000) / 1000;
return diffTime;
}
/*
判断开始时间是否大于结束时间 compareTime
@param { startTime, endTime } 一个时间,另一个时间
@return { boolean } 结束时间大于开始时间 返回true 否者false
*/
export function compareTime (startTime, endTime) {
const time1 = new Date(startTime)
const time2 = new Date(endTime)
if(time2.getTime() - time1.getTime() > 0){
return true
}else{
return false
}
}
+40
View File
@@ -0,0 +1,40 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
+
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Documentation
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
</p>
<button type="button" @click="count++">count is: {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<style scoped>
a {
color: #42b983;
}
</style>
+191
View File
@@ -0,0 +1,191 @@
<template>
<div @click.self="onClickCancel" class='fixed-model'>
<div class="model">
<header>
<h3>新增特殊人员</h3>
</header>
<div class="content">
<input v-model="form.name" class="name-input" type="text" placeholder="请输入人员姓名" />
<input v-model="form.userId" class="tel-input" type="text" placeholder="请输入人员联系方式" />
<p>人脸上传请上传正脸无遮挡照片</p>
<div class="upload-box">
<img v-if="!faceImg" class="icon-img" src="../assets/img/xiangji.png" alt="">
<img v-else class="photo" :src="faceImg" alt="">
<input @change="onChangeFile" type="file">
</div>
<div class="button-box">
<div @click="onClickCancel" class="cancel-button">取消</div>
<div @click="onClickSave" class="save-button">保存</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
/* @data */
const router = useRouter()
const store = useStore()
const emits = defineEmits(['change', 'cancel', 'save'])
const form = reactive({
face: '',
userId: '',
name: '',
})
const faceImg = ref('')
/* @setup */
/* @method */
// 上传图片
const onChangeFile = (e) =>{
if(e.target.files[0]){
form.face = e.target.files[0]
let reader = new FileReader()
reader.readAsDataURL(e.target.files[0]) // 转base64
reader.onload = () =>{ // 简化代码 - 转换完成
faceImg.value = reader.result
}
}
}
const onClickCancel = () =>{
emits('cancel')
}
const onClickSave = () =>{
emits('save', form)
}
</script>
<style lang='scss' scoped>
.fixed-model{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(60,60,67,0.6);
.model{
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-60%) ;
width: 8.1867rem /* 614/75 */;
// height: 11.7333rem /* 880/75 */;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */;
header{
width: 100%;
height: 3.7333rem /* 280/75 */;
background: url('../assets/img/xinzengbeijing.png') left top/100% 100% no-repeat;
padding: .8267rem /* 62/75 */ 0 0;
box-sizing: border-box;
h3{
line-height: .8rem /* 60/75 */;
font-size: .5333rem /* 40/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
text-align: center;
}
}
.content{
width: 100%;
padding: 0 .72rem /* 54/75 */ .8rem /* 60/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: -1.6267rem /* 122/75 */;
.name-input, .tel-input{
width: 100%;
height: 1.1467rem /* 86/75 */;
background: #F5F5F5;
border-radius: 1.3333rem /* 100/75 */;
opacity: 1;
border: none;
outline: none;
padding: 0 .4533rem /* 34/75 */ 0;
box-sizing: border-box;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
margin-bottom: .48rem /* 36/75 */;
}
.tel-input{
margin-bottom: .3733rem /* 28/75 */;
}
.name-input::-webkit-input-placeholder{
color: #B6B7B8;
}
.tel-input::-webkit-input-placeholder{
color: #B6B7B8;
}
p{
line-height: .5333rem /* 40/75 */;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #000000;
padding: 0 0 0 .2667rem /* 20/75 */;
box-sizing: border-box;
margin-bottom: .2667rem /* 20/75 */;
}
.upload-box{
position: relative;
width: 3.28rem /* 246/75 */;
height: 2.6667rem /* 200/75 */;
background: #F5F5F5;
border-radius: .2667rem /* 20/75 */;
opacity: 1;
border: .0267rem /* 2/75 */ solid #D1D1D6;
display: flex;
justify-content: center;
align-items: center;
.icon-img{
width: 1.0667rem /* 80/75 */;
height: 1.0667rem /* 80/75 */;
}
.photo{
display: block;
width: 100%;
height: 100%;
border-radius: .2667rem /* 20/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
}
}
.button-box{
margin-top: 1.1467rem /* 86/75 */;
display: flex;
div{
// width: 240px;
flex: 1;
height: 1.0667rem /* 80/75 */;
line-height: 1.0667rem /* 80/75 */;
background: #448CF7;
border-radius: 1.3333rem /* 100/75 */;
text-align: center;
font-size: $md-size;
color: #FFFFFF;
}
.cancel-button{
margin-right: .2667rem /* 20/75 */;
background: #D9D9D9;
color: #999999;
}
}
}
}
}
</style>
+162
View File
@@ -0,0 +1,162 @@
<template>
<div @click.self="onClickCancel" class='fixed-model'>
<div class="model">
<header>
<h3>员工人脸照片</h3>
<div class="upload-box">
<div class="span">点击上传</div>
<img v-if="faceImg" class="face-img" :src="props.faceImg" alt="">
<input @change="onChangeFile" type="file" accept="image/*"/>
</div>
</header>
<p class="tips">点击照片框进行人脸照片上传</p>
<div class="button-box">
<div @click="onClickCancel" class="cancel-button">取消</div>
<div @click="onClickSave" class="save-button">保存</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
/* @data */
const router = useRouter()
const store = useStore()
const emits = defineEmits(['change', 'cancel', 'save'])
const props = defineProps({
faceImg: {
default: ''
},
})
const file = ref()
/* @setup */
/* @method */
// 上传图片
const onChangeFile = (e) =>{
file.value = e.target.files[0]
emits('change', e.target.files[0])
}
const onClickCancel = () =>{
emits('cancel')
}
const onClickSave = () =>{
emits('save', file.value)
}
</script>
<style lang='scss' scoped>
.fixed-model{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(60,60,67,0.6);
.model{
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-65%) ;
width: 8.1867rem /* 614/75 */;
// height: 394px;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */;
padding: 0 0 .7733rem /* 58/75 */;
box-sizing: border-box;
header{
width: 100%;
height: 10.5333rem /* 790/75 */;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */;
background: url('../assets/img/renlianbeijing.png') left top/100% 100% no-repeat;
h3{
line-height: .7733rem /* 58/75 */;
font-size: .5333rem /* 40/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
padding: .8533rem /* 64/75 */ 0 0 ;
text-align: center;
box-sizing: border-box;
}
.upload-box{
position: relative;
width: 4.4rem /* 330/75 */;
height: 4.4rem /* 330/75 */;
background: #FFFFFF;
box-shadow: 0px .1067rem /* 8/75 */ .8rem /* 60/75 */ .2667rem /* 20/75 */ rgba(68,140,247,0.4);
border-radius: .2667rem /* 20/75 */;
margin: 1.28rem /* 96/75 */ auto 0;
overflow: hidden;
.span{
text-align: center;
line-height: 4.4rem /* 330/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
}
// display: flex;
.face-img, input{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: .2667rem /* 20/75 */;
opacity: 0;
}
.face-img{
opacity: 1;
}
}
}
.tips{
line-height: .5333rem /* 40/75 */;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
text-align: center;
margin-top: -1.6rem /* 120/75 */;
}
.button-box{
// margin-top: 1.1467rem /* 86/75 */;
display: flex;
padding: 0 .7733rem /* 58/75 */;
box-sizing: border-box;
margin-top: .6133rem /* 46/75 */;
div{
// width: 240px;
flex: 1;
height: 1.0667rem /* 80/75 */;
line-height: 1.0667rem /* 80/75 */;
background: #448CF7;
border-radius: 1.3333rem /* 100/75 */;
text-align: center;
font-size: $md-size;
color: #FFFFFF;
}
.cancel-button{
margin-right: .2667rem /* 20/75 */;
background: #D9D9D9;
color: #999999;
}
}
}
}
</style>
+115
View File
@@ -0,0 +1,115 @@
<!-- label 为字段名 -->
<!-- type 为输入框类型 默认text -->
<!-- isMust 为是否存在* 默认有 -->
<!-- disabled 是否禁用 -->
<template>
<div class="input-box" :class=" isline ? 'select-active' : ''">
<div class="label"> <span v-if="props.isMust && props.isMustPosition == 'left'">*&nbsp;</span>{{ props.label }} <span v-if="props.isMust && props.isMustPosition == 'right'">*</span></div>
<input @blur="onClickBlur" @touchstart="onClickInput" :type="props.type" v-model="sea" @input="valChange(sea)" :disabled="props.disabled" :placeholder="'请输入'+props.label">
</div>
</template>
<script lang='ts' setup>
import { ref, watch } from 'vue'
import { useRouter } from 'vue-router'
/* props */
const props = defineProps({
modelValue:{
default: '' as any,
},
type:{
type: String,
default: 'text'
},
label: {
type: String,
default: ''
},
isline: {
default: false,
},
isMust: {
type: Boolean,
default: true,
},
isMustPosition: {
type: String,
default: 'right',
},
disabled: {
type: Boolean,
default: false,
},
})
/* emits */
const emits = defineEmits(['update:modelValue', 'click', 'input', 'blur'])
/* watch */
// 因为props.modelValue是非引用类型,改变时,需要监听,对sea重新赋值
watch(() => props.modelValue, () => { sea.value = props.modelValue })
// 数据双向绑定
const valChange = (e: string) =>{
emits('update:modelValue', e)
emits('input', e)
}
/* @data */
const router = useRouter()
const sea = ref(props.modelValue)
/* @method */
const onClickInput = () =>{
emits('click')
}
const onClickBlur = () =>{
emits('blur', sea.value)
}
/* setup */
// console.log(props.modelValue)
</script>
<style lang='scss' scoped>
.input-box{
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin: 0 auto .2667rem /* 20/75 */;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
}
}
input{
flex: 1;
border: none;
outline: none;
text-align: right;
font-size: $size26;
color: #333333;
// background: #F9F9F9;
padding-right: 0.3733rem;
}
input::-webkit-input-placeholder {
font-size: $size26;
color: #999999;
}
}
.select-active{
border-bottom: .0267rem /* 2/75 */ dashed #D9D9D9;
}
</style>
+98
View File
@@ -0,0 +1,98 @@
<template>
<div class='fixed-model'>
<div class="model">
<h3>删除事件提醒</h3>
<p>确认删除此员工人脸信息</p>
<div class="button-box">
<div @click="onClickCancel" class="cancel-button">取消</div>
<div @click="onClickSave" class="save-button">删除</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
const emits = defineEmits(['change', 'cancel', 'delete'])
/* @data */
const router = useRouter()
const store = useStore()
/* @setup */
/* @method */
const onClickCancel = () =>{
emits('cancel')
}
const onClickSave = () =>{
emits('delete')
}
</script>
<style lang='scss' scoped>
.fixed-model{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(60,60,67,0.6);
.model{
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-65%) ;
width: 8.1867rem /* 614/75 */;
// height: 394px;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */;
padding: .48rem /* 36/75 */ 0 .7733rem /* 58/75 */;
box-sizing: border-box;
h3{
line-height: .6667rem /* 50/75 */;
font-size: .4533rem /* 34/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
text-align: center;
color: #222222;
}
p{
line-height: .5867rem /* 44/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
margin: .6933rem /* 52/75 */ 0 .9867rem /* 74/75 */;
text-align: center;
}
.button-box{
// margin-top: 1.1467rem /* 86/75 */;
display: flex;
padding: 0 .7733rem /* 58/75 */;
box-sizing: border-box;
div{
// width: 240px;
flex: 1;
height: 1.0667rem /* 80/75 */;
line-height: 1.0667rem /* 80/75 */;
background: #FF6643;
border-radius: 1.3333rem /* 100/75 */;
text-align: center;
font-size: $md-size;
color: #FFFFFF;
}
.cancel-button{
margin-right: .2667rem /* 20/75 */;
background: #D9D9D9;
color: #999999;
}
}
}
}
</style>
+84
View File
@@ -0,0 +1,84 @@
<template>
<div class='success-model-fixed'>
<div class="success-model">
<img src="../assets/chenggong.png" alt="">
<div class="title">保存成功</div>
<p>请在手机相册查看访客邀约海报</p>
<button class="affirm-button">确定</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
/* @data */
const router = useRouter()
const store = useStore()
/* @setup */
/* @method */
</script>
<style lang='scss' scoped>
.success-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(60,60,67,0.6);
.success-model{
position: absolute;
left: 50%;
top: 24%;
transform: translateX(-50%);
width: 7.1467rem /* 536/75 */;
height: 7.6267rem /* 572/75 */;
padding: .56rem /* 42/75 */ .9333rem /* 70/75 */;
box-sizing: border-box;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */;
display: flex;
flex-direction: column;
align-items: center;
img{
width: 2.6667rem /* 200/75 */;
height: 2.6667rem;
}
.title{
text-align: center;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
line-height: .56rem /* 42/75 */;
margin: .3467rem /* 26/75 */ auto .4533rem /* 34/75 */;
}
p{
// width: 5.2rem /* 390/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: .48rem /* 36/75 */;
}
.affirm-button{
width: 2.24rem /* 168/75 */;
height: .9867rem /* 74/75 */;
background: #30BF78;
border-radius: .1333rem /* 10/75 */ ;
outline: none;
border: none;
font-size: $size30;
color: #FFFFFF;
margin-top: 1.04rem /* 78/75 */;
}
}
}
</style>
+84
View File
@@ -0,0 +1,84 @@
<template>
<div class='success-model-fixed'>
<div class="success-model">
<img src="../assets/chenggong.png" alt="">
<div class="title">保存成功</div>
<p>请在手机相册查看访客邀约海报</p>
<button class="affirm-button">确定</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
/* @data */
const router = useRouter()
const store = useStore()
/* @setup */
/* @method */
</script>
<style lang='scss' scoped>
.success-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(60,60,67,0.6);
.success-model{
position: absolute;
left: 50%;
top: 24%;
transform: translateX(-50%);
width: 7.1467rem /* 536/75 */;
height: 7.6267rem /* 572/75 */;
padding: .56rem /* 42/75 */ .9333rem /* 70/75 */;
box-sizing: border-box;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */;
display: flex;
flex-direction: column;
align-items: center;
img{
width: 2.6667rem /* 200/75 */;
height: 2.6667rem;
}
.title{
text-align: center;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
line-height: .56rem /* 42/75 */;
margin: .3467rem /* 26/75 */ auto .4533rem /* 34/75 */;
}
p{
// width: 5.2rem /* 390/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: .48rem /* 36/75 */;
}
.affirm-button{
width: 2.24rem /* 168/75 */;
height: .9867rem /* 74/75 */;
background: #30BF78;
border-radius: .1333rem /* 10/75 */ ;
outline: none;
border: none;
font-size: $size30;
color: #FFFFFF;
margin-top: 1.04rem /* 78/75 */;
}
}
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<!-- label 为字段名 -->
<!-- isMust 为是否存在* 默认无 -->
<template>
<div class="select-box" :class=" isline ? 'select-active' : ''">
<div class="label"><span v-if="props.isMust">*&nbsp;</span>{{ props.label }} </div>
<div class="select-button">
<span v-if="date" style="color: #333333;">{{ date }}</span>
<span v-else>请选择{{ props.label }}</span>
<input @change="onConfirm" class="date" type="datetime-local" v-model="date" :min="minDate" >
</div>
<!-- <img src="../../assets/employment/youjiantou.png" alt=""> -->
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { ref, watch } from 'vue'
import { formatDate } from '../com/index.js'
/* props */
const props = defineProps({
isMust:{
default: false
},
label:{
default: ''
},
modelValue:{
},
isline: {
default: false,
},
minDate: {
default: '2023-01-01 08:00:00'
}
})
/* emits */
const emits = defineEmits(['update:modelValue'])
/* @data */
let date = ref(props.modelValue)
const router = useRouter()
/* watch */
// 因为props.modelValue是非引用类型,改变时,需要监听,对sea重新赋值
watch(() => props.modelValue, () => {
if(props.modelValue == null){
date.value = ''
}else{
date.value = props.modelValue
}
})
/* @setup */
// console.log(props.modelValue)
/* @method */
const onConfirm = () => {
// console.log(date.value)
date.value = formatDate(date.value, '-')
emits('update:modelValue', date.value)
console.log(date.value)
};
</script>
<style lang='scss' scoped>
.select-box{
position: relative;
width: 9.2rem /* 690/75 */;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
margin-bottom: .2667rem /* 20/75 */;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.select-button{
position: relative;
width: 5.2rem /* 390/75 */;
text-align: right;
font-size: $size26;
color: #999999;
line-height: 1.5733rem /* 118/75 */;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
overflow: hidden;
.date{
position: absolute;
right: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
}
}
.active{
color: #333333;
}
img{
position: absolute;
right: .4rem /* 30/75 */;
top: 50%;
width: .1467rem /* 11/75 */;
height: .2667rem /* 20/75 */;
margin-top: -.16rem /* 12/75 */;
}
}
.select-active{
border-bottom: .0267rem /* 2/75 */ dashed #D9D9D9;
}
</style>
+163
View File
@@ -0,0 +1,163 @@
<!-- label 为字段名 -->
<!-- isMust 为是否存在* 默认无 -->
<!-- isline 是否有下虚线 -->
<template>
<div class="select-box" :class=" isline ? 'select-active' : ''">
<div class="label"> <span v-if="props.isMust">*&nbsp;</span>{{ props.label }}</div>
<div class="select-button" @click="showPicker = true">
<span v-if="selectVal" style="color: #333333;">{{ selectVal }}</span>
<span v-else>请选择{{ props.label }}</span>
</div>
<!-- <div class="select-line"></div> -->
<!-- <img src="../../assets/employment/youjiantou.png" alt=""> -->
<Popup v-model:show="showPicker" round position="bottom">
<!-- title="标题" -->
<Picker
:columns="props.columns"
:columns-field-names="customFieldName"
:default-index="defaultIndex"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</Popup>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { ref, watch } from 'vue'
import { Picker, Popup } from 'vant';
/* props */
const props = defineProps({
isMust:{
default: false
},
label:{
default: ''
},
isline: {
default: false,
},
columns:{
default: []
},
modelValue: {
},
name: {
default: 'title'
},
nameId: {
default: 'id'
}
})
/* emits */
const emits = defineEmits(['update:modelValue'])
/* @data */
const customFieldName = {
text: props.name,
children: 'cities',
};
let selectVal = ref('')
const router = useRouter()
let showPicker = ref(false)
let defaultIndex = ref(0)
/* @setup */
/* watch */
// 因为props.modelValue是非引用类型,改变时,需要监听,对sea重新赋值
watch(() => props.modelValue, () => {
if(props.modelValue == null){
// console.log('props.modelValue')
// console.log(props.modelValue)
selectVal.value = ''
}
})
// 根据数据赋值
watch(() => props.columns, () => {
if(props.modelValue != undefined || props.modelValue != null){
props.columns.forEach(( item ,index ) => {
// console.log('知悉了'+ item[props.name] + item[props.nameId])
if(item[props.nameId] == props.modelValue){
selectVal.value = item[props.name]
defaultIndex.value = index
}
})
}
})
/* @method */
const onConfirm = (value , index) => {
let str = ''
let ids = []
if(value.length){
value.forEach((item) => {
str = str + item[props.name]
ids.push(item[props.nameId])
});
}else{
str = value[props.name]
ids = value[props.nameId]
}
selectVal.value = str
emits('update:modelValue', ids)
showPicker.value = false
};
</script>
<style lang='scss' scoped>
.select-box{
position: relative;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin-bottom: .2667rem /* 20/75 */;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.select-button{
width: 4.8rem /* 360/75 */;
text-align: right;
font-size: $size26;
color: #999999;
line-height: 1.5733rem /* 118/75 */;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
}
.active{
color: #333333;
}
img{
position: absolute;
right: .4rem /* 30/75 */;
top: 50%;
width: .1467rem /* 11/75 */;
height: .2667rem /* 20/75 */;
margin-top: -.16rem /* 12/75 */;
}
}
.select-active{
border-bottom: .0267rem /* 2/75 */ dashed #D9D9D9;
}
</style>
+88
View File
@@ -0,0 +1,88 @@
<template>
<div class='success-model-fixed'>
<div class="success-model">
<img src="../assets/chenggong.png" alt="">
<div class="title">登记成功等待审核</div>
<p>请在预约记录里面输入联系方式查看审核进度及结果反馈</p>
<button @click="onClickAffirm" class="affirm-button">确定</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
/* @data */
const router = useRouter()
const store = useStore()
const emits = defineEmits(['click'])
/* @setup */
/* @method */
const onClickAffirm = () =>{
emits('click')
}
</script>
<style lang='scss' scoped>
.success-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(60,60,67,0.6);
.success-model{
position: absolute;
left: 50%;
top: 24%;
transform: translateX(-50%);
width: 7.1467rem /* 536/75 */;
height: 7.6267rem /* 572/75 */;
padding: .56rem /* 42/75 */ .9333rem /* 70/75 */;
box-sizing: border-box;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */;
display: flex;
flex-direction: column;
align-items: center;
img{
width: 2.6667rem /* 200/75 */;
height: 2.6667rem;
}
.title{
text-align: center;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
line-height: .56rem /* 42/75 */;
margin: .3467rem /* 26/75 */ auto .4533rem /* 34/75 */;
}
p{
// width: 5.2rem /* 390/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: .48rem /* 36/75 */;
text-align: center;
}
.affirm-button{
width: 2.24rem /* 168/75 */;
height: .9867rem /* 74/75 */;
background: #30BF78;
border-radius: .1333rem /* 10/75 */ ;
outline: none;
border: none;
font-size: $size30;
color: #FFFFFF;
margin-top: .56rem /* 42/75 */;
}
}
}
</style>
+9
View File
@@ -0,0 +1,9 @@
import { createApp } from 'vue'
import router from './router'
import store from './store'
import App from './App.vue'
const app = createApp(App)
app.use(router).use(store).mount('#app')
+110
View File
@@ -0,0 +1,110 @@
import { createRouter, createWebHistory } from 'vue-router'
import register from "../views/register.vue";
const routerHistory = createWebHistory()
const routes = [
{
path: '/login',
name:'login',
// redirect: '/login',
component: () => import('../views/login.vue')
},
{
path: '/visitorApply',
name:'visitorApply',
// redirect: '/login',
component: () => import('../views/visitorApply.vue'),
meta: { title: '我要预约' }
},
{
path: '/applyInquire',
name:'applyInquire',
component: () => import('../views/applyInquire.vue'),
meta: { title: '预约查询' }
},
{
path: '/applyRecord',
name:'applyRecord',
// redirect: '/login',
component: () => import('../views/applyRecord.vue'),
meta: { title: '预约记录' }
},
{
path: '/apply',
name:'apply',
// redirect: '/login',
component: () => import('../views/apply.vue'),
meta: { title: '我要预约' }
},
{
path: '/adminHome',
name:'adminHome',
// redirect: '/login',
component: () => import('../views/adminHome.vue'),
meta: { title: '智能访客' }
},
{
path: '/visitorRecord',
name:'visitorRecord',
// redirect: '/login',
component: () => import('../views/visitorRecord.vue'),
meta: { title: '访客记录' }
},
{
path: '/Invitation',
name:'Invitation',
// redirect: '/login',
component: () => import('../views/Invitation.vue'),
meta: { title: '我要邀约' }
},
{
path: '/userHome',
name:'userHome',
// redirect: '/login',
component: () => import('../views/userHome.vue'),
meta: { title: '智能访客' }
},
{
path: '/register',
name:'register',
component: register
},
{
path: '/home',
name:'home',
// redirect: '/login',
component: () => import('../views/home.vue'),
meta: { title: '员工信息' }
},
{
path: '/specialCrowd',
name:'specialCrowd',
// redirect: '/login',
component: () => import('../views/specialCrowd.vue'),
meta: { title: '特殊人员录入' }
},
{
path:'/',
name:'',
component:()=>import('../views/userHome.vue'),
meta: { title: '智能访客' }
},
{ path: '/:pathMatch(.*)',
redirect: '/userHome',
}
]
const router = createRouter({
history: routerHistory,
routes
})
const title = document.querySelector('title')
router.beforeEach((to, from, next) => {
title.innerText = to.meta.title
next()
})
export default router
+42
View File
@@ -0,0 +1,42 @@
import { createStore } from 'vuex'
const store = createStore({
state: {
name: '访客预约系统',
stateEnum: {
0: {
statusName:'待审核',
className: 'status',
statusText: '您的来访预约还未审核,请耐心等待',
status: 0
},
1: {
statusName: '已通过',
className: 'pass',
statusText: '预约审核通过',
status: 1
},
2: {
statusName:'未通过',
className: 'refuse',
statusText: '预约审核已拒绝',
status: 2
}
}
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
export default store
+310
View File
@@ -0,0 +1,310 @@
<template>
<div class='invitation-model-fixed' id="canvas">
<div class="invitation-model">
<img class="english-title" src="../assets/yinwen.png" alt="">
<div class="ewm-box">
<!-- <img :src=" 'data:image/png;base64,'+imgEncode" alt=""> -->
<img src="https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pcs" alt="">
<b class="ewm-box-one"></b>
<b class="ewm-box-two"></b>
<b class="ewm-box-three"></b>
<b class="ewm-box-four"></b>
</div>
<div class="tips">快来保存您的专属邀约二维码吧</div>
<button @click="onClickDown" class="save-button">保存图片</button>
<span @click="onClickA" :href="canvasUrl" download="下载">下载</span>
</div>
<!-- {{ raw }} -->
</div>
<Savesuccessmodel v-if="false" />
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import dd from 'dingtalk-jsapi'
import html2canvas from 'html2canvas'
import Savesuccessmodel from '@/components/save-success.vue'
import { getToken, getVisitorRecord, getWhiteCode } from '@/api/admin.js'
/* @data */
const router = useRouter()
const store = useStore()
const imgEncode = ref('')
const raw = ref('')
const canvasUrl = ref('')
/* @setup */
getWhiteCode().then(res =>{
console.log(res)
imgEncode.value = res.imgEncode
// raw.value = window.atob(res.imgEncode);
})
/* @method */
const onClickDown1 = () =>{
// const byteCharacters = atob(imgEncode.value);
// const byteNumbers = new Array(byteCharacters.length);
// for (let i = 0; i < byteCharacters.length; i++) {
// byteNumbers[i] = byteCharacters.charCodeAt(i);
// }
// const byteArray = new Uint8Array(byteNumbers);
// const blob = new Blob([byteArray], { type: 'image/jpeg' });
// raw.value = byteArray
// // 创建可下载链接
// const url = URL.createObjectURL(blob);
// // 创建隐藏的<a>标签
// const link = document.createElement('a');
// link.href = url;
// link.download = 'fileName';
// // 模拟点击隐藏的<a>标签来触发下载
// link.click();
// // 清理URL对象
// URL.revokeObjectURL(url);
}
const onClickA =() =>{
const canvas = document.getElementById('canvas')
console.log(canvas.offsetHeight)
console.log(canvas.scrollHeight)
// alert('asjfjafj1')
// // window.location.href = canvasUrl.value
// dd.previewImage({
// urls: [
// canvasUrl.value,
// ],
// current: 1,
// success: () => {},
// fail: () => {},
// complete: () => {},
// });
// dd.openLink({
// url: 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png',
// success: () => {},
// fail: () => {},
// complete: () => {},
// });
// dd.saveImage({
// url: 'https://img.alicdn.com/tps/TB1sXGYIFXXXXc5XpXXXXXXXXXX.jpg',
// success: () => {},
// fail: () => {},
// complete: () => {},
// });
}
const onClickDown = () =>{
let options = { scale: 1, allowTaint: false, useCORS: true, width: '375', height: '649', backgroundColor: null }
autoPicture('canvas')
}
const autoPicture = async (el, options) => {
// let { scale = 1, allowTaint = false, useCORS = true, width = '375', height = '649', backgroundColor = null } = options
const id = document.getElementById(el)
const canvas = await html2canvas(id, {
scale: 1, //缩放比例,默认为1
allowTaint: false, //是否允许跨域图像污染画布
useCORS: true, //是否尝试使用CORS从服务器加载图像
width: id.offsetWidth, //画布的宽度
height: id.offsetHeight, //画布的高度
backgroundColor: null //画布的背景色,默认为透明
})
// canvas.toDataURL('image/png')
console.log(canvas.toDataURL('image/png'))
canvasUrl.value = canvas.toDataURL('image/png')
}
</script>
<style lang='scss' scoped>
.invitation-model-fixed{
position: fixed;
width: 100%;
height: 100%;
background: rgba(60,60,67,0.6);
.invitation-model{
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 7.2267rem /* 542/75 */;
height: 9.3067rem /* 698/75 */;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */.5333rem /* 40/75 */ .5333rem /* 40/75 */ .5333rem /* 40/75 */;
// overflow: hidden;
opacity: 1;
background: url('../assets/yaoqingbeijing.png') left top/100% 100% no-repeat;
.english-title{
margin: 1.1733rem /* 88/75 */ auto 0;
width: 4.9867rem /* 374/75 */;
height: .6133rem /* 46/75 */;
}
.ewm-box{
position: relative;
border-radius: .1333rem /* 10/75 */;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: 4.64rem /* 348/75 */;
height: 4.64rem /* 348/75 */;
margin: .5067rem /* 38/75 */ auto 0;
display: flex;
justify-content: center;
align-items: center;
img{
width: 4rem /* 300/75 */;
height: 4rem;
}
}
@mixin border-basics {
position: absolute;
width: 1.1733rem /* 88/75 */;
height: 1.1733rem /* 88/75 */;
// border-radius: .1333rem /* 10/75 */;
opacity: 1;
border: .1333rem /* 10/75 */ solid #30BF78;
}
.ewm-box-one::after{
content: '';
position: absolute;
left: 0;
top: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: .1333rem /* 10/75 */;
height: 1.3067rem /* 98/75 */;
background-color: #30BF78;
border-radius: .2667rem /* 20/75 */ 0 .1333rem /* 10/75 */ .1333rem /* 10/75 */;
}
.ewm-box-one::before{
content: '';
position: absolute;
left: 0;
top: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: 1.3067rem /* 98/75 */;
height: .1333rem /* 10/75 */;
background-color: #30BF78;
border-radius: .2667rem /* 20/75 */ .1333rem /* 10/75 */ .1333rem /* 10/75 */ 0 ;
}
.ewm-box-two::after{
content: '';
position: absolute;
right: 0;
top: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: .1333rem /* 10/75 */;
height: 1.3067rem /* 98/75 */;
background-color: #30BF78;
border-radius: 0 .2667rem /* 20/75 */ .1333rem /* 10/75 */ .1333rem /* 10/75 */;
}
.ewm-box-two::before{
content: '';
position: absolute;
right: 0;
top: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: 1.3067rem /* 98/75 */;
height: .1333rem /* 10/75 */;
background-color: #30BF78;
border-radius: .1333rem /* 10/75 */ .2667rem /* 20/75 */ 0 .1333rem /* 10/75 */ ;
}
// .ewm-box-three{
// @include border-basics;
// left: 0;
// bottom: 0;
// border-top: none;
// border-right: none;
// border-radius: 0 0 0 .1333rem /* 10/75 */ ;
// }
.ewm-box-three::after{
content: '';
position: absolute;
left: 0;
bottom: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: .1333rem /* 10/75 */;
height: 1.3067rem /* 98/75 */;
background-color: #30BF78;
border-radius: .1333rem /* 10/75 */ .1333rem /* 10/75 */ 0 .2667rem /* 20/75 */ ;
}
.ewm-box-three::before{
content: '';
position: absolute;
left: 0;
bottom: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: 1.3067rem /* 98/75 */;
height: .1333rem /* 10/75 */;
background-color: #30BF78;
border-radius: 0 .1333rem /* 10/75 */ .1333rem /* 10/75 */ .2667rem /* 20/75 */ ;
}
// .ewm-box-four{
// @include border-basics;
// bottom: 0;
// right: 0;
// border-top: none;
// border-left: none;
// border-radius: 0 0 .1333rem /* 10/75 */ 0;
// }
.ewm-box-four::after{
content: '';
position: absolute;
right: 0;
bottom: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: .1333rem /* 10/75 */;
height: 1.3067rem /* 98/75 */;
background-color: #30BF78;
border-radius: .1333rem /* 10/75 */ .1333rem /* 10/75 */ .2667rem /* 20/75 */ 0 ;
}
.ewm-box-four::before{
content: '';
position: absolute;
right: 0;
bottom: 0;
// border: .1333rem /* 10/75 */ solid #30BF78;
width: 1.3067rem /* 98/75 */;
height: .1333rem /* 10/75 */;
background-color: #30BF78;
border-radius: .1333rem /* 10/75 */ 0 .2667rem /* 20/75 */ .1333rem /* 10/75 */ ;
}
.tips{
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
margin-top: .72rem /* 54/75 */;
text-align: center;
line-height: .6133rem /* 46/75 */;
}
.save-button{
position: absolute;
bottom: 0;
left: 50%;
z-index: 10;
transform: translateY(50% ) translateX(-50% );
width: 3.76rem /* 282/75 */;
height: 1.1467rem /* 86/75 */;
line-height: 1.1467rem /* 86/75 */;
background: #30BF78;
border-radius: 1.3333rem /* 100/75 */;
opacity: 1;
font-size: $lg-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FFFFFF;
text-align: center;
border: none;
outline: none;
}
}
}
</style>
+143
View File
@@ -0,0 +1,143 @@
<template>
<div v-if="!canvasUrl" class='invitation-model-fixed' id="canvas">
<img src="../assets/yaoqingbeijingtu.png" alt="" class="big-img">
<img class="ewm" :src="'data:image/png;base64,'+imgEncode" alt="">
<!-- <img src="" alt=""> -->
</div>
<div v-if="canvasUrl" class="img-fixed">
<img :src="canvasUrl" alt="">
</div>
<!-- <img src="" alt=""> -->
<Savesuccessmodel v-if="false" />
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import dd from 'dingtalk-jsapi'
import html2canvas from 'html2canvas'
import Savesuccessmodel from '@/components/save-success.vue'
import { getToken, getVisitorRecord, getWhiteCode } from '@/api/admin.js'
/* @data */
const router = useRouter()
const store = useStore()
const imgEncode = ref('')
const raw = ref('')
const canvasUrl = ref('')
/* @setup */
/* @method */
const onClickDown1 = () =>{
// const byteCharacters = atob(imgEncode.value);
// const byteNumbers = new Array(byteCharacters.length);
// for (let i = 0; i < byteCharacters.length; i++) {
// byteNumbers[i] = byteCharacters.charCodeAt(i);
// }
// const byteArray = new Uint8Array(byteNumbers);
// const blob = new Blob([byteArray], { type: 'image/jpeg' });
// raw.value = byteArray
// // 创建可下载链接
// const url = URL.createObjectURL(blob);
// // 创建隐藏的<a>标签
// const link = document.createElement('a');
// link.href = url;
// link.download = 'fileName';
// // 模拟点击隐藏的<a>标签来触发下载
// link.click();
// // 清理URL对象
// URL.revokeObjectURL(url);
}
const onClickA =() =>{
const canvas = document.getElementById('canvas')
console.log(canvas.offsetHeight)
console.log(canvas.scrollHeight)
dd.previewImage({
urls: [
canvasUrl.value,
],
current: 1,
success: () => {},
fail: () => {},
complete: () => {},
});
}
const onClickDown = () =>{
// let options = { scale: 1, allowTaint: false, useCORS: true, width: '375', height: '649', backgroundColor: null }
autoPicture('canvas')
}
const autoPicture = async (el, options) => {
// let { scale = 1, allowTaint = false, useCORS = true, width = '375', height = '649', backgroundColor = null } = options
const id = document.getElementById(el)
// alert(document.getElementById('canvas').offsetWidth
const canvas = await html2canvas(id, {
scale: 2, //缩放比例,默认为1
allowTaint: false, //是否允许跨域图像污染画布
useCORS: true, //是否尝试使用CORS从服务器加载图像
// width: document.getElementById('canvas').offsetWidth, //画布的宽度
// height: document.getElementById('canvas').offsetHeight, //画布的高度
width: document.getElementById('canvas').offsetWidth, //画布的宽度
height: document.getElementById('canvas').offsetHeight, //画布的高度
backgroundColor: null //画布的背景色,默认为透明
})
// canvas.toDataURL('image/png')
// console.log(canvas.toDataURL('image/png'))
canvasUrl.value = canvas.toDataURL('image/png')
}
// localStorage.setItem('token', 123456)
getWhiteCode().then(res =>{
console.log(res)
imgEncode.value = res.imgEncode
// autoPicture(canvas)
// raw.value = window.atob(res.imgEncode);
setTimeout(()=>{
onClickDown()
}, 100)
})
</script>
<style lang='scss' scoped>
.invitation-model-fixed{
position: fixed;
width: 100%;
height: 100%;
// background: url('../assets/yaoqingbeijingtu.png') left top/100% auto no-repeat;
background: #fff;
.big-img{
width: 100%;
}
.ewm{
position: absolute;
left: 50%;
top: 4rem /* 300/75 */;
transform: translateX(-50%);
width:4.5333rem /* 340/75 */;
height: 4.5333rem /* 340/75 */;
}
}
.img-fixed{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
img{
width: 100%;
}
}
</style>
+321
View File
@@ -0,0 +1,321 @@
<template>
<div v-if="true" class='box'>
<header>
<h3>Hello~{{userName}}</h3>
<h4> 你又来审批预约了啊ㄟ( , )</h4>
<div class="plate-box">
<div @click="onClickGo('/Invitation')" class="plate-button">
<img src="../assets/ren.png" alt="">
<div class="right">
<div>我要邀约</div>
<p>保存专属二维码</p>
</div>
</div>
<div @click="onClickGo('/visitorRecord')" class="plate-button">
<img src="../assets/shuben.png" alt="">
<div class="right">
<div>访客记录</div>
<p>查看历史来访记录</p>
</div>
</div>
</div>
<div class="position-img">
<img src="../assets/fangzi.png" alt="">
</div>
</header>
<div class="content">
<h2>
<div>待审批列表</div>
<span @click="onClickGo('/visitorRecord')">查看全部</span>
</h2>
<div class="content-ul">
<div v-for="item in recordArr" class="wait-list" :key="item.id">
<h5>被拜访人{{ item.visitingName }}</h5>
<h5>拜访时间{{ item.hours }}</h5>
<div class="apply-box">
<p><span>访客姓名</span><span>{{ item.name }}</span></p>
<p><span>联系方式</span><span>{{ item.phone }}</span></p>
<p><span>随行人数</span><span>{{ item.visitingNum }} </span></p>
<p><span>来访事由</span><span>{{ item.reason }}</span></p>
</div>
<div class="status" :class="stateEnum[item.status].className">{{ stateEnum[item.status].statusName }}</div>
</div>
</div>
<div v-if="isLoading && recordArr.length == 0" class="no-have">
<img class="" src="../assets/nodata.png" alt="">
<p>暂无待审批数据</p>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { Toast, ImagePreview } from 'vant';
import dd from 'dingtalk-jsapi'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import { getToken, getVisitorRecord, getWhiteCode } from '@/api/admin.js'
/* @data */
const router = useRouter()
const store = useStore()
const isLoading = ref(false)
const token = ref('')
const userName = ref('')
const query = reactive({
status: 0,
size: 2,
current: 1,
})
const recordArr = ref([])
const stateEnum = store.state.stateEnum
token.value = localStorage.getItem('token')
localStorage.setItem('redirect', '/adminHome')
/* @setup */
const login = () =>{
Toast.loading({ duration: 0, forbidClick: true, message: '登录中', });
dd.getAuthCode({
corpId: 'ding03f8e88dde9b426835c2f4657eb6378f',
success: (res) => {
const { code } = res;
localStorage.removeItem('token');
localStorage.removeItem('user');
getToken(code).then(data =>{
localStorage.setItem('user', JSON.stringify(data.data) )
localStorage.setItem('token', data.data.authorization)
userName.value = data.data.name
Toast.clear()
Toast.success('登录成功')
visitorList()
})
},
fail: (err) => {
},
complete: () => {},
});
}
// login()
const visitorList = () =>{
Toast.loading({ duration: 0, forbidClick: true, message: '加载中', });
getVisitorRecord(query).then(res =>{
isLoading.value = true
recordArr.value = res.data
}).catch(err=>{
isLoading.value = true
})
}
// visitorList()
if(token.value){
visitorList()
console.log((JSON.parse(localStorage.getItem('user'))).name )
userName.value = JSON.parse(localStorage.getItem('user')).name
}else{
login()
}
/* @method */
const onClickGo = (url = '/adminHome') =>{
router.push(url)
}
</script>
<style lang='scss' scoped>
.box{
background-color: #F9FBFC;
display: flex;
flex-direction: column;
header{
position: relative;
width: 100%;
height: 10.4rem /* 780/75 */;
background: url('../assets/guanlibeijing.png') left top/100% 100% no-repeat;
box-sizing: border-box;
h3{
padding: 1.0933rem /* 82/75 */ .6933rem /* 52/75 */ .1067rem /* 8/75 */;
box-sizing: border-box;
line-height: .72rem /* 54/75 */;
font-size: .48rem /* 36/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
}
h4{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FFFFFF;
padding: 0 .6933rem /* 52/75 */ 1.12rem /* 84/75 */;
box-sizing: border-box;
}
.position-img{
position: absolute;
right: .4267rem /* 32/75 */;
top: .2933rem /* 22/75 */;
width: 3.6267rem /* 272/75 */;
height: 2.96rem /* 222/75 */;
img{
width: 100%;
height: 100%;
}
}
.plate-box{
width: 100%;
padding: 0 .4rem /* 30/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
.plate-button{
width: 4.4rem /* 330/75 */;
height: 1.92rem /* 144/75 */;
background: linear-gradient(180deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.6) 48%);
border-radius: .2667rem /* 20/75 */;
border: .0267rem /* 2/75 */ solid #FFFFFF;
display: flex;
align-items: center;
img{
width: .9867rem /* 74/75 */;
height: .9867rem /* 74/75 */;
margin-left: .32rem /* 24/75 */;
}
.right{
margin-left: .3467rem /* 26/75 */;
div{
line-height: .64rem /* 48/75 */;
font-size: $lg-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
}
P{
line-height: .4rem /* 30/75 */;
font-size: .2667rem /* 20/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
margin-top: .1067rem /* 8/75 */;
}
}
}
}
}
.content{
flex: 1;
overflow: auto;
position: relative;
// top: 5.8667rem /* 440/75 */;
margin-top: -4.5333rem /* 340/75 */;
width: 100%;
padding: 0 .4rem /* 30/75 */;
box-sizing: border-box;
h2{
width: 100%;
padding: .2667rem /* 20/75 */ .32rem /* 24/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
line-height: .72rem /* 54/75 */;
font-size: .48rem /* 36/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
span{
// line-height: 36px;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
}
}
.content-ul{
.wait-list{
position: relative;
width: 9.2rem /* 690/75 */;
// height: 470px;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
padding: .48rem /* 36/75 */;
box-sizing: border-box;
margin-bottom: .2667rem /* 20/75 */;
h5{
height: .6667rem /* 50/75 */;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #222222;
}
.apply-box{
width: 100%;
padding: .4rem /* 30/75 */ .3733rem /* 28/75 */ .5067rem /* 38/75 */;
box-sizing: border-box;
border-radius: .1333rem /* 10/75 */;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: .72rem /* 54/75 */;
background: #F5F7F9;
span:last-child{
color: #5F6583;
}
}
.status{
position: absolute;
top: 0;
right: 0;
width: 1.52rem /* 114/75 */;
height: .6667rem /* 50/75 */;
line-height: .6667rem /* 50/75 */;
background: #FFE9E4;
border-radius: 0px .2667rem /* 20/75 */ 0px .1333rem /* 10/75 */;
color: #FF6643;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FF6643;
text-align: center;
}
.refuse{
color: #5F6583;
background: #EAEEF6;
}
.pass{
color: #30BF78;
background: #E7F8F5;
}
}
}
.no-have{
// display: block;
width: 7.4667rem /* 560/75 */;
height: 5.3867rem /* 404/75 */;
margin: .8rem /* 60/75 */ auto;
img{
width: 100%;
height: 100%;
}
// font-size: 15px;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
text-align: center;
}
}
}
</style>
+720
View File
@@ -0,0 +1,720 @@
<template>
<div v-if="true" class='box'>
<div class="apply-box">
<img class="text" src="../assets/zhinengfangke.png" alt="">
<p>来访提前约 拜访开门无需按铃</p>
<div class="apply-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>拜访信息<span>(我找谁)</span></span>
</h3>
<div class="apply-content-form">
<!-- <Selectli label="被拜访对象" v-model="form.visitingTel" :columns="visitDict" :isMust="true" :isline="true" name="name" nameId="userId"/> -->
<!-- <div class="search-select-box">
<label class="label"><span>*&nbsp;</span>被拜访人</label>
<div class="search-select-input">
<input @click.stop="onClickVisitor" @input="onInputVisior" :value="visitorName" type="text" placeholder="可填写选择">
<div v-if="isSelect && visitDict.length" class="select-box">
<b></b>
<div class="select-sroll-box">
<div @click.self="onClickSelect(item)" v-for="item in visitDict" :class="item.userId == form.visitingTel ? 'select-active' : ''" class="select-li" :key="item.userId">{{ item.name }}</div>
</div>
</div>
<div v-if="isSelect && !visitDict.length" class="select-box">
<b></b>
<div class="select-li" >查无此人</div>
</div>
</div>
</div> -->
<Inputli v-if="telName" v-model="telName" @input="onInputName" label="被访人" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-else v-model="form.visitingTel" @blur="onBlurTel" @input="onInputTel" label="被访人电话" :isMust="true" :isline="true" isMustPosition="left"/>
<Selectdate v-model="form.hours" label="来访时间" :isMust="true" :isline="false" :minDate="minDate"/>
</div>
</div>
<img class="absolute-img" src="../assets/woyaoyuyue.png" alt="">
</div>
<p class="tips">
说明拜访时间前后一小时都可刷脸通过门禁
</p>
<div class="visitor-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>访客信息<span>(我是谁)</span></span>
</h3>
<div class="visitor-content-form">
<Inputli v-model="form.name" label="访客姓名" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.phone" label="联系方式" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.visitingNum" label="随行人数" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.reason" label="来访事由" :isMust="true" :isline="true" isMustPosition="left"/>
<div class="upload-box">
<label for=""><span>*&nbsp;</span>人脸照片</label>
<div v-if="!form.img" class="upload-button">
<img @click="onClickUpload" src="../assets/xiangji.png" alt="">
<input v-if="isDisabled" @change="onChangeFile" type="file" accept="image/*">
</div>
<div v-else class="face-img">
<img :src="form.img" alt="">
<input v-if="true" @change="onChangeFile" type="file" accept="image/*">
</div>
</div>
</div>
</div>
<p class="tips">
说明人脸照片用于通过门禁系统请上传正脸无遮挡照片
</p>
<p class="tel-tips">
如果您在使用中遇到问题请联系<span @click="onClickCall">400040-1573</span>
</p>
<!-- <div class="disclaimer-box">
<input type="checkbox">
<p>勾选表示同意<span>用户协议</span></p>
</div> -->
<button @click="onClcikSubmit" class="submit-button " :class="isSubmit ? 'active-button' : ''">提交</button>
<Successmodel v-if="isSuccess" @click="onClickAffirm" />
<div v-if="false" class="disclaimer-model-fixed">
<h3>用户协议</h3>
<div class="disclaimer-model">
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { Toast, ImagePreview , Uploader, Dialog } from 'vant';
import { rulesForm, resetForm, rulesPhone, rulesFormFBoolean, formatDate } from '@/com/index'
import Inputli from '@/components/input-li.vue'
import Selectli from '@/components/select-li.vue'
import Selectdate from '@/components/select-li-date.vue'
import Successmodel from '../components/success.vue'
import { postApply, getVisitor, getUserNameByTel } from '@/api/user.js'
import { uploadApi } from '@/api/upload'
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
const phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/;
let form = reactive({
name: "",
phone: "",
hours: "",
reason: "",
img: "",
visitingTel: "",
visitingNum: '',
})
const isSubmit = ref(false)
const visitDict = ref([]) // 被访客数据
const visitAllDict = ref([]) // 被访客数据
const isDisabled = ref(false) // 是否能提交
const isSuccess = ref(false) // 是否提交成功
const isSelect = ref( false) // 是否显示下拉选择
const visitorName = ref( '') // 是否显示下拉选择的名字
const telName = ref( '') // 电话查询姓名
/* watch */
watch(() => form, (v1, v2) => {
isSubmit.value = rulesFormFBoolean(v2)
if(v2.name && v2.phone){
if(!phoneReg.test(v2.phone)){
isDisabled.value = false
}else{
isDisabled.value = true
}
}else{
isDisabled.value = false
}
},{ deep: true }
) // 深度监听 ) // 监听单个
/* @method */
const onInputTel = (e) =>{
}
const onInputName = (e) =>{
form.visitingTel = ''
telName.value = ''
}
const onBlurTel = (phone) =>{
console.log(phone)
if(rulesPhone(phone)){
getUserNameByTel(phone).then(res =>{
console.log(res)
if(res.data){
telName.value = res.data
}
}).catch(err =>{
console.log(err)
Dialog.alert({
message: err.msg,
}).then(() => {
// on close
});
})
}
}
// const onBlurVisitor = () =>{
// isSelect.value = false
// let item = visitAllDict.value.find(item =>{ return item.userId == form.visitingTel })
// if(item){
// visitorName.value = item.name
// visitDict.value = visitAllDict.value
// }else{
// visitorName.value = '' // 是否显示下拉选择的名字
// visitDict.value = visitAllDict.value
// }
// }
// 输入框
// const onInputVisior = (e) =>{
// visitorName.value = e.target.value
// let arr = []
// visitAllDict.value.map( item =>{
// if(item.name.search(e.target.value) != -1){
// arr.push(item)
// }
// })
// visitDict.value = [ ...arr ]
// }
// 点击选择了拜访对象
// const onClickSelect = (item) =>{
// form.visitingTel = item.userId
// visitorName.value = item.name
// isSelect.value = false
// }
// const onClickVisitor = () =>{
// isSelect.value = true
// }
// 点击确认模态框
const onClickAffirm = () =>{
router.go(-1)
}
const onClickUpload = () =>{
if(isDisabled.value){
}else{
Toast.fail('请填写访客姓名和正确的联系方式!')
}
}
const onClcikSubmit = () =>{
if(isSubmit.value){
if(rulesPhone(form.phone) && rulesPhone(form.visitingTel)){
Toast.loading({ duration: 0, forbidClick: true, message: '提交中', });
postApply(form).then(res =>{
Toast.clear();
isSuccess.value = true
})
}
}else{
Toast.fail('请先填写完整信息后提交!')
}
}
// 上传照片
const onChangeFile = (e) =>{
console.log(e.target.files[0].size)
if(e.target.files[0]){
newcompressImg(e.target.files[0], 0.5).then(res =>{
let formData = new FormData()
// formData.append('file', e.target.files[0])
formData.append('file', res.file)
formData.append('userId', form.phone)
formData.append('name', form.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
Toast.success(res.msg);
form.img = res.data
})
}).catch(err =>{
Toast.fail('图片上传失败,请重新上传')
})
}
}
const onClickCall = () =>{
window.location.href = 'tel:400-040-1573'
}
const newcompressImg = (file, quality) =>{
var qualitys = 0.52
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => this.compressImg(e,
qualitys))) // 如果是 file 数组返回 Promise 数组
} else {
return new Promise((resolve) => {
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 创建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 创建 img 元素
image.onload = async() => {
const canvas = document.createElement('canvas') // 创建 canvas 元素
const context = canvas.getContext('2d')
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
}
/* @setup */
const minDate = ref( formatDate(new Date()))
console.log(route.query.tel)
if(route.query.tel){
form.visitingTel = route.query.tel
onBlurTel(route.query.tel)
}
getVisitor().then( res=>{
visitDict.value = res.data
visitAllDict.value = res.data
})
</script>
<style lang='scss' scoped>
.box{
width: 100%;
height: 100%;
background: #F5F5F5;
padding: 0 0 1.08rem /* 81/75 */;
box-sizing: border-box;
overflow: auto;
.apply-box{
position: relative;
width: 100%;
height: 8.0267rem /* 602/75 */;
background: url('../assets/yuyuebeijing.png') left top/ 100% 100% no-repeat;
padding: 1.04rem /* 78/75 */ 0 0;
box-sizing: border-box;
.text{
width: 3.5467rem /* 266/75 */;
height: 1.1467rem /* 86/75 */;
margin-left: .6133rem /* 46/75 */;
}
p{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .1067rem /* 8/75 */ 0 0 .7467rem /* 56/75 */;
}
.apply-content{
width: 9.2rem /* 690/75 */;
height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 1.0133rem /* 76/75 */ auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.apply-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
.search-select-box{
position: relative;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin-bottom: .2667rem /* 20/75 */;
border-bottom: 0.0267rem dashed #D9D9D9;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.search-select-input{
position: relative;
height: 100%;
box-sizing: border-box;
input{
flex: 1;
border: none;
outline: none;
text-align: right;
font-size: $size26;
color: #333333;
// background: #F9F9F9;
padding-right: 0.3733rem;
// line-height: 1.5733rem /* 118/75 */;
box-sizing: border-box;
height: 100%;
}
input::-webkit-input-placeholder {
font-size: $size26;
color: #999999;
}
.select-box{
position: absolute;
right: 0;
top: 1.8667rem /* 140/75 */;
z-index: 5;
width: 3.7333rem /* 280/75 */;
// max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
border-radius: .0533rem /* 4/75 */ ;
// overflow: auto;
.select-sroll-box{
// position: absolute;
// right: 0;
// top: 1.8667rem /* 140/75 */;
// z-index: 5;
width: 100%;
max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
// box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
// border-radius: .0533rem /* 4/75 */ ;
overflow: auto;
}
.select-li{
height: 1.0133rem /* 76/75 */;
background: #fff;
border-radius: .0533rem /* 4/75 */ .0533rem /* 4/75 */ 0px 0px;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: 1.0133rem /* 76/75 */;
text-align: center;
}
.select-active{
color: #30BF78;
background: #F3FEFB;
}
}
b{
position: absolute;
left: 50%;
// top: -100%;
// width: .2rem /* 15/75 */;
// height: 20px;
// background: red;
// display: block;
width: 0;
height: 0;
border: .16rem /* 12/75 */ solid transparent;
border-bottom: .16rem /* 12/75 */ solid #fff;
transform: translateY(-100%) translateX(-50%);
// margin-top: -.6667rem /* 50/75 */;
}
}
}
}
}
.absolute-img{
position: absolute;
top: .5333rem /* 40/75 */;
right: .4rem /* 30/75 */;
width: 4.5067rem /* 338/75 */;
height: 3.7867rem /* 284/75 */;
}
}
.tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .2133rem /* 16/75 */ 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
}
.visitor-content{
width: 9.2rem /* 690/75 */;
// height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 0 auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.visitor-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ .16rem /* 12/75 */ .4533rem /* 34/75 */;
box-sizing: border-box;
.upload-box{
width: 100%;
height: 1.9733rem /* 148/75 */;
// padding: 0 .6933rem /* 52/75 */ 0 0;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
font-size: $size26;
label{
line-height: 1.9733rem /* 148/75 */;
span{
color: #FF0000;
}
}
.upload-button{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
border: .0267rem /* 2/75 */ dashed #D9D9D9;
display: flex;
align-items: center;
justify-content: center;
// overflow: hidden;
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
.face-img{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
img{
width: 100%;
height: 100%;
border-radius: .1333rem /* 10/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
}
}
}
.disclaimer-box{
display: flex;
justify-content: center;
input{
margin-right: .1333rem /* 10/75 */;
}
span{
color: #30BF78;
}
}
.tel-tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: 0 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: -.24rem /* 18/75 */;
span{
color: #409eff;
}
}
.submit-button{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #D9D9D9;
border-radius: 1.3333rem /* 100/75 */ ;
opacity: 1;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
border: none;
outline: none;
margin: 1.0133rem /* 76/75 */ auto 0;
}
.active-button{
background-color: #30BF78;
color: #fff;
}
}
.disclaimer-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
padding: .4rem /* 30/75 */;
box-sizing: border-box;
h3{
font-size: .4267rem /* 32/75 */;
line-height: .8rem /* 60/75 */;
text-align: center;
}
.disclaimer-model{
flex: 1;
line-height: .5333rem /* 40/75 */;
text-align: justify;
}
}
</style>
+720
View File
@@ -0,0 +1,720 @@
<template>
<div v-if="true" class='box'>
<div class="apply-box">
<img class="text" src="../assets/zhinengfangke.png" alt="">
<p>来访提前约 拜访开门无需按铃</p>
<div class="apply-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>拜访信息<span>(我找谁)</span></span>
</h3>
<div class="apply-content-form">
<!-- <Selectli label="被拜访对象" v-model="form.visitingTel" :columns="visitDict" :isMust="true" :isline="true" name="name" nameId="userId"/> -->
<!-- <div class="search-select-box">
<label class="label"><span>*&nbsp;</span>被拜访人</label>
<div class="search-select-input">
<input @click.stop="onClickVisitor" @input="onInputVisior" :value="visitorName" type="text" placeholder="可填写选择">
<div v-if="isSelect && visitDict.length" class="select-box">
<b></b>
<div class="select-sroll-box">
<div @click.self="onClickSelect(item)" v-for="item in visitDict" :class="item.userId == form.visitingTel ? 'select-active' : ''" class="select-li" :key="item.userId">{{ item.name }}</div>
</div>
</div>
<div v-if="isSelect && !visitDict.length" class="select-box">
<b></b>
<div class="select-li" >查无此人</div>
</div>
</div>
</div> -->
<Inputli v-if="telName" v-model="telName" @input="onInputName" label="被访人" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-else v-model="form.visitingTel" @blur="onBlurTel" @input="onInputTel" label="被访人电话" :isMust="true" :isline="true" isMustPosition="left"/>
<Selectdate v-model="form.hours" label="来访时间" :isMust="true" :isline="false" :minDate="minDate"/>
</div>
</div>
<img class="absolute-img" src="../assets/woyaoyuyue.png" alt="">
</div>
<p class="tips">
说明拜访时间前后一小时都可刷脸通过门禁
</p>
<div class="visitor-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>访客信息<span>(我是谁)</span></span>
</h3>
<div class="visitor-content-form">
<Inputli v-model="form.name" label="访客姓名" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.phone" label="联系方式" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.visitingNum" label="随行人数" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.reason" label="来访事由" :isMust="true" :isline="true" isMustPosition="left"/>
<div class="upload-box">
<label for=""><span>*&nbsp;</span>人脸照片</label>
<div v-if="!form.img" class="upload-button">
<img @click="onClickUpload" src="../assets/xiangji.png" alt="">
<input v-if="isDisabled" @change="onChangeFile" type="file" accept="image/*">
</div>
<div v-else class="face-img">
<img :src="form.img" alt="">
<input v-if="true" @change="onChangeFile" type="file" accept="image/*">
</div>
</div>
</div>
</div>
<p class="tips">
说明人脸照片用于通过门禁系统请上传正脸无遮挡照片
</p>
<p class="tel-tips">
如果您在使用中遇到问题请联系<span @click="onClickCall">400040-1573</span>
</p>
<!-- <div class="disclaimer-box">
<input type="checkbox">
<p>勾选表示同意<span>用户协议</span></p>
</div> -->
<button @click="onClcikSubmit" class="submit-button " :class="isSubmit ? 'active-button' : ''">提交</button>
<Successmodel v-if="isSuccess" @click="onClickAffirm" />
<div v-if="false" class="disclaimer-model-fixed">
<h3>用户协议</h3>
<div class="disclaimer-model">
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { Toast, ImagePreview , Uploader, Dialog } from 'vant';
import { rulesForm, resetForm, rulesPhone, rulesFormFBoolean, formatDate } from '@/com/index'
import Inputli from '@/components/input-li.vue'
import Selectli from '@/components/select-li.vue'
import Selectdate from '@/components/select-li-date.vue'
import Successmodel from '../components/success.vue'
import { postApply, getVisitor, getUserNameByTel } from '@/api/user.js'
import { uploadApi } from '@/api/upload'
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
const phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/;
let form = reactive({
name: "",
phone: "",
hours: "",
reason: "",
img: "",
visitingTel: "",
visitingNum: '',
})
const isSubmit = ref(false)
const visitDict = ref([]) // 被访客数据
const visitAllDict = ref([]) // 被访客数据
const isDisabled = ref(false) // 是否能提交
const isSuccess = ref(false) // 是否提交成功
const isSelect = ref( false) // 是否显示下拉选择
const visitorName = ref( '') // 是否显示下拉选择的名字
const telName = ref( '') // 电话查询姓名
/* watch */
watch(() => form, (v1, v2) => {
isSubmit.value = rulesFormFBoolean(v2)
if(v2.name && v2.phone){
if(!phoneReg.test(v2.phone)){
isDisabled.value = false
}else{
isDisabled.value = true
}
}else{
isDisabled.value = false
}
},{ deep: true }
) // 深度监听 ) // 监听单个
/* @method */
const onInputTel = (e) =>{
}
const onInputName = (e) =>{
form.visitingTel = ''
telName.value = ''
}
const onBlurTel = (phone) =>{
console.log(phone)
if(rulesPhone(phone)){
getUserNameByTel(phone).then(res =>{
console.log(res)
if(res.data){
telName.value = res.data
}
}).catch(err =>{
console.log(err)
Dialog.alert({
message: err.msg,
}).then(() => {
// on close
});
})
}
}
// const onBlurVisitor = () =>{
// isSelect.value = false
// let item = visitAllDict.value.find(item =>{ return item.userId == form.visitingTel })
// if(item){
// visitorName.value = item.name
// visitDict.value = visitAllDict.value
// }else{
// visitorName.value = '' // 是否显示下拉选择的名字
// visitDict.value = visitAllDict.value
// }
// }
// 输入框
// const onInputVisior = (e) =>{
// visitorName.value = e.target.value
// let arr = []
// visitAllDict.value.map( item =>{
// if(item.name.search(e.target.value) != -1){
// arr.push(item)
// }
// })
// visitDict.value = [ ...arr ]
// }
// 点击选择了拜访对象
// const onClickSelect = (item) =>{
// form.visitingTel = item.userId
// visitorName.value = item.name
// isSelect.value = false
// }
// const onClickVisitor = () =>{
// isSelect.value = true
// }
// 点击确认模态框
const onClickAffirm = () =>{
router.go(-1)
}
const onClickUpload = () =>{
if(isDisabled.value){
}else{
Toast.fail('请填写访客姓名和正确的联系方式!')
}
}
const onClcikSubmit = () =>{
if(isSubmit.value){
if(rulesPhone(form.phone) && rulesPhone(form.visitingTel)){
Toast.loading({ duration: 0, forbidClick: true, message: '提交中', });
postApply(form).then(res =>{
Toast.clear();
isSuccess.value = true
})
}
}else{
Toast.fail('请先填写完整信息后提交!')
}
}
// 上传照片
const onChangeFile = (e) =>{
console.log(e.target.files[0].size)
if(e.target.files[0]){
newcompressImg(e.target.files[0], 0.5).then(res =>{
let formData = new FormData()
// formData.append('file', e.target.files[0])
formData.append('file', res.file)
formData.append('userId', form.phone)
formData.append('name', form.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
Toast.success(res.msg);
form.img = res.data
})
}).catch(err =>{
Toast.fail('图片上传失败,请重新上传')
})
}
}
const onClickCall = () =>{
window.location.href = 'tel:400-040-1573'
}
const newcompressImg = (file, quality) =>{
var qualitys = 0.52
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => this.compressImg(e,
qualitys))) // 如果是 file 数组返回 Promise 数组
} else {
return new Promise((resolve) => {
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 创建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 创建 img 元素
image.onload = async() => {
const canvas = document.createElement('canvas') // 创建 canvas 元素
const context = canvas.getContext('2d')
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
}
/* @setup */
const minDate = ref( formatDate(new Date()))
console.log(route.query.tel)
if(route.query.tel){
form.visitingTel = route.query.tel
onBlurTel(route.query.tel)
}
getVisitor().then( res=>{
visitDict.value = res.data
visitAllDict.value = res.data
})
</script>
<style lang='scss' scoped>
.box{
width: 100%;
height: 100%;
background: #F5F5F5;
padding: 0 0 1.08rem /* 81/75 */;
box-sizing: border-box;
overflow: auto;
.apply-box{
position: relative;
width: 100%;
height: 8.0267rem /* 602/75 */;
background: url('../assets/yuyuebeijing.png') left top/ 100% 100% no-repeat;
padding: 1.04rem /* 78/75 */ 0 0;
box-sizing: border-box;
.text{
width: 3.5467rem /* 266/75 */;
height: 1.1467rem /* 86/75 */;
margin-left: .6133rem /* 46/75 */;
}
p{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .1067rem /* 8/75 */ 0 0 .7467rem /* 56/75 */;
}
.apply-content{
width: 9.2rem /* 690/75 */;
height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 1.0133rem /* 76/75 */ auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.apply-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
.search-select-box{
position: relative;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin-bottom: .2667rem /* 20/75 */;
border-bottom: 0.0267rem dashed #D9D9D9;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.search-select-input{
position: relative;
height: 100%;
box-sizing: border-box;
input{
flex: 1;
border: none;
outline: none;
text-align: right;
font-size: $size26;
color: #333333;
// background: #F9F9F9;
padding-right: 0.3733rem;
// line-height: 1.5733rem /* 118/75 */;
box-sizing: border-box;
height: 100%;
}
input::-webkit-input-placeholder {
font-size: $size26;
color: #999999;
}
.select-box{
position: absolute;
right: 0;
top: 1.8667rem /* 140/75 */;
z-index: 5;
width: 3.7333rem /* 280/75 */;
// max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
border-radius: .0533rem /* 4/75 */ ;
// overflow: auto;
.select-sroll-box{
// position: absolute;
// right: 0;
// top: 1.8667rem /* 140/75 */;
// z-index: 5;
width: 100%;
max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
// box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
// border-radius: .0533rem /* 4/75 */ ;
overflow: auto;
}
.select-li{
height: 1.0133rem /* 76/75 */;
background: #fff;
border-radius: .0533rem /* 4/75 */ .0533rem /* 4/75 */ 0px 0px;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: 1.0133rem /* 76/75 */;
text-align: center;
}
.select-active{
color: #30BF78;
background: #F3FEFB;
}
}
b{
position: absolute;
left: 50%;
// top: -100%;
// width: .2rem /* 15/75 */;
// height: 20px;
// background: red;
// display: block;
width: 0;
height: 0;
border: .16rem /* 12/75 */ solid transparent;
border-bottom: .16rem /* 12/75 */ solid #fff;
transform: translateY(-100%) translateX(-50%);
// margin-top: -.6667rem /* 50/75 */;
}
}
}
}
}
.absolute-img{
position: absolute;
top: .5333rem /* 40/75 */;
right: .4rem /* 30/75 */;
width: 4.5067rem /* 338/75 */;
height: 3.7867rem /* 284/75 */;
}
}
.tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .2133rem /* 16/75 */ 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
}
.visitor-content{
width: 9.2rem /* 690/75 */;
// height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 0 auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.visitor-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ .16rem /* 12/75 */ .4533rem /* 34/75 */;
box-sizing: border-box;
.upload-box{
width: 100%;
height: 1.9733rem /* 148/75 */;
// padding: 0 .6933rem /* 52/75 */ 0 0;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
font-size: $size26;
label{
line-height: 1.9733rem /* 148/75 */;
span{
color: #FF0000;
}
}
.upload-button{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
border: .0267rem /* 2/75 */ dashed #D9D9D9;
display: flex;
align-items: center;
justify-content: center;
// overflow: hidden;
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
.face-img{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
img{
width: 100%;
height: 100%;
border-radius: .1333rem /* 10/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
}
}
}
.disclaimer-box{
display: flex;
justify-content: center;
input{
margin-right: .1333rem /* 10/75 */;
}
span{
color: #30BF78;
}
}
.tel-tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: 0 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: -.24rem /* 18/75 */;
span{
color: #409eff;
}
}
.submit-button{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #D9D9D9;
border-radius: 1.3333rem /* 100/75 */ ;
opacity: 1;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
border: none;
outline: none;
margin: 1.0133rem /* 76/75 */ auto 0;
}
.active-button{
background-color: #30BF78;
color: #fff;
}
}
.disclaimer-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
padding: .4rem /* 30/75 */;
box-sizing: border-box;
h3{
font-size: .4267rem /* 32/75 */;
line-height: .8rem /* 60/75 */;
text-align: center;
}
.disclaimer-model{
flex: 1;
line-height: .5333rem /* 40/75 */;
text-align: justify;
}
}
</style>
+721
View File
@@ -0,0 +1,721 @@
<template>
<div v-if="true" class='box'>
<div class="apply-box">
<img class="text" src="../assets/zhinengfangke.png" alt="">
<p>来访提前约 拜访开门无需按铃</p>
<div class="apply-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>拜访信息<span>(我找谁)</span></span>
</h3>
<div class="apply-content-form">
<!-- <Selectli label="被拜访对象" v-model="form.visitingTel" :columns="visitDict" :isMust="true" :isline="true" name="name" nameId="userId"/> -->
<!-- <div class="search-select-box">
<label class="label"><span>*&nbsp;</span>被拜访人</label>
<div class="search-select-input">
<input @click.stop="onClickVisitor" @input="onInputVisior" :value="visitorName" type="text" placeholder="可填写选择">
<div v-if="isSelect && visitDict.length" class="select-box">
<b></b>
<div class="select-sroll-box">
<div @click.self="onClickSelect(item)" v-for="item in visitDict" :class="item.userId == form.visitingTel ? 'select-active' : ''" class="select-li" :key="item.userId">{{ item.name }}</div>
</div>
</div>
<div v-if="isSelect && !visitDict.length" class="select-box">
<b></b>
<div class="select-li" >查无此人</div>
</div>
</div>
</div> -->
<Inputli v-if="telName" v-model="telName" @input="onInputName" label="被访人" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-else v-model="form.visitingTel" @blur="onBlurTel" @input="onInputTel" label="被访人电话" :isMust="true" :isline="true" isMustPosition="left"/>
<Selectdate v-model="form.hours" label="来访时间" :isMust="true" :isline="false" :minDate="minDate"/>
</div>
</div>
<img class="absolute-img" src="../assets/woyaoyuyue.png" alt="">
</div>
<p class="tips">
说明拜访时间前后一小时都可刷脸通过门禁
</p>
<div class="visitor-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>访客信息<span>(我是谁)</span></span>
</h3>
<div class="visitor-content-form">
<Inputli v-model="form.name" label="访客姓名" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.phone" label="联系方式" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.visitingNum" label="随行人数" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.reason" label="来访事由" :isMust="true" :isline="true" isMustPosition="left"/>
<div class="upload-box">
<label for=""><span>*&nbsp;</span>人脸照片</label>
<div v-if="!form.img" class="upload-button">
<img @click="onClickUpload" src="../assets/xiangji.png" alt="">
<input v-if="isDisabled" @change="onChangeFile" type="file" accept="image/*">
</div>
<div v-else class="face-img">
<img :src="form.img" alt="">
<input v-if="true" @change="onChangeFile" type="file" accept="image/*">
</div>
</div>
</div>
</div>
<p class="tips">
说明人脸照片用于通过门禁系统请上传正脸无遮挡照片
</p>
<p class="tel-tips">
如果您在使用中遇到问题请联系<span @click="onClickCall">400040-1573</span>
</p>
<!-- <div class="disclaimer-box">
<input type="checkbox">
<p>勾选表示同意<span>用户协议</span></p>
</div> -->
<button @click="onClcikSubmit" class="submit-button " :class="isSubmit ? 'active-button' : ''">提交</button>
<Successmodel v-if="isSuccess" @click="onClickAffirm" />
<div v-if="false" class="disclaimer-model-fixed">
<h3>用户协议</h3>
<div class="disclaimer-model">
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { Toast, ImagePreview , Uploader, Dialog } from 'vant';
import { rulesForm, resetForm, rulesPhone, rulesFormFBoolean, formatDate } from '@/com/index'
import Inputli from '@/components/input-li.vue'
import Selectli from '@/components/select-li.vue'
import Selectdate from '@/components/select-li-date.vue'
import Successmodel from '../components/success.vue'
import { postApply, getVisitor, getUserNameByTel } from '@/api/user.js'
import { uploadApi } from '@/api/upload'
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
const phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/;
let form = reactive({
name: "",
phone: "",
hours: "",
reason: "",
img: "",
visitingTel: "",
visitingNum: '',
})
const isSubmit = ref(false)
const visitDict = ref([]) // 被访客数据
const visitAllDict = ref([]) // 被访客数据
const isDisabled = ref(false) // 是否能提交
const isSuccess = ref(false) // 是否提交成功
const isSelect = ref( false) // 是否显示下拉选择
const visitorName = ref( '') // 是否显示下拉选择的名字
const telName = ref( '') // 电话查询姓名
/* watch */
watch(() => form, (v1, v2) => {
isSubmit.value = rulesFormFBoolean(v2)
if(v2.name && v2.phone){
if(!phoneReg.test(v2.phone)){
isDisabled.value = false
}else{
isDisabled.value = true
}
}else{
isDisabled.value = false
}
},{ deep: true }
) // 深度监听 ) // 监听单个
/* @method */
const onInputTel = (e) =>{
}
const onInputName = (e) =>{
form.visitingTel = ''
telName.value = ''
}
const onBlurTel = (phone) =>{
console.log(phone)
if(rulesPhone(phone)){
getUserNameByTel(phone).then(res =>{
console.log(res)
if(res.data){
telName.value = res.data
}
}).catch(err =>{
Toast.clear();
console.log(err)
Dialog.alert({
message: err.msg,
}).then(() => {
// on close
});
})
}
}
// const onBlurVisitor = () =>{
// isSelect.value = false
// let item = visitAllDict.value.find(item =>{ return item.userId == form.visitingTel })
// if(item){
// visitorName.value = item.name
// visitDict.value = visitAllDict.value
// }else{
// visitorName.value = '' // 是否显示下拉选择的名字
// visitDict.value = visitAllDict.value
// }
// }
// 输入框
// const onInputVisior = (e) =>{
// visitorName.value = e.target.value
// let arr = []
// visitAllDict.value.map( item =>{
// if(item.name.search(e.target.value) != -1){
// arr.push(item)
// }
// })
// visitDict.value = [ ...arr ]
// }
// 点击选择了拜访对象
// const onClickSelect = (item) =>{
// form.visitingTel = item.userId
// visitorName.value = item.name
// isSelect.value = false
// }
// const onClickVisitor = () =>{
// isSelect.value = true
// }
// 点击确认模态框
const onClickAffirm = () =>{
router.go(-1)
}
const onClickUpload = () =>{
if(isDisabled.value){
}else{
Toast.fail('请填写访客姓名和正确的联系方式!')
}
}
const onClcikSubmit = () =>{
if(isSubmit.value){
if(rulesPhone(form.phone) && rulesPhone(form.visitingTel)){
Toast.loading({ duration: 0, forbidClick: true, message: '提交中', });
postApply(form).then(res =>{
Toast.clear();
isSuccess.value = true
})
}
}else{
Toast.fail('请先填写完整信息后提交!')
}
}
// 上传照片
const onChangeFile = (e) =>{
console.log(e.target.files[0].size)
if(e.target.files[0]){
newcompressImg(e.target.files[0], 0.5).then(res =>{
let formData = new FormData()
// formData.append('file', e.target.files[0])
formData.append('file', res.file)
formData.append('userId', form.phone)
formData.append('name', form.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
Toast.success(res.msg);
form.img = res.data
})
}).catch(err =>{
Toast.fail('图片上传失败,请重新上传')
})
}
}
const onClickCall = () =>{
window.location.href = 'tel:400-040-1573'
}
const newcompressImg = (file, quality) =>{
var qualitys = 0.52
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => this.compressImg(e,
qualitys))) // 如果是 file 数组返回 Promise 数组
} else {
return new Promise((resolve) => {
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 创建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 创建 img 元素
image.onload = async() => {
const canvas = document.createElement('canvas') // 创建 canvas 元素
const context = canvas.getContext('2d')
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
}
/* @setup */
const minDate = ref( formatDate(new Date()))
console.log(route.query.tel)
if(route.query.tel){
form.visitingTel = route.query.tel
onBlurTel(route.query.tel)
}
getVisitor().then( res=>{
visitDict.value = res.data
visitAllDict.value = res.data
})
</script>
<style lang='scss' scoped>
.box{
width: 100%;
height: 100%;
background: #F5F5F5;
padding: 0 0 1.08rem /* 81/75 */;
box-sizing: border-box;
overflow: auto;
.apply-box{
position: relative;
width: 100%;
height: 8.0267rem /* 602/75 */;
background: url('../assets/yuyuebeijing.png') left top/ 100% 100% no-repeat;
padding: 1.04rem /* 78/75 */ 0 0;
box-sizing: border-box;
.text{
width: 3.5467rem /* 266/75 */;
height: 1.1467rem /* 86/75 */;
margin-left: .6133rem /* 46/75 */;
}
p{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .1067rem /* 8/75 */ 0 0 .7467rem /* 56/75 */;
}
.apply-content{
width: 9.2rem /* 690/75 */;
height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 1.0133rem /* 76/75 */ auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.apply-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
.search-select-box{
position: relative;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin-bottom: .2667rem /* 20/75 */;
border-bottom: 0.0267rem dashed #D9D9D9;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.search-select-input{
position: relative;
height: 100%;
box-sizing: border-box;
input{
flex: 1;
border: none;
outline: none;
text-align: right;
font-size: $size26;
color: #333333;
// background: #F9F9F9;
padding-right: 0.3733rem;
// line-height: 1.5733rem /* 118/75 */;
box-sizing: border-box;
height: 100%;
}
input::-webkit-input-placeholder {
font-size: $size26;
color: #999999;
}
.select-box{
position: absolute;
right: 0;
top: 1.8667rem /* 140/75 */;
z-index: 5;
width: 3.7333rem /* 280/75 */;
// max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
border-radius: .0533rem /* 4/75 */ ;
// overflow: auto;
.select-sroll-box{
// position: absolute;
// right: 0;
// top: 1.8667rem /* 140/75 */;
// z-index: 5;
width: 100%;
max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
// box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
// border-radius: .0533rem /* 4/75 */ ;
overflow: auto;
}
.select-li{
height: 1.0133rem /* 76/75 */;
background: #fff;
border-radius: .0533rem /* 4/75 */ .0533rem /* 4/75 */ 0px 0px;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: 1.0133rem /* 76/75 */;
text-align: center;
}
.select-active{
color: #30BF78;
background: #F3FEFB;
}
}
b{
position: absolute;
left: 50%;
// top: -100%;
// width: .2rem /* 15/75 */;
// height: 20px;
// background: red;
// display: block;
width: 0;
height: 0;
border: .16rem /* 12/75 */ solid transparent;
border-bottom: .16rem /* 12/75 */ solid #fff;
transform: translateY(-100%) translateX(-50%);
// margin-top: -.6667rem /* 50/75 */;
}
}
}
}
}
.absolute-img{
position: absolute;
top: .5333rem /* 40/75 */;
right: .4rem /* 30/75 */;
width: 4.5067rem /* 338/75 */;
height: 3.7867rem /* 284/75 */;
}
}
.tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .2133rem /* 16/75 */ 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
}
.visitor-content{
width: 9.2rem /* 690/75 */;
// height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 0 auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.visitor-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ .16rem /* 12/75 */ .4533rem /* 34/75 */;
box-sizing: border-box;
.upload-box{
width: 100%;
height: 1.9733rem /* 148/75 */;
// padding: 0 .6933rem /* 52/75 */ 0 0;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
font-size: $size26;
label{
line-height: 1.9733rem /* 148/75 */;
span{
color: #FF0000;
}
}
.upload-button{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
border: .0267rem /* 2/75 */ dashed #D9D9D9;
display: flex;
align-items: center;
justify-content: center;
// overflow: hidden;
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
.face-img{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
img{
width: 100%;
height: 100%;
border-radius: .1333rem /* 10/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
}
}
}
.disclaimer-box{
display: flex;
justify-content: center;
input{
margin-right: .1333rem /* 10/75 */;
}
span{
color: #30BF78;
}
}
.tel-tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: 0 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: -.24rem /* 18/75 */;
span{
color: #409eff;
}
}
.submit-button{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #D9D9D9;
border-radius: 1.3333rem /* 100/75 */ ;
opacity: 1;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
border: none;
outline: none;
margin: 1.0133rem /* 76/75 */ auto 0;
}
.active-button{
background-color: #30BF78;
color: #fff;
}
}
.disclaimer-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
padding: .4rem /* 30/75 */;
box-sizing: border-box;
h3{
font-size: .4267rem /* 32/75 */;
line-height: .8rem /* 60/75 */;
text-align: center;
}
.disclaimer-model{
flex: 1;
line-height: .5333rem /* 40/75 */;
text-align: justify;
}
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class='box'>
<div class="content">
<img class="icon-h1" src="../assets/yuyuejilu.png" alt="">
<div class="title">请输入预约时填写的手机号</div>
<p>手机号用于查询所提交的预约记录</p>
<input @input="onInputPhone" v-model="phone" placeholder="输入手机号" :maxlength="11" />
<button @click="onClickQuery" :class=" phone.length >= 11 ? 'active-button' : '' " class="query-button">查询</button>
<!-- <p v-for="item in 90">54444444444444</p> -->
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import { postApply, getVisitor, getByPhone } from '@/api/user.js'
import { Toast, ImagePreview } from 'vant';
/* @data */
const router = useRouter()
const store = useStore()
let phone = ref('')
/* @setup */
const getStorage = localStorage.getItem('iphone')
if(getStorage){
phone.value = getStorage
}
/* @method */
const onInputPhone = (e) =>{
// console.log(e.target.value.length)
}
const onClickQuery = () =>{
if(phone.value.length >= 11){
if( rulesPhone(phone.value)){
console.log('可以提交')
localStorage.setItem('iphone', phone.value)
let obj = { phone: phone.value }
Toast.loading({ duration: 0, forbidClick: true, message: '查询中', });
getByPhone(obj).then(res =>{
console.log(res)
Toast.clear()
router.replace('/applyRecord/'+phone.value)
}).catch(err =>{
console.log(err)
})
}
}
}
</script>
<style lang='scss' scoped>
.box{
// position: relative;
// display: flex;
// flex-direction: column;
overflow: auto;
align-items: center;
padding: 2rem /* 150/75 */ 0 0;
box-sizing: border-box;
.icon-h1{
width: 5.3333rem /* 400/75 */;
height: 4rem /* 300/75 */;
margin: 0 auto;
}
.title{
line-height: .72rem /* 54/75 */;
font-size: .48rem /* 36/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #222222;
margin: .4533rem /* 34/75 */ auto .16rem /* 12/75 */;
// margin: 0 auto;
text-align: center;
}
p{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
text-align: center;
}
input{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #F5F5F5;
border-radius: 1.3333rem /* 100/75 */;
margin: .9067rem /* 68/75 */ auto 0;
font-size: $md-size;
border: none;
outline: none;
padding: 0 .56rem /* 42/75 */;
box-sizing: border-box;
}
input::-webkit-input-placeholder{
color: #999999;
}
.query-button{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #D9D9D9;
border-radius: 1.3333rem /* 100/75 */ ;
color: #222222;
border: none;
font-size: $size30;
margin: .64rem /* 48/75 */ auto 0;
}
.active-button{
background-color: #30BF78;
color: #fff;
}
}
</style>
+196
View File
@@ -0,0 +1,196 @@
<template>
<div class='box' @scroll="onScroll">
<div v-for="item in recordArr" class="apply-list" :key="item.id">
<h3 class="h3">
<span>提交时间{{ item.createTime }}</span>
<!-- refuse拒绝 pass通过 状态 -->
<div class="status" :class="stateEnum[item.status].className">{{ stateEnum[item.status].statusName }}</div>
</h3>
<div class="apply-content">
<div class="user-info">
<img src="../assets/lanseyuanquan.png" alt="">
<span>拜访人{{ item.name }}</span>
</div>
<div class="user-info">
<img src="../assets/huangseyuanquan.png" alt="">
<span>拜访时间{{ item.hours }}</span>
</div>
<p>
{{ item.status == 2 && item.remark ? item.remark : stateEnum[item.status].statusText }}
<span class="triangle"></span>
</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import { postApply, getVisitor, getByPhone } from '@/api/user.js'
import { Toast, ImagePreview } from 'vant';
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
// console.log(route.params.iphone)
const query = reactive({
// phone: route.params.iphone,
current: 1,
size: 10
})
const total = ref()
const recordArr = ref([])
const stateEnum = store.state.stateEnum
/* @setup */
console.log(localStorage.getItem('userToken'))
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
console.log(windowHeight)
getByPhone(query).then(res =>{
// console.log(res)
recordArr.value = res.data
total.value = res.total
// alert(windowHeight)
}).catch(err =>{
console.log(err)
})
/* @method */
let old = 0
const onScroll = (e) =>{
if(e.target.scrollTop > old){ // 说明是下拉
if(e.target.scrollHeight <= windowHeight + Math.ceil(e.target.scrollTop) ){ // 说明到底了
console.log('到底了')
if(recordArr.value.length < total.value ){
query.current++
getList()
}else{
Toast.success('数据已全部加载完成!');
}
}
}else{ // 说明是上拉
}
old = e.target.scrollTop
// setTimeout(()=>{
// alert(e.target.scrollHeight - e.target.scrollTop)
// },2000)
}
const getList = () =>{
Toast.loading({ duration: 0, forbidClick: true, message: '加载中', });
getByPhone(query).then(res =>{
// console.log(res)
recordArr.value = [ ...recordArr.value , ...res.data]
total.value = res.total
Toast.clear();
}).catch(err =>{
query.current--
console.log(err)
})
}
</script>
<style lang='scss' scoped>
.box{
width: 100%;
height: 100vh;
overflow: auto;
background: #EDEDED;
padding: .4rem /* 30/75 */;
box-sizing: border-box;
.apply-list{
width: 100%;
// height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
padding: 0 .4rem /* 30/75 */ .5867rem /* 44/75 */;
box-sizing: border-box;
margin-bottom: .4rem /* 30/75 */;
.h3{
width: 100%;
height: 1.2267rem /* 92/75 */;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: .0267rem /* 2/75 */ dashed #D9D9D9;
padding: 0 .1067rem /* 8/75 */;
box-sizing: border-box;
span{
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
}
.status{
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #448CF7;
}
.pass{
color: #18C297;
}
.refuse{
color: #FF6643;
}
}
.apply-content{
width: 100%;
padding: .5067rem /* 38/75 */ 0 0;
.user-info{
padding: 0 .24rem /* 18/75 */;
box-sizing: border-box;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #000000;
display: flex;
align-items: center;
height: .56rem /* 42/75 */;
margin-bottom: .2133rem /* 16/75 */;
img{
width: .2667rem /* 20/75 */;
height: .2667rem /* 20/75 */;
margin-right: .2133rem /* 16/75 */;
}
}
p{
position: relative;
width: 8.2133rem /* 616/75 */;
padding: .2133rem /* 16/75 */ .4533rem /* 34/75 */;
box-sizing: border-box;
line-height: .48rem /* 36/75 */;
// height: .9067rem /* 68/75 */;
background: #EAEEF6;
// background: #fff;
border-radius: .1333rem /* 10/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
margin-top: .5067rem /* 38/75 */;
.triangle{
position: absolute;
top: -.08rem /* 6/75 */;
left: .5333rem /* 40/75 */;
width: .2rem /* 15/75 */;
height: .2rem /* 15/75 */;
transform: rotateZ(-45deg);
background: #EAEEF6;
}
}
}
}
}
</style>
+786
View File
@@ -0,0 +1,786 @@
<template>
<div v-if="true" class='box'>
<div class="apply-box">
<img class="text" src="../assets/zhinengfangke.png" alt="">
<p>来访提前约 拜访开门无需按铃</p>
<img class="absolute-img" src="../assets/woyaoyuyue.png" alt="">
<div class="apply-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>拜访信息<span>(我找谁)</span></span>
</h3>
<div class="apply-content-form">
<Inputli v-model="form.name" label="访客姓名" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.phone" label="联系方式" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.visitingNum" label="随行人数" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-model="form.reason" label="来访事由" :isMust="true" :isline="true" isMustPosition="left"/>
<div class="upload-box">
<label for=""><span>*&nbsp;</span>人脸照片</label>
<div v-if="!form.img" class="upload-button">
<img @click="onClickUpload" src="../assets/xiangji.png" alt="">
<input v-if="isDisabled" @change="onChangeFile" type="file" accept="image/*">
</div>
<div v-else class="face-img">
<img :src="form.img" alt="">
<input v-if="true" @change="onChangeFile" type="file" accept="image/*">
</div>
</div>
</div>
</div>
</div>
<p class="tips">
说明人脸照片用于通过门禁系统请上传正脸无遮挡照片
</p>
<p class="tel-tips">
如果您在使用中遇到问题请联系<span @click="onClickCall">400040-1573</span>
</p>
<div class="visitor-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>访客信息<span>(我是谁)</span></span>
</h3>
<div class="visitor-content-form">
<Inputli v-if="telName" v-model="telName" @input="onInputName" label="被访人" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-else v-model="form.visitingTel" @blur="onBlurTel" @input="onInputTel" label="被访人电话" :isMust="true" :isline="true" isMustPosition="left"/>
<Selectdate v-model="form.hours" label="来访时间" :isMust="true" :isline="false" :minDate="minDate"/>
</div>
</div>
<p class="tips" style="margin-top: .2rem /* 15/75 */">
说明拜访时间前后一小时都可刷脸通过门禁
</p>
<!-- <p class="tips">
说明拜访时间前后一小时都可刷脸通过门禁
</p>
<div v-if="false" class="visitor-content">
<h3>
<img src="../assets/icon-shenfenzheng.png" alt="">
<span>访客信息<span>(我是谁)</span></span>
</h3>
<div class="visitor-content-form">
<Inputli v-if="telName" v-model="telName" @input="onInputName" label="被访人" :isMust="true" :isline="true" isMustPosition="left"/>
<Inputli v-else v-model="form.visitingTel" @blur="onBlurTel" @input="onInputTel" label="被访人电话" :isMust="true" :isline="true" isMustPosition="left"/>
<Selectdate v-model="form.hours" label="来访时间" :isMust="true" :isline="false" :minDate="minDate"/>
</div>
</div>
<p class="tips">
说明人脸照片用于通过门禁系统请上传正脸无遮挡照片
</p>
<p class="tel-tips">
如果您在使用中遇到问题请联系<span @click="onClickCall">400040-1573</span>
</p> -->
<!-- <div class="disclaimer-box">
<input type="checkbox">
<p>勾选表示同意<span>用户协议</span></p>
</div> -->
<button @click="onClcikSubmit" class="submit-button " :class="isSubmit ? 'active-button' : ''">提交</button>
<Successmodel v-if="isSuccess" @click="onClickAffirm" />
<div v-if="false" class="disclaimer-model-fixed">
<h3>用户协议</h3>
<div class="disclaimer-model">
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
阿哈撒发哈师范哈撒发哈很舒服哈哈
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { Toast, ImagePreview , Uploader, Dialog } from 'vant';
import { rulesForm, resetForm, rulesPhone, rulesFormFBoolean, formatDate } from '@/com/index'
import Inputli from '@/components/input-li.vue'
import Selectli from '@/components/select-li.vue'
import Selectdate from '@/components/select-li-date.vue'
import Successmodel from '../components/success.vue'
import { postApply, getVisitor, getUserNameByTel } from '@/api/user.js'
import { uploadApi } from '@/api/upload'
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
const phoneReg = /(^1\d{10}$)|(^[0-9]\d{7}$)/;
let form = reactive({
name: "",
phone: "",
hours: "",
reason: "",
img: "",
visitingTel: "",
visitingNum: '',
})
const isSubmit = ref(false)
const visitDict = ref([]) // 被访客数据
const visitAllDict = ref([]) // 被访客数据
const isDisabled = ref(false) // 是否能提交
const isSuccess = ref(false) // 是否提交成功
const isSelect = ref( false) // 是否显示下拉选择
const visitorName = ref( '') // 是否显示下拉选择的名字
const telName = ref( '') // 电话查询姓名
/* watch */
watch(() => form, (v1, v2) => {
isSubmit.value = rulesFormFBoolean(v2)
if(v2.name && v2.phone){
if(!phoneReg.test(v2.phone)){
isDisabled.value = false
}else{
isDisabled.value = true
}
}else{
isDisabled.value = false
}
},{ deep: true }
) // 深度监听 ) // 监听单个
/* @method */
const onInputTel = (e) =>{
}
const onInputName = (e) =>{
form.visitingTel = ''
telName.value = ''
}
const onBlurTel = (phone) =>{
console.log(phone)
if(rulesPhone(phone)){
getUserNameByTel(phone).then(res =>{
console.log(res)
if(res.data){
telName.value = res.data
}
}).catch(err =>{
console.log(err)
Dialog.alert({
message: err.msg,
}).then(() => {
// on close
});
})
}
}
// const onBlurVisitor = () =>{
// isSelect.value = false
// let item = visitAllDict.value.find(item =>{ return item.userId == form.visitingTel })
// if(item){
// visitorName.value = item.name
// visitDict.value = visitAllDict.value
// }else{
// visitorName.value = '' // 是否显示下拉选择的名字
// visitDict.value = visitAllDict.value
// }
// }
// 输入框
// const onInputVisior = (e) =>{
// visitorName.value = e.target.value
// let arr = []
// visitAllDict.value.map( item =>{
// if(item.name.search(e.target.value) != -1){
// arr.push(item)
// }
// })
// visitDict.value = [ ...arr ]
// }
// 点击选择了拜访对象
// const onClickSelect = (item) =>{
// form.visitingTel = item.userId
// visitorName.value = item.name
// isSelect.value = false
// }
// const onClickVisitor = () =>{
// isSelect.value = true
// }
// 点击确认模态框
const onClickAffirm = () =>{
router.go(-1)
}
const onClickUpload = () =>{
if(isDisabled.value){
}else{
Toast.fail('请填写访客姓名和正确的联系方式!')
}
}
const onClcikSubmit = () =>{
if(isSubmit.value){
if(rulesPhone(form.phone) && rulesPhone(form.visitingTel)){
Toast.loading({ duration: 0, forbidClick: true, message: '提交中', });
postApply(form).then(res =>{
Toast.clear();
isSuccess.value = true
})
}
}else{
Toast.fail('请先填写完整信息后提交!')
}
}
// 上传照片
const onChangeFile = (e) =>{
console.log(e.target.files[0].size)
if(e.target.files[0]){
newcompressImg(e.target.files[0], 0.5).then(res =>{
let formData = new FormData()
// formData.append('file', e.target.files[0])
formData.append('file', res.file)
formData.append('userId', form.phone)
formData.append('name', form.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
Toast.success(res.msg);
form.img = res.data
})
}).catch(err =>{
Toast.fail('图片上传失败,请重新上传')
})
}
}
const onClickCall = () =>{
window.location.href = 'tel:400-040-1573'
}
const newcompressImg = (file, quality) =>{
var qualitys = 0.52
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => this.compressImg(e,
qualitys))) // 如果是 file 数组返回 Promise 数组
} else {
return new Promise((resolve) => {
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 创建 FileReader
reader.onload = ({
target: {
result: src
}
}) => {
const image = new Image() // 创建 img 元素
image.onload = async() => {
const canvas = document.createElement('canvas') // 创建 canvas 元素
const context = canvas.getContext('2d')
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) && parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
const canvasURL = canvas.toDataURL('image/jpeg', qualitys)
const buffer = atob(canvasURL.split(',')[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: 'image/jpeg'
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
}
/* @setup */
const minDate = ref( formatDate(new Date()))
console.log(route.query.tel)
if(route.query.tel){
form.visitingTel = route.query.tel
onBlurTel(route.query.tel)
}
getVisitor().then( res=>{
visitDict.value = res.data
visitAllDict.value = res.data
})
</script>
<style lang='scss' scoped>
.box{
width: 100%;
height: 100%;
background: #F5F5F5;
padding: 0 0 1.08rem /* 81/75 */;
box-sizing: border-box;
overflow: auto;
.apply-box{
position: relative;
width: 100%;
height: 8.0267rem /* 602/75 */;
background: url('../assets/yuyuebeijing.png') left top/ 100% 100% no-repeat;
padding: 1.04rem /* 78/75 */ 0 0;
box-sizing: border-box;
.text{
width: 3.5467rem /* 266/75 */;
height: 1.1467rem /* 86/75 */;
margin-left: .6133rem /* 46/75 */;
}
p{
line-height: .48rem /* 36/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .1067rem /* 8/75 */ 0 0 .7467rem /* 56/75 */;
}
.apply-content{
width: 9.2rem /* 690/75 */;
// height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 1.0133rem /* 76/75 */ auto 0;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.apply-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
.search-select-box{
position: relative;
width: 100%;
height: 1.5733rem /* 118/75 */;
// background: #F9F9F9;
// border-radius: .2667rem /* 20/75 */;
padding: 0 .0267rem /* 2/75 */ 0;
box-sizing: border-box;
display: flex;
// margin-bottom: .2667rem /* 20/75 */;
border-bottom: 0.0267rem dashed #D9D9D9;
.label{
flex: 1;
font-size: $size26;
color: #333333;
line-height: 1.5733rem /* 118/75 */;
span{
color: #FF0000;
margin-right: .0267rem /* 2/75 */;
}
}
.search-select-input{
position: relative;
height: 100%;
box-sizing: border-box;
input{
flex: 1;
border: none;
outline: none;
text-align: right;
font-size: $size26;
color: #333333;
// background: #F9F9F9;
padding-right: 0.3733rem;
// line-height: 1.5733rem /* 118/75 */;
box-sizing: border-box;
height: 100%;
}
input::-webkit-input-placeholder {
font-size: $size26;
color: #999999;
}
.select-box{
position: absolute;
right: 0;
top: 1.8667rem /* 140/75 */;
z-index: 5;
width: 3.7333rem /* 280/75 */;
// max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
border-radius: .0533rem /* 4/75 */ ;
// overflow: auto;
.select-sroll-box{
// position: absolute;
// right: 0;
// top: 1.8667rem /* 140/75 */;
// z-index: 5;
width: 100%;
max-height: 5.0667rem /* 380/75 */;
background: #FFFFFF;
// box-shadow: 0px 0px .2667rem /* 20/75 */ .1067rem /* 8/75 */ rgba(0,0,0,0.1);
// border-radius: .0533rem /* 4/75 */ ;
overflow: auto;
}
.select-li{
height: 1.0133rem /* 76/75 */;
background: #fff;
border-radius: .0533rem /* 4/75 */ .0533rem /* 4/75 */ 0px 0px;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: 1.0133rem /* 76/75 */;
text-align: center;
}
.select-active{
color: #30BF78;
background: #F3FEFB;
}
}
b{
position: absolute;
left: 50%;
// top: -100%;
// width: .2rem /* 15/75 */;
// height: 20px;
// background: red;
// display: block;
width: 0;
height: 0;
border: .16rem /* 12/75 */ solid transparent;
border-bottom: .16rem /* 12/75 */ solid #fff;
transform: translateY(-100%) translateX(-50%);
// margin-top: -.6667rem /* 50/75 */;
}
}
}
.upload-box{
width: 100%;
height: 1.9733rem /* 148/75 */;
// padding: 0 .6933rem /* 52/75 */ 0 0;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
font-size: $size26;
label{
line-height: 1.9733rem /* 148/75 */;
span{
color: #FF0000;
}
}
.upload-button{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
border: .0267rem /* 2/75 */ dashed #D9D9D9;
display: flex;
align-items: center;
justify-content: center;
// overflow: hidden;
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
.face-img{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
img{
width: 100%;
height: 100%;
border-radius: .1333rem /* 10/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
}
}
}
.absolute-img{
position: absolute;
top: .5333rem /* 40/75 */;
right: .4rem /* 30/75 */;
width: 4.5067rem /* 338/75 */;
height: 3.7867rem /* 284/75 */;
}
}
.tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .2133rem /* 16/75 */ 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: 5.3333rem /* 400/75 */;
}
.visitor-content{
width: 9.2rem /* 690/75 */;
// height: 4.32rem /* 324/75 */;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
margin: 0 auto 0;
// margin-top: 5.3333rem /* 400/75 */;
h3{
padding: .5067rem /* 38/75 */ .3467rem /* 26/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
display: flex;
align-items: center;
color: #222222;
font-size: $size30;
img{
width: .5867rem /* 44/75 */;
height: .5333rem /* 40/75 */;
margin-right: .16rem /* 12/75 */;
}
span{
span{
font-size: .32rem /* 24/75 */;
color: #666;
}
}
}
.visitor-content-form{
width: 100%;
padding: 0 .3467rem /* 26/75 */ .16rem /* 12/75 */ .4533rem /* 34/75 */;
box-sizing: border-box;
.upload-box{
width: 100%;
height: 1.9733rem /* 148/75 */;
// padding: 0 .6933rem /* 52/75 */ 0 0;
padding-right: .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
font-size: $size26;
label{
line-height: 1.9733rem /* 148/75 */;
span{
color: #FF0000;
}
}
.upload-button{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
border: .0267rem /* 2/75 */ dashed #D9D9D9;
display: flex;
align-items: center;
justify-content: center;
// overflow: hidden;
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
.face-img{
position: relative;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
background: #FFFFFF;
border-radius: .1333rem /* 10/75 */;
img{
width: 100%;
height: 100%;
border-radius: .1333rem /* 10/75 */;
}
input{
position: absolute;
left: 0;
top: 0;
width: 1.3333rem /* 100/75 */;
height: 1.3333rem /* 100/75 */;
opacity: 0;
}
}
}
}
}
.disclaimer-box{
display: flex;
justify-content: center;
input{
margin-right: .1333rem /* 10/75 */;
}
span{
color: #30BF78;
}
}
.tel-tips{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: 0 0 .4267rem /* 32/75 */ .8rem /* 60/75 */;
box-sizing: border-box;
margin-top: -.24rem /* 18/75 */;
span{
color: #409eff;
}
}
.submit-button{
display: block;
width: 6.72rem /* 504/75 */;
height: 1.12rem /* 84/75 */;
background: #D9D9D9;
border-radius: 1.3333rem /* 100/75 */ ;
opacity: 1;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
border: none;
outline: none;
margin: 1.0133rem /* 76/75 */ auto 0;
}
.active-button{
background-color: #30BF78;
color: #fff;
}
}
.disclaimer-model-fixed{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
padding: .4rem /* 30/75 */;
box-sizing: border-box;
h3{
font-size: .4267rem /* 32/75 */;
line-height: .8rem /* 60/75 */;
text-align: center;
}
.disclaimer-model{
flex: 1;
line-height: .5333rem /* 40/75 */;
text-align: justify;
}
}
</style>
+404
View File
@@ -0,0 +1,404 @@
<template>
<div class="box">
<header>
<h2>门禁人脸信息</h2>
<h3> <img src="../assets/img/dunpai.png" alt=""> 公司门禁系统人脸上传和查看 </h3>
<div class="entering-box">
<img src="../assets/img/shenfenzheng.png" alt="">
<div class="entering-center">
<p>特殊人员录入</p>
<p>暂未进入公司钉钉组织人员</p>
</div>
<button @click="onClickGoSpecialCrowd" class="entering-button">录入</button>
</div>
</header>
<div class="content">
<h2>通讯录</h2>
<div class="content-box" >
<div class="nav">
<div @click="onClickDept(item.deptId)" v-for="item in deptArr" class="li" :class="item.deptId == deptId ? 'active' : ''" :style=" item.name.length >= 6 ? 'padding-right: 0.28rem;' : '' " :key="item.userId">
{{ item.name }}
</div>
</div>
<div class="user-content">
<div class="user-scroll">
<div v-for="item in userArr" class="user-list">
<div @click="onClickPreviewImg(item.img)" class="img-box">
<img v-if="item.img" :src="item.img" alt="">
</div>
<div class="name">{{ item.name }}</div>
<div @click="onClcikUser(item)" v-if="item.img" class="button">查看</div>
<div @click="onClcikUser(item)" v-else class="button active-button">上传</div>
</div>
</div>
</div>
</div>
</div>
</div>
<Facemodel @change="onChangeFile" @cancel="isFace = false; form.face =''" @save="onClciksave" v-if="isFace" :faceImg="form.face" />
</template>
<script setup>
import { rulesPhone, compareTime, timeDiff,formatDate } from '../com/index.js'
// import { getShop, getKind } from '../api/user.js'
import { reactive, ref } from 'vue'
import { Toast, ImagePreview, Skeleton } from 'vant';
import dd from 'dingtalk-jsapi'
import { getToken, getDept, getUserListByDept, putUserId } from '@/api/doorAdmin.js'
import Facemodel from '@/components/face-model.vue'
import { uploadApi } from '@/api/upload'
import { useRouter } from 'vue-router';
const router = useRouter()
const deptArr = ref([]) // 部门数据
const userArr = ref([]) // 用户
const deptId = ref('') // 选中的部门Id
const isFace = ref(false)
const form = reactive({ //
face: '',
userId: '',
name: ''
})
const token = ref('')
const isLoading = ref(false)
localStorage.setItem('redirect', '/home')
token.value = localStorage.getItem('token')
// setTimeout( ()=>{
// form.face = 'https://img0.baidu.com/it/u=3229206321,1418550973&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500'
// }, 2000)
// method
// 点击预栏图片
const onClickPreviewImg = (img) =>{
dd.previewImage({
urls: [img
],
current: 1,
success: () => {},
fail: () => {},
complete: () => {},
});
}
// 点击切换部门
const onClickDept = (Id) =>{
if(deptId.value != Id){
deptId.value = Id
userList()
}
}
// 点击上传了文件
const onChangeFile = (file) =>{
console.log(file)
if(file){
let reader = new FileReader()
reader.readAsDataURL(file) // 转base64
reader.onload = () =>{ // 简化代码 - 转换完成
form.face = reader.result
}
}
}
// 点击保存
const onClciksave = (file) =>{
if(form.face && file){
let formData = new FormData()
formData.append('file', file)
formData.append('userId', form.userId)
formData.append('name', form.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
form.face = res.data
let obj = { userId: form.userId, img: form.face }
putUserId(obj).then(response =>{
userList()
isFace.value = false
Toast.success(response.msg);
})
})
}else if(form.face && !file ){ // 说明之前有数据现在没传
isFace.value = false
}else if(!form.face && !file){
Toast.fail('请先上传照片!');
}
// if(file){ // 文件是否上传了
// let formData = new FormData()
// formData.append('face', file)
// formData.append('userId', form.userId)
// formData.append('name', form.name)
// Toast.loading({ duration: 0, forbidClick: true, message: '保存中', });
// uploadApi(formData).then(res =>{
// Toast.success(res.msg);
// form.img = res.data
// })
// }else{
// Toast.fail('请先上传照片!');
// }
}
// 点击用户上传照片或查看
const onClcikUser = (item) =>{
console.log(item)
isFace.value = true
form.face = item.img
form.userId = item.userId
form.name = item.name
}
const login = () =>{
Toast.loading({ duration: 0, forbidClick: true, message: '登录中', });
dd.getAuthCode({
corpId: 'ding03f8e88dde9b426835c2f4657eb6378f',
success: (res) => {
const { code } = res;
localStorage.removeItem('token');
localStorage.removeItem('user');
getToken(code).then(data =>{
localStorage.setItem('user', data.data)
localStorage.setItem('token', data.data.authorization)
Toast.clear()
Toast.success('登录成功')
deptList()
})
}
});
}
// 获取部门用户
const userList = () =>{
isLoading.value = false
getUserListByDept(deptId.value).then(res =>{
isLoading.value = true
userArr.value = res.data
})
}
// 获取部门列表
const deptList = () =>{
getDept().then(res =>{
deptArr.value = res.data
deptId.value = res.data[0].deptId
userList()
})
}
// 跳转特殊人员
const onClickGoSpecialCrowd = () =>{
router.push('/specialCrowd')
}
if(token.value){
// localStorage.setItem('token', 123)
deptList()
}else{
login()
}
</script>
<style lang="scss" scoped>
.box{
display: flex;
flex-direction:column ;
header{
width: 100%;
height: 5.8933rem /* 442/75 */;
background: url('../assets/img/shouyebeijing.png') left top/100% 100% no-repeat;
h2{
line-height: 1.12rem /* 84/75 */;
font-size: .7467rem /* 56/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #FFFFFF;
padding: .8533rem /* 64/75 */ 0 0 .64rem /* 48/75 */;
box-sizing: border-box;
}
h3{
display: flex;
padding: .32rem /* 24/75 */ 0 0 .7467rem /* 56/75 */;
box-sizing: border-box;
line-height: .5333rem /* 40/75 */;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FFFFFF;
img{
width: .5333rem /* 40/75 */;
height: .5333rem /* 40/75 */;
margin-right: .1067rem /* 8/75 */;
}
}
.entering-box{
width: 8rem /* 600/75 */;
height: 1.6rem /* 120/75 */;
margin: .6667rem /* 50/75 */ auto 0;
background: #202F44;
border-radius: .5333rem /* 40/75 */ .5333rem /* 40/75 */ 0px 0px;
padding: 0 .4rem /* 30/75 */ .8rem /* 60/75 */ .4533rem /* 34/75 */;
display: flex;
align-items: center;
img{
width: .6133rem /* 46/75 */;
height: .48rem /* 36/75 */;
margin-right: .2933rem /* 22/75 */;
}
.entering-center{
flex: 1;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #FFFFFF;
p:nth-child(1){
line-height: .56rem /* 42/75 */;
padding: .2933rem /* 22/75 */ 0 0;
}
p:nth-child(2){
line-height: .4533rem /* 34/75 */;
font-size: $sm-size;
font-weight: 400;
color: #B6B7B8;
}
}
.entering-button{
width: 1.4667rem /* 110/75 */;
height: .6933rem /* 52/75 */;
background: #F6C976;
border-radius: .5333rem /* 40/75 */;
outline: none;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #202F44;
border: none;
}
}
}
.content{
width: 100%;
background: #FFFFFF;
border-radius: .5333rem /* 40/75 */ .5333rem /* 40/75 */ 0px 0px;
margin-top: -20px;
overflow: auto;
flex: 1;
display: flex;
flex-direction: column ;
h2{
padding: .4267rem /* 32/75 */ .48rem /* 36/75 */;
box-sizing: border-box;
line-height: .8rem /* 60/75 */;
font-size: .5333rem /* 40/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
}
.content-box{
overflow: auto;
display: flex;
flex: 1;
.nav{
width: 3.6533rem /* 274/75 */;
height: 100%;
// width: 360px;
padding: 0 0 0 .4rem /* 30/75 */;
box-sizing: border-box;
display: flex;
align-content: flex-start;
// flex-direction: column;
flex-wrap: wrap;
border-right: .0267rem /* 2/75 */ dashed #D9D9D9;
.li{
display:block;
box-sizing: border-box;
height: .9067rem /* 68/75 */;
line-height: .9067rem /* 68/75 */;
border-radius: 0px .8rem /* 60/75 */ .8rem /* 60/75 */ 0px ;
padding: 0 .48rem /* 36/75 */ 0 .24rem /* 18/75 */;
box-sizing: border-box;
box-sizing: border-box;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
margin-bottom: .28rem /* 21/75 */;
}
.active{
color: #FFFFFF;
background: #448CF7;
}
}
.user-content{
flex: 1;
height: 100%;
overflow: auto;
.user-scroll{
width: 100%;
// height: 100%;
}
.user-list{
display: flex;
align-items: center;
padding: 0 .4rem /* 30/75 */;
box-sizing: border-box;
margin-bottom: .5333rem /* 40/75 */;
.img-box{
width: .9867rem /* 74/75 */;
height: .9867rem /* 74/75 */;
border-radius:.1333rem /* 10/75 */;
background: #D9D9D9;
img{
display: block;
width: 100%;
height: 100%;
border-radius:.1333rem /* 10/75 */;
}
}
.name{
padding: 0 0 0 .2933rem /* 22/75 */;
box-sizing: border-box;
flex: 1;
line-height: .9867rem /* 74/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #4B4C4E;
}
.button{
width: 1.2267rem /* 92/75 */;
height: .6667rem /* 50/75 */;
background: #EDEDED;
border-radius:.1333rem /* 10/75 */;
border: none;
outline: none;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
box-sizing: border-box;
text-align: center;
line-height: .6667rem /* 50/75 */;
}
.active-button{
background: #EBFCF4;
border: .0267rem /* 2/75 */ solid #30BF78;
color: #30BF78;
}
}
}
}
}
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<div>
登录
<button @click="onClcikGo">注册</button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter()
console.log(router)
const onClcikGo = () =>{
router.push('/register')
}
</script>
+5
View File
@@ -0,0 +1,5 @@
<template>
<div>
注册
</div>
</template>
+370
View File
@@ -0,0 +1,370 @@
<template>
<div class='box'>
<header @click="onClickAdd">
<img src="../assets/img/baiseshenfenzheng.png" alt="">
<span>新增特殊人员</span>
</header>
<h2>
<div class="title">已录入人员 <img class="subscript" src="../assets/img/xiabiao.png" alt=""></div>
<div class="tips">人员信息删除后不可在刷脸通过门禁</div>
</h2>
<div class="content">
<div class="search-box">
<input type="text" placeholder="输入员工姓名进行搜索">
<img src="../assets/img/sousuo.png" alt="">
</div>
<div v-if="specialArr.length" class="scroll-content">
<div v-for="item in specialArr" class="user-list" :key="item.id">
<div class="img-box">
<img @click="onClickPreviewImg(item.img)" v-if="item.img" :src="item.img" alt="">
</div>
<div class="name">
<span>{{ item.name }}</span>
<p>联系方式{{ item.tel }}</p>
</div>
<div @click="onClickLook(item)" class="button">查看</div>
<div @click="onClickDelete(item)" class="remove-button">删除</div>
</div>
</div>
<div v-else class="scroll-content">
<div class="no-have">
暂无特殊人员
</div>
</div>
</div>
</div>
<Addmodel @cancel="isAddmodel = false" @save="onClciksave" v-if="isAddmodel" />
<Removemodel @delete="onClickDeleteAffirm" @cancel="isRemovemodel = false" v-if="isRemovemodel" />
<Facemodel @save="onClickSaveFace" @change="onChangeFace" @cancel="isFacemodel = false" v-if="isFacemodel" :faceImg="updateFaceImg" />
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import Addmodel from '@/components/add-model.vue'
import Removemodel from '@/components/remove-model.vue'
import Facemodel from '@/components/face-model.vue'
import dd from 'dingtalk-jsapi'
import { getToken, getDept, getUserListByDept, putUserId, getSpecial, putSpecial, postSpecial, putSpecialFace } from '@/api/doorAdmin.js'
import { uploadApi } from '@/api/upload'
import { Toast } from 'vant'
/* @data */
const router = useRouter()
const store = useStore()
const isAddmodel = ref(false) // 新增特殊人员模态框
const isRemovemodel = ref(false) // 删除特殊人员模态框
const isFacemodel = ref(false) // 人脸模态框
const specialArr = ref([]) // 特殊人员数据
const update = ref() // 特殊人员修改数据
const deleteId = ref('') // 需要删除的id
const updateFaceImg = ref() // 修改人脸照片base64或之前的数据
const query = reactive({
current: -1,
size: 10,
})
/* @setup */
/* @method */
// 点击预栏图片
const onClickPreviewImg = (img) =>{
dd.previewImage({
urls: [img
],
current: 1,
success: () => {},
fail: () => {},
complete: () => {},
});
}
const onChangeFace = (file) =>{
if(file){
let reader = new FileReader()
reader.readAsDataURL(file) // 转base64
reader.onload = () =>{ // 简化代码 - 转换完成
updateFaceImg.value = reader.result
}
}
}
// 点击确认保存修改图片
const onClickSaveFace = (file) =>{
console.log(update.value)
if(file){
let formData = new FormData()
formData.append('file', file)
formData.append('userId', update.value.tel)
formData.append('name', update.value.name)
Toast.loading({ duration: 0, forbidClick: true, message: '上传中', });
uploadApi(formData).then(res =>{
// form.face = res.data
let obj = { id: update.value.id, img: res.data }
putSpecialFace(obj).then(response =>{
specialList()
isFacemodel.value = false
Toast.success(response.msg);
})
})
}
}
// 点击查看按钮
const onClickLook = (item) =>{
update.value = item
isFacemodel.value = true
updateFaceImg.value = item.img
console.log(item)
}
// 点击新增特殊人员按钮
const onClickAdd = () =>{
isAddmodel.value = true
}
// 确认删除
const onClickDeleteAffirm = () =>{
console.log(deleteId.value)
putSpecial(deleteId.value).then(res =>{
Toast.success(res.msg)
isRemovemodel.value = false
specialList()
})
}
// 点击删除
const onClickDelete = (item) =>{
console.log(item)
deleteId.value = item.id
isRemovemodel.value = true
}
// 点击保存
const onClciksave = (specialForm) =>{
console.log(specialForm)
if( !specialForm.face ){
Toast.fail('请上传人脸照片!')
}else if( !specialForm.userId ){
Toast.fail('请输入人员联系方式!')
}else if( !specialForm.name){
Toast.fail('请输入人员姓名!')
}else {
if(rulesPhone(specialForm.userId)){
let formData = new FormData()
formData.append('file', specialForm.face)
formData.append('userId', specialForm.userId)
formData.append('name', specialForm.name)
Toast.loading({ duration: 0, forbidClick: true, message: '保存中', });
uploadApi(formData).then(res =>{
let obj = { tel: specialForm.userId, img: res.data, name: specialForm.name }
postSpecial(obj).then(response =>{
specialList()
isAddmodel.value = false
Toast.success(response.msg);
})
})
}
}
}
// 特殊人员列表
const specialList = () =>{
getSpecial(query).then(res =>{
specialArr.value = res.data
})
}
/* @setup */
specialList()
</script>
<style lang='scss' scoped>
.box{
padding: .6667rem /* 50/75 */ .4rem /* 30/75 */ .4rem /* 30/75 */;
box-sizing: border-box;
background: #F4F6F7;
display: flex;
flex-direction: column;
header{
width: 8.4267rem /* 632/75 */;
height: 1.6rem /* 120/75 */;
background: linear-gradient(108deg, #7CB5FF 0%, #448CF7 84%);
box-shadow: 0px .1067rem /* 8/75 */ .2667rem /* 20/75 */ 0px #C2DBFF;
border-radius: 1.3333rem /* 100/75 */;
display: flex;
// justify-content: center;
align-items: center;
padding: 0 0 0 2.4533rem /* 184/75 */;
box-sizing: border-box;
margin: 0 auto;
img{
width: .72rem /* 54/75 */;
height: .6933rem /* 52/75 */;
margin-right: .2133rem /* 16/75 */;
}
span{
font-size: $lg-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #FFFFFF;
line-height: 1.6rem /* 120/75 */;
}
}
h2{
margin-top: .64rem /* 48/75 */;
height: 1.2533rem /* 94/75 */;
display: flex;
.title{
position: relative;
width: 3.0933rem /* 232/75 */;
line-height: 1.2533rem /* 94/75 */;
font-size: $lg-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
background: url('../assets/img/tixing.png') left top/100% 100% no-repeat;
border-radius: .2667rem /* 20/75 */ .2667rem /* 20/75 */ 0 0 ;
text-align: center;
.subscript{
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%) translateY(50%);
width: .6133rem /* 46/75 */;
height: .2667rem /* 20/75 */;
}
}
.tips{
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
margin-left: .5333rem /* 40/75 */;
line-height: 1.2533rem /* 94/75 */;
}
}
.content{
flex: 1;
overflow: auto;
background-color: #FFFFFF;
border-radius:0 .2667rem /* 20/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */;
padding:.5867rem /* 44/75 */ .3733rem /* 28/75 */;
box-sizing: border-box;
display: flex;
flex-direction: column;
.search-box{
width: 100%;
height: .9333rem /* 70/75 */;
background: #EEEFF3;
border-radius: .1333rem /* 10/75 */;
padding: 0 .4533rem /* 34/75 */ 0 .3467rem /* 26/75 */ ;
box-sizing: border-box;
display: flex;
align-items: center;
margin-bottom: .6667rem /* 50/75 */;
input{
flex: 1;
background: #EEEFF3;
padding: 0 .6667rem /* 50/75 */ 0 0;
box-sizing: border-box;
line-height: .9333rem /* 70/75 */;
outline: none;
border: none;
font-size: $sm-size;
}
input::-webkit-input-placeholder{
color: #CCCCCC;
font-size: $sm-size;
}
img{
width: .48rem /* 36/75 */;
height: .48rem /* 36/75 */;
}
}
.scroll-content{
flex: 1;
overflow: auto;
.user-list{
display: flex;
align-items: center;
// padding: 0 .4rem /* 30/75 */;
box-sizing: border-box;
margin-bottom: .5333rem /* 40/75 */;
.img-box{
width: .9867rem /* 74/75 */;
height: .9867rem /* 74/75 */;
border-radius:.1333rem /* 10/75 */;
background: #D9D9D9;
img{
display: block;
width: 100%;
height: 100%;
border-radius:.1333rem /* 10/75 */;
}
}
.name{
padding: 0 0 0 .2933rem /* 22/75 */;
box-sizing: border-box;
flex: 1;
// line-height: .9867rem /* 74/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #4B4C4E;
span{
line-height: .6133rem /* 46/75 */;
color: #4B4C4E;
}
p{
font-size: $sm-size;
line-height: .4533rem /* 34/75 */;
color: #B6B7B8;
}
}
.button{
width: 1.2267rem /* 92/75 */;
height: .6667rem /* 50/75 */;
background: #EDEDED;
border-radius:.1333rem /* 10/75 */;
border: none;
outline: none;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
box-sizing: border-box;
text-align: center;
line-height: .6667rem /* 50/75 */;
margin-right: .2133rem /* 16/75 */;
}
.remove-button{
width: 1.2267rem /* 92/75 */;
height: .6667rem /* 50/75 */;
background: #FFE9E4;
border-radius:.1333rem /* 10/75 */;
border: none;
outline: none;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FF6643;
box-sizing: border-box;
text-align: center;
line-height: .6667rem /* 50/75 */;
}
}
}
.scroll-content::-webkit-scrollbar {
display: none; /* Chrome Safari */
} // 超出部分隐藏滚动条
}
}
</style>
+340
View File
@@ -0,0 +1,340 @@
<template>
<div class='box'>
<header>
<div class="header-top">
<h1><img src="../assets/userhome/dingsheng.png" alt=""></h1>
<img class="h3" src="../assets/userhome/zhiengfangke.png" alt="">
<p>欢迎进入鼎盛无际智能访客系统</p>
</div>
<div class="plate-box">
<div @click="onClickGo('/apply')" class="list">
<h4>来访预约</h4>
<p>登记来访</p>
<img class="icon-arrowhead" src="../assets/userhome/youjiantou.png" alt="">
<img class="img" src="../assets/userhome/ren.png" alt="">
</div>
<div @click="onClickGo('/applyRecord')" class="list">
<h4>预约记录</h4>
<p>记录查询</p>
<img class="icon-arrowhead" src="../assets/userhome/youjiantou.png" alt="">
<img class="img" src="../assets/userhome/fang.png" alt="">
</div>
</div>
</header>
<div class="content">
<div class="contact-box">
<h3><span>联系我们 </span><p><b class="square"></b></p> </h3>
<div class="time">法定工作日 09:00-12:00 14:00-18:00</div>
<p @click="onClickCall" class="text-list">
<img src="../assets/userhome/dianhua.png" alt="">
<span>服务热线400040-1573</span>
</p>
<p class="text-list">
<img src="../assets/userhome/dingwei.png" alt="">
<span>地址四川省泸州市高新区众创中心12楼</span>
</p>
</div>
<div class="controls-box">
<img class="icon-img" src="../assets/caozuo.png" >
<p>操作手册</p>
<img class="icon-right" src="../assets/youjiantou.png" alt="">
<a href="https://www.yuque.com/zhoupangpang-m5aid/qp94nz/hhvxk757iolg8gah?singleDoc#"></a>
</div>
<!-- <div><button @click="onClickUpdate">修改token</button></div> -->
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import { getWeChatCode, login } from '../api/user'
import { Toast, ImagePreview } from 'vant';
/* @data */
const router = useRouter()
const store = useStore()
const url = 'http://visitor.scdswj.cn/?code=071ucW200YtsQR1AUj3007Tabj2ucW26&state=STATE'
/* @setup */
// url.split('?')[1].split('&').map(item =>{
// if(item.split('=')[0] === 'code'){
// // code = item.split('=')[1]
// console.log(item.split('=')[1])
// alert(item.split('=')[1])
// }
// })
localStorage.setItem('redirect', '/userhome')
/* @method */
// const onClickUpdate = () =>{
// localStorage.setItem("userToken", 'res.data.authorization');
// }
const onClickGo = (url = '/userHome') =>{
router.push(url)
}
const onClickCall = () =>{
window.location.href = 'tel:400-040-1573'
}
const onClickMap = () =>{
window.location.href = ''
}
//判断是否是微信内置的浏览器
const isWXBrowserFun = ()=> {
return (
String(navigator.userAgent.toLowerCase().match(/MicroMessenger/i)) ===
"micromessenger"
);
}
//截取地址栏code
const getUrlCode = (name) =>{
if(window.location.href.indexOf(name) === -1) return
let code = ''
window.location.href.split('?')[1].split('&').map(item =>{
if(item.split('=')[0] === name){
code = item.split('=')[1]
}
})
return code
}
//访问微信地址,用来获取code
const weChatCode = () =>{
getWeChatCode().then(res =>{
window.location.href = res.data
})
}
//把code传递给后台接口,静默登录
const wxLogin = (code) =>{
login(code).then((res) => {
console.log(res)
if (res.code===200) {
localStorage.setItem("userToken", res.data.authorization);
Toast.success('登录成功!')
} else {
Toast.fail('登录失败!')
}
});
}
const isWXBrowser = isWXBrowserFun();
if (isWXBrowser) {
const userToken = localStorage.getItem('userToken')
if(!userToken){
let code = getUrlCode("code");
console.log(code)
if (code) {
wxLogin(code); //后台登录
} else {
weChatCode(); //微信授权
}
}
}else {
alert('请在微信中打开!')
}
</script>
<style lang='scss' scoped>
.box{
background-color: #F4F5F8;
header{
width: 100%;
height: 6.9333rem /* 520/75 */;
background: url('../assets/userhome/beijign.png') left top/100% 100% no-repeat;
.header-top{
width: 100%;
height: 4.1867rem /* 314/75 */;
background: url('../assets/userhome/topbeijing.png') left top/100% 100% no-repeat;
h1{
padding: .7467rem /* 56/75 */ .64rem /* 48/75 */;
box-sizing: border-box;
img{
width: 2.1333rem /* 160/75 */;
height: .5867rem /* 44/75 */;
}
}
.h3{
width: 3.12rem /* 234/75 */;
height: .7733rem /* 58/75 */;
margin: 0 0 .2133rem /* 16/75 */ .6667rem /* 50/75 */;
}
p{
line-height: .48rem /* 36/75 */;
font-size: .32rem /* 24/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #514E4B;
padding: 0 0 0 .7467rem /* 56/75 */;
box-sizing: border-box;
}
}
.plate-box{
width: 100%;
height: 3.3333rem /* 250/75 */;
padding: 0 .4267rem /* 32/75 */;
box-sizing: border-box;
display: flex;
justify-content: space-between;
.list{
position: relative;
flex: 1;
height: 3.3333rem /* 250/75 */;
background: linear-gradient(180deg, rgba(255,255,255,0) 0%, #FFFFFF 100%);
border-radius: .2667rem /* 20/75 */;
opacity: 1;
border: .0267rem /* 2/75 */ solid #FFFFFF;
h4{
padding: .4533rem /* 34/75 */ 0 .1333rem /* 10/75 */ .5067rem /* 38/75 */;
box-sizing: border-box;
line-height: .6133rem /* 46/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
}
p{
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
padding: 0 0 0 .5333rem /* 40/75 */;
box-sizing: border-box;
}
.icon-arrowhead{
width: .5333rem /* 40/75 */;
height: .5333rem /* 40/75 */;
margin: .7733rem /* 58/75 */ 0 0 .4533rem /* 34/75 */;
}
.img{
position: absolute;
bottom: 0;
right: .3733rem /* 28/75 */;
width: 1.5733rem /* 118/75 */;
height: 1.8133rem /* 136/75 */;
}
}
.list:first-child{
margin-right: .3467rem /* 26/75 */;
}
}
}
.content{
width: 100%;
padding: 1.12rem /* 84/75 */ .4rem /* 30/75 */ 0;
box-sizing: border-box;
.contact-box{
width: 100%;
height: 4.24rem /* 318/75 */;
// background: linear-gradient(261deg, #FFFFFF 0%, rgba(255,255,255,0) 100%);
background: url('../assets/userhome/ditu.png') left top/100% 100% no-repeat;
background-color: #FFFFFF;
border-radius: .2667rem /* 20/75 */ 1.0667rem /* 80/75 */ .2667rem /* 20/75 */ .2667rem /* 20/75 */;
h3{
position: relative;
// padding: .48rem /* 36/75 */ .5333rem /* 40/75 */ .2667rem /* 20/75 */;
box-sizing: border-box;
display: flex;
span{
display: block;
padding: .48rem /* 36/75 */ 0 0 .5333rem /* 40/75 */ ;
line-height: .6133rem /* 46/75 */;
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #000000;
position: relative;
z-index: 2;
}
p{
position: relative;
flex: 1;
.square{
position: absolute;
bottom: .0;
left: 0;
z-index: 1;
transform: translateX(-70%) translateY(-20%);
width: .24rem /* 18/75 */;
height: .24rem /* 18/75 */;
background: #30BF78;
border-radius: 0px 0px 0px 0px;
opacity: 1;
}
}
}
.time{
padding: .2667rem /* 20/75 */ 0 0 .5333rem /* 40/75 */;
box-sizing: border-box;
line-height: .4533rem /* 34/75 */;
font-size: .2933rem /* 22/75 */;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #5F6583;
margin-bottom: .56rem /* 42/75 */;
}
.text-list{
padding: 0 .64rem /* 48/75 */;
box-sizing: border-box;
line-height: .5333rem /* 40/75 */;
font-size: $size26;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #5F6583;
display: flex;
margin-bottom: .3733rem /* 28/75 */;
img{
width: .5333rem /* 40/75 */;
height: .5333rem /* 40/75 */;
margin-right: .2133rem /* 16/75 */;
}
}
}
.controls-box{
position: relative;
height: 1.3333rem /* 100/75 */;
background: rgba(255,255,255,0.8);
border-radius:.2667rem /* 20/75 */;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 .24rem /* 18/75 */ 0 .4533rem /* 34/75 */;
box-sizing: border-box;
margin-top: .32rem /* 24/75 */ ;
.icon-img{
width: .8533rem /* 64/75 */;
height: .8533rem /* 64/75 */;
}
p{
flex: 1;
font-size: $lg-size;
font-family: Inter, Inter;
font-weight: 500;
color: #5F6583;
line-height: .5067rem /* 38/75 */;
padding: 0 0 0 .24rem /* 18/75 */;
box-sizing: border-box;
}
.icon-right{
width: .64rem /* 48/75 */;
height: .64rem /* 48/75 */;
}
a{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
}
}
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class='box'>
<Successmodel />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { rulesForm } from '@/com/index'
import Successmodel from '@/components/success.vue'
/* @data */
const router = useRouter()
const store = useStore()
/* @setup */
/* @method */
</script>
<style lang='scss' scoped>
</style>
+192
View File
@@ -0,0 +1,192 @@
<template>
<div class='box'>
<header class="tabbar">
<div @click="onClickTabbar(item)" v-for="item in stateEnum" class="tabbar-li" :key="item.status" :class="query.status == item.status ? 'active' : ''">
{{ item.statusName }}
<img v-if="query.status == item.status" src="../assets/maomaochong.png" alt="">
</div>
</header>
<div v-if="recordArr.length" class="content">
<div @click="onClcikItem(item)" v-for="item in recordArr" class="wait-list" :key="item.id">
<h5>被拜访人{{ item.visitingName }}</h5>
<h5>拜访时间{{ item.hours }}</h5>
<div class="apply-box">
<p><span>访客姓名</span><span>{{ item.name }}</span></p>
<p><span>联系方式</span><span>{{ item.phone }}</span></p>
<p><span>随行人数</span><span>{{ item.visitingNum }} </span></p>
<p><span>来访事由</span><span>{{ item.reason }}</span></p>
</div>
<!-- refuse 拒绝 pass 通过 状态 -->
<div class="status" :class="stateEnum[item.status].className">{{ stateEnum[item.status].statusName }}</div>
</div>
</div>
<div v-if="isLoading && recordArr.length == 0" class="content">
<div class="no-have">
暂无数据
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import dd from 'dingtalk-jsapi'
import { Toast, ImagePreview } from 'vant';
import { rulesForm, resetForm, rulesPhone } from '@/com/index'
import { getToken, getVisitorRecord } from '@/api/admin.js'
/* @data */
const router = useRouter()
const route = useRoute()
const store = useStore()
const stateEnum = store.state.stateEnum
const recordArr = ref([])
const isLoading = ref(false)
const query = reactive({
status: 0,
size: -1,
current: 1,
})
console.log(route)
/* @setup */
/* @method */
const onClcikItem = (item) =>{
// window.location.href = item.instanceUrl
// window.open(item.instanceUrl, '审批详情');
dd.openLink({
url: item.instanceUrl,
success: () => {},
fail: () => {},
complete: () => {},
});
}
const onClickTabbar = (item) =>{
if(query.status != item.status){
query.status = item.status
recordArr.value = []
visitorList()
}
}
const visitorList = () =>{
Toast.loading({ duration: 0, forbidClick: true, message: '加载中', });
getVisitorRecord(query).then(res =>{
isLoading.value = true
recordArr.value = res.data
}).catch(err =>{
isLoading.value = true
})
}
visitorList()
</script>
<style lang='scss' scoped>
.box{
background: #F9FBFC;;
display: flex;
flex-direction: column;
.tabbar{
height: 1.8933rem /* 142/75 */;
line-height: .6133rem /* 46/75 */;
display: flex;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
padding: .56rem /* 42/75 */ .4rem /* 30/75 */ 0;
box-sizing: border-box;
.tabbar-li{
position: relative;
flex: 1;
text-align: center;
img{
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: .48rem /* 36/75 */;
width: .6133rem /* 46/75 */;
height: .2667rem /* 20/75 */;
}
}
.active{
font-size: $size30;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 500;
color: #222222;
}
}
.content{
width: 100%;
flex: 1;
padding: 0 .4rem /* 30/75 */;
box-sizing: border-box;
overflow: auto;
.wait-list{
position: relative;
width: 100%;
// height: 470px;
background: #FFFFFF;
border-radius: .2667rem /* 20/75 */;
padding: .48rem /* 36/75 */;
box-sizing: border-box;
margin-bottom: .2667rem /* 20/75 */;
h5{
height: .6667rem /* 50/75 */;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #222222;
}
.apply-box{
width: 100%;
padding: .4rem /* 30/75 */ .3733rem /* 28/75 */ .5067rem /* 38/75 */;
box-sizing: border-box;
border-radius: .1333rem /* 10/75 */;
font-size: $md-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #999999;
line-height: .72rem /* 54/75 */;
background: #F5F7F9;
span:last-child{
color: #5F6583;
}
}
.status{
position: absolute;
top: 0;
right: 0;
width: 1.52rem /* 114/75 */;
height: .6667rem /* 50/75 */;
line-height: .6667rem /* 50/75 */;
background: #FFE9E4;
border-radius: 0px .2667rem /* 20/75 */ 0px .1333rem /* 10/75 */;
color: #FF6643;
font-size: $sm-size;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
color: #FF6643;
text-align: center;
}
.refuse{
color: #5F6583;
background: #EAEEF6;
}
.pass{
color: #30BF78;
background: #E7F8F5;
}
}
}
}
</style>