|
|
@@ -1,172 +1,404 @@
|
|
|
<template>
|
|
|
- <ScreenLayout :show-back-btn="true" back-text="返回登记" back-target="/visitor">
|
|
|
- <div class="page-walk-in">
|
|
|
- <h1 class="page-title">现场登记</h1>
|
|
|
-
|
|
|
- <div class="form-container">
|
|
|
- <!-- 身份证读取 -->
|
|
|
- <div class="idcard-section">
|
|
|
- <button class="btn-idcard" @click="handleReadIdCard" :disabled="reading">
|
|
|
- <div class="idcard-icon">
|
|
|
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
- <rect x="2" y="4" width="20" height="16" rx="2" />
|
|
|
- <line x1="6" y1="8" x2="10" y2="8" />
|
|
|
- <line x1="6" y1="12" x2="18" y2="12" />
|
|
|
- <line x1="6" y1="16" x2="14" y2="16" />
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- <span v-if="reading">读取中...</span>
|
|
|
- <span v-else>读取身份证</span>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <div class="walkin-layout">
|
|
|
+ <!-- 顶部状态栏 -->
|
|
|
+ <StatusBar :title="robotName" />
|
|
|
|
|
|
- <div class="form-divider">
|
|
|
- <span>或手动填写</span>
|
|
|
+ <!-- 主内容区 -->
|
|
|
+ <div class="layout-main">
|
|
|
+ <div class="walkin-page">
|
|
|
+ <!-- 动态背景层 -->
|
|
|
+ <div class="bg-layer">
|
|
|
+ <div class="bg-orb orb-1"></div>
|
|
|
+ <div class="bg-orb orb-2"></div>
|
|
|
+ <div class="bg-orb orb-3"></div>
|
|
|
+ <div class="bg-grid-overlay"></div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 表单 -->
|
|
|
- <div class="form-fields">
|
|
|
- <!-- 姓名 -->
|
|
|
- <div class="form-field">
|
|
|
- <label>访客姓名</label>
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- v-model="formData.visitorName"
|
|
|
- placeholder="请输入姓名"
|
|
|
- class="input"
|
|
|
- />
|
|
|
+ <!-- 内容区 -->
|
|
|
+ <div class="walkin-content">
|
|
|
+ <!-- 标题区 -->
|
|
|
+ <div class="walkin-hero">
|
|
|
+ <h1 class="page-title">现场登记</h1>
|
|
|
+ <p class="page-subtitle">请填写访客信息,完成现场登记</p>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 手机号 -->
|
|
|
- <div class="form-field">
|
|
|
- <label>手机号码</label>
|
|
|
- <div class="input-with-keyboard" @click="showKeyboard('mobile')">
|
|
|
- <span class="input-value">{{ formData.mobile || '请输入手机号' }}</span>
|
|
|
+ <!-- 登记信息卡片 -->
|
|
|
+ <div class="form-card">
|
|
|
+ <!-- 访客姓名 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openTextModal('visitorName', '输入访客姓名', formData.visitorName)">
|
|
|
+ <span class="form-label">访客姓名</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="formData.visitorName" class="form-value form-value-editable">{{ formData.visitorName }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请输入访客姓名</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 手机号码 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openPhoneModal">
|
|
|
+ <span class="form-label">手机号码</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="formData.mobile" class="form-value form-value-editable">{{ formData.mobile }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请输入手机号</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 身份证号 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openIdCardModal">
|
|
|
+ <span class="form-label">身份证号</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="formData.idCardNo" class="form-value form-value-editable">{{ formData.idCardNo }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请输入身份证号</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 来访单位 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openTextModal('visitorCompany', '输入来访单位', formData.visitorCompany)">
|
|
|
+ <span class="form-label">来访单位</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="formData.visitorCompany" class="form-value form-value-editable">{{ formData.visitorCompany }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请输入来访单位</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 被访人 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openTextModal('visitedPerson', '输入被访人', formData.visitedPerson)">
|
|
|
+ <span class="form-label">被访人</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="formData.visitedPerson" class="form-value form-value-editable">{{ formData.visitedPerson }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请输入被访人</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 来访事由 -->
|
|
|
+ <div class="form-row form-row-editable" @click="openReasonModal">
|
|
|
+ <span class="form-label">来访事由</span>
|
|
|
+ <div class="form-value-wrapper">
|
|
|
+ <span v-if="currentReasonLabel" class="form-value form-value-editable">{{ currentReasonLabel }}</span>
|
|
|
+ <span v-else class="form-value form-value-placeholder">请选择来访事由</span>
|
|
|
+ <svg class="edit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
|
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 身份证号 -->
|
|
|
- <div class="form-field">
|
|
|
- <label>身份证号</label>
|
|
|
- <div class="input-with-keyboard" @click="showKeyboard('idCardNo')">
|
|
|
- <span class="input-value">{{ formData.idCardNo || '请输入身份证号' }}</span>
|
|
|
+ <!-- 底部操作区 -->
|
|
|
+ <div class="confirm-actions">
|
|
|
+ <button class="btn-idcard" :disabled="reading" @click="handleReadIdCard">
|
|
|
+ <svg class="idcard-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <rect x="2" y="4" width="20" height="16" rx="2" />
|
|
|
+ <circle cx="8.5" cy="10" r="2.5" />
|
|
|
+ <path d="M2 16h5" />
|
|
|
+ <path d="M2 19h3" />
|
|
|
+ <line x1="13" y1="9" x2="20" y2="9" />
|
|
|
+ <line x1="13" y1="13" x2="20" y2="13" />
|
|
|
+ <line x1="13" y1="17" x2="17" y2="17" />
|
|
|
+ </svg>
|
|
|
+ <span v-if="reading">读取中...</span>
|
|
|
+ <span v-else>读取身份证</span>
|
|
|
+ </button>
|
|
|
+ <div class="primary-actions">
|
|
|
+ <button class="btn-back" @click="goBack">
|
|
|
+ <svg class="back-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
|
+ <path d="M15 18l-6-6 6-6" />
|
|
|
+ </svg>
|
|
|
+ <span>返回登记</span>
|
|
|
+ </button>
|
|
|
+ <button class="btn-confirm" :disabled="submitting" @click="handleSubmit">
|
|
|
+ <span v-if="submitting" class="loading-spinner"></span>
|
|
|
+ <span v-else>提交登记</span>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 被访人 -->
|
|
|
- <div class="form-field">
|
|
|
- <label>被访人</label>
|
|
|
+ <!-- ===== 文本输入弹窗 ===== -->
|
|
|
+ <!-- 触摸屏文本输入依赖系统虚拟键盘(如 Ubuntu onboard),前端通过 focus 尽量触发键盘弹出 -->
|
|
|
+ <Teleport to="body">
|
|
|
+ <div v-if="showTextModal" class="text-modal-overlay" @click.self="closeTextModal">
|
|
|
+ <div class="text-modal-card">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>{{ textModalTitle }}</h2>
|
|
|
+ </div>
|
|
|
+ <div class="text-input-wrapper">
|
|
|
<input
|
|
|
+ ref="textInputRef"
|
|
|
+ v-model="tempText"
|
|
|
+ class="text-modal-input"
|
|
|
type="text"
|
|
|
- v-model="formData.visitedPerson"
|
|
|
- placeholder="请输入被访人姓名"
|
|
|
- class="input"
|
|
|
+ :placeholder="textModalPlaceholder"
|
|
|
+ @keydown.enter="confirmTextEdit"
|
|
|
/>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 来访事由 -->
|
|
|
- <div class="form-field">
|
|
|
- <label>来访事由</label>
|
|
|
- <div class="reason-options">
|
|
|
- <button
|
|
|
- v-for="option in visitReasons"
|
|
|
- :key="option.value"
|
|
|
- class="reason-btn"
|
|
|
- :class="{ active: formData.visitReason === option.value }"
|
|
|
- @click="formData.visitReason = option.value"
|
|
|
- >
|
|
|
- {{ option.label }}
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <div class="text-modal-actions">
|
|
|
+ <button class="text-btn-cancel" @click="closeTextModal">取消</button>
|
|
|
+ <button class="text-btn-save" @click="confirmTextEdit">保存</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+ </Teleport>
|
|
|
|
|
|
- <!-- 提交按钮 -->
|
|
|
- <button class="btn-submit" @click="handleSubmit" :disabled="submitting">
|
|
|
- <span v-if="submitting" class="loading-spinner"></span>
|
|
|
- <span v-else>提交登记</span>
|
|
|
- </button>
|
|
|
+ <!-- ===== 手机号 NumericKeyboard 弹窗 ===== -->
|
|
|
+ <Teleport to="body">
|
|
|
+ <div v-if="showPhoneModal" class="phone-modal" @click.self="closePhoneModal">
|
|
|
+ <div class="modal-card">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>输入手机号</h2>
|
|
|
+ <p>请输入访客手机号码</p>
|
|
|
+ </div>
|
|
|
+ <NumericKeyboard
|
|
|
+ v-model="tempPhone"
|
|
|
+ :max-length="11"
|
|
|
+ type="phone"
|
|
|
+ placeholder="请输入手机号"
|
|
|
+ />
|
|
|
+ <div class="modal-actions">
|
|
|
+ <button class="btn-cancel" @click="closePhoneModal">取消</button>
|
|
|
+ <button class="btn-confirm" @click="confirmPhoneEdit">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </Teleport>
|
|
|
|
|
|
- <!-- 数字键盘弹窗 -->
|
|
|
- <div v-if="showKeyboardFlag" class="keyboard-modal" @click.self="hideKeyboard">
|
|
|
- <div class="keyboard-content">
|
|
|
- <div class="keyboard-header">
|
|
|
- <span>{{ keyboardLabel }}</span>
|
|
|
- <button class="keyboard-close" @click="hideKeyboard">完成</button>
|
|
|
+ <!-- ===== 身份证号 NumericKeyboard 弹窗 ===== -->
|
|
|
+ <Teleport to="body">
|
|
|
+ <div v-if="showIdCardModal" class="phone-modal" @click.self="closeIdCardModal">
|
|
|
+ <div class="modal-card">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>输入身份证号</h2>
|
|
|
+ <p>请输入访客身份证号码</p>
|
|
|
</div>
|
|
|
<NumericKeyboard
|
|
|
- v-model="currentInput"
|
|
|
- :label="keyboardLabel"
|
|
|
- :max-length="keyboardMaxLength"
|
|
|
- :type="keyboardType"
|
|
|
- @confirm="handleKeyboardConfirm"
|
|
|
+ v-model="tempIdCard"
|
|
|
+ :max-length="18"
|
|
|
+ type="idcard"
|
|
|
+ placeholder="请输入身份证号"
|
|
|
/>
|
|
|
+ <div class="modal-actions">
|
|
|
+ <button class="btn-cancel" @click="closeIdCardModal">取消</button>
|
|
|
+ <button class="btn-confirm" @click="confirmIdCardEdit">保存</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </ScreenLayout>
|
|
|
+ </Teleport>
|
|
|
+
|
|
|
+ <!-- ===== 来访事由选项弹窗 ===== -->
|
|
|
+ <Teleport to="body">
|
|
|
+ <div v-if="showReasonModal" class="reason-modal-overlay" @click.self="closeReasonModal">
|
|
|
+ <div class="reason-modal-card">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h2>选择来访事由</h2>
|
|
|
+ </div>
|
|
|
+ <div class="reason-options">
|
|
|
+ <button
|
|
|
+ v-for="opt in visitReasonOptions"
|
|
|
+ :key="opt.value"
|
|
|
+ class="reason-btn"
|
|
|
+ :class="{ 'reason-btn-active': tempReason === opt.value }"
|
|
|
+ @click="tempReason = opt.value"
|
|
|
+ >
|
|
|
+ {{ opt.label }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="reason-modal-actions">
|
|
|
+ <button class="reason-btn-cancel" @click="closeReasonModal">取消</button>
|
|
|
+ <button class="reason-btn-save" @click="confirmReasonEdit">保存</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Teleport>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive } from 'vue'
|
|
|
+import { ref, reactive, computed, nextTick } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
+import StatusBar from '@/components/StatusBar.vue'
|
|
|
+import NumericKeyboard from '@/components/NumericKeyboard.vue'
|
|
|
import { useScreenStore } from '@/stores/screen'
|
|
|
import { useVisitorStore } from '@/stores/visitor'
|
|
|
-import ScreenLayout from '@/layouts/ScreenLayout.vue'
|
|
|
-import NumericKeyboard from '@/components/NumericKeyboard.vue'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const screenStore = useScreenStore()
|
|
|
const visitorStore = useVisitorStore()
|
|
|
|
|
|
-const reading = ref(false)
|
|
|
+const robotName = computed(() => screenStore.screenTheme?.robotName || '迎宾巡逻机器人')
|
|
|
const submitting = ref(false)
|
|
|
+const reading = ref(false)
|
|
|
+const visitReasonOptions = visitorStore.visitReasonOptions
|
|
|
|
|
|
-// 键盘相关
|
|
|
-const showKeyboardFlag = ref(false)
|
|
|
-const currentInput = ref('')
|
|
|
-const currentField = ref('')
|
|
|
-const keyboardLabel = ref('')
|
|
|
-const keyboardMaxLength = ref(11)
|
|
|
-const keyboardType = ref('phone')
|
|
|
-
|
|
|
-const visitReasons = visitorStore.visitReasonOptions
|
|
|
-
|
|
|
+// 表单数据
|
|
|
const formData = reactive({
|
|
|
visitorName: '',
|
|
|
mobile: '',
|
|
|
idCardNo: '',
|
|
|
+ visitorCompany: '',
|
|
|
visitedPerson: '',
|
|
|
visitReason: ''
|
|
|
})
|
|
|
|
|
|
-const showKeyboard = (field) => {
|
|
|
- currentField.value = field
|
|
|
- currentInput.value = formData[field] || ''
|
|
|
-
|
|
|
- if (field === 'mobile') {
|
|
|
- keyboardLabel.value = '手机号'
|
|
|
- keyboardMaxLength.value = 11
|
|
|
- keyboardType.value = 'phone'
|
|
|
- } else if (field === 'idCardNo') {
|
|
|
- keyboardLabel.value = '身份证号'
|
|
|
- keyboardMaxLength.value = 18
|
|
|
- keyboardType.value = 'idcard'
|
|
|
+// 来访事由标签
|
|
|
+const currentReasonLabel = computed(() => {
|
|
|
+ const found = visitReasonOptions.find(o => o.value === formData.visitReason)
|
|
|
+ return found ? found.label : ''
|
|
|
+})
|
|
|
+
|
|
|
+// ===== 导航 =====
|
|
|
+const goBack = () => {
|
|
|
+ router.push('/visitor')
|
|
|
+}
|
|
|
+
|
|
|
+// ===== 文本输入弹窗 =====
|
|
|
+const showTextModal = ref(false)
|
|
|
+const textModalField = ref('')
|
|
|
+const textModalTitle = ref('')
|
|
|
+const textModalPlaceholder = ref('')
|
|
|
+const tempText = ref('')
|
|
|
+const textInputRef = ref(null)
|
|
|
+
|
|
|
+const openTextModal = (field, title, currentValue) => {
|
|
|
+ textModalField.value = field
|
|
|
+ textModalTitle.value = title
|
|
|
+ textModalPlaceholder.value = title
|
|
|
+ tempText.value = currentValue
|
|
|
+ showTextModal.value = true
|
|
|
+ nextTick(() => {
|
|
|
+ if (textInputRef.value) {
|
|
|
+ textInputRef.value.focus()
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const closeTextModal = () => {
|
|
|
+ showTextModal.value = false
|
|
|
+ textModalField.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+const confirmTextEdit = () => {
|
|
|
+ if (!tempText.value.trim()) {
|
|
|
+ screenStore.showAlert({
|
|
|
+ type: 'warning',
|
|
|
+ message: '该项不能为空',
|
|
|
+ duration: 3000
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (textModalField.value === 'visitorName') {
|
|
|
+ formData.visitorName = tempText.value
|
|
|
+ } else if (textModalField.value === 'visitorCompany') {
|
|
|
+ formData.visitorCompany = tempText.value
|
|
|
+ } else if (textModalField.value === 'visitedPerson') {
|
|
|
+ formData.visitedPerson = tempText.value
|
|
|
+ }
|
|
|
+ closeTextModal()
|
|
|
+}
|
|
|
+
|
|
|
+// ===== 手机号弹窗 =====
|
|
|
+const showPhoneModal = ref(false)
|
|
|
+const tempPhone = ref('')
|
|
|
+const phoneEditError = ref(false)
|
|
|
+
|
|
|
+const openPhoneModal = () => {
|
|
|
+ tempPhone.value = formData.mobile
|
|
|
+ phoneEditError.value = false
|
|
|
+ showPhoneModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const closePhoneModal = () => {
|
|
|
+ showPhoneModal.value = false
|
|
|
+ phoneEditError.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const confirmPhoneEdit = () => {
|
|
|
+ if (!tempPhone.value || !/^1[3-9]\d{9}$/.test(tempPhone.value)) {
|
|
|
+ phoneEditError.value = true
|
|
|
+ screenStore.showAlert({
|
|
|
+ type: 'warning',
|
|
|
+ message: '请输入正确的11位手机号',
|
|
|
+ duration: 3000
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ formData.mobile = tempPhone.value
|
|
|
+ closePhoneModal()
|
|
|
+}
|
|
|
+
|
|
|
+// ===== 身份证号弹窗 =====
|
|
|
+const showIdCardModal = ref(false)
|
|
|
+const tempIdCard = ref('')
|
|
|
+
|
|
|
+const openIdCardModal = () => {
|
|
|
+ tempIdCard.value = formData.idCardNo
|
|
|
+ showIdCardModal.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const closeIdCardModal = () => {
|
|
|
+ showIdCardModal.value = false
|
|
|
+}
|
|
|
+
|
|
|
+const confirmIdCardEdit = () => {
|
|
|
+ if (!tempIdCard.value || tempIdCard.value.length < 15) {
|
|
|
+ screenStore.showAlert({
|
|
|
+ type: 'warning',
|
|
|
+ message: '请输入正确的身份证号',
|
|
|
+ duration: 3000
|
|
|
+ })
|
|
|
+ return
|
|
|
}
|
|
|
+ formData.idCardNo = tempIdCard.value
|
|
|
+ closeIdCardModal()
|
|
|
+}
|
|
|
+
|
|
|
+// ===== 来访事由弹窗 =====
|
|
|
+const showReasonModal = ref(false)
|
|
|
+const tempReason = ref('')
|
|
|
|
|
|
- showKeyboardFlag.value = true
|
|
|
+const openReasonModal = () => {
|
|
|
+ tempReason.value = formData.visitReason
|
|
|
+ showReasonModal.value = true
|
|
|
}
|
|
|
|
|
|
-const hideKeyboard = () => {
|
|
|
- showKeyboardFlag.value = false
|
|
|
+const closeReasonModal = () => {
|
|
|
+ showReasonModal.value = false
|
|
|
}
|
|
|
|
|
|
-const handleKeyboardConfirm = (value) => {
|
|
|
- formData[currentField.value] = value
|
|
|
- hideKeyboard()
|
|
|
+const confirmReasonEdit = () => {
|
|
|
+ if (!tempReason.value) {
|
|
|
+ screenStore.showAlert({
|
|
|
+ type: 'warning',
|
|
|
+ message: '请选择来访事由',
|
|
|
+ duration: 3000
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ formData.visitReason = tempReason.value
|
|
|
+ closeReasonModal()
|
|
|
}
|
|
|
|
|
|
+// ===== 身份证读取 =====
|
|
|
const handleReadIdCard = async () => {
|
|
|
reading.value = true
|
|
|
try {
|
|
|
@@ -180,44 +412,82 @@ const handleReadIdCard = async () => {
|
|
|
} catch {
|
|
|
screenStore.showAlert({
|
|
|
type: 'error',
|
|
|
- message: '身份证读取失败,请手动输入'
|
|
|
+ message: '身份证读取失败,请手动填写'
|
|
|
})
|
|
|
} finally {
|
|
|
reading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// ===== 提交登记 =====
|
|
|
const handleSubmit = async () => {
|
|
|
- // 验证
|
|
|
+ if (submitting.value) return
|
|
|
+
|
|
|
if (!formData.visitorName?.trim()) {
|
|
|
- screenStore.showAlert({ type: 'warning', message: '请输入访客姓名' })
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请输入访客姓名', duration: 3000 })
|
|
|
return
|
|
|
}
|
|
|
if (!formData.mobile?.trim()) {
|
|
|
- screenStore.showAlert({ type: 'warning', message: '请输入手机号' })
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请输入手机号', duration: 3000 })
|
|
|
return
|
|
|
}
|
|
|
if (!/^1[3-9]\d{9}$/.test(formData.mobile)) {
|
|
|
- screenStore.showAlert({ type: 'warning', message: '手机号格式不正确' })
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请输入正确的11位手机号', duration: 3000 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!formData.visitorCompany?.trim()) {
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请输入来访单位', duration: 3000 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!formData.visitedPerson?.trim()) {
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请输入被访人', duration: 3000 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!formData.visitReason) {
|
|
|
+ screenStore.showAlert({ type: 'warning', message: '请选择来访事由', duration: 3000 })
|
|
|
return
|
|
|
}
|
|
|
|
|
|
submitting.value = true
|
|
|
try {
|
|
|
- // 更新 store
|
|
|
visitorStore.setFormField('visitorName', formData.visitorName)
|
|
|
visitorStore.setFormField('mobile', formData.mobile)
|
|
|
visitorStore.setFormField('idCardNo', formData.idCardNo)
|
|
|
+ visitorStore.setFormField('visitorCompany', formData.visitorCompany)
|
|
|
visitorStore.setFormField('visitedPerson', formData.visitedPerson)
|
|
|
visitorStore.setFormField('visitReason', formData.visitReason)
|
|
|
+ visitorStore.setFormField('visitType', 'walk_in')
|
|
|
|
|
|
await visitorStore.submitRegistration()
|
|
|
router.push('/visitor/success')
|
|
|
- } catch {
|
|
|
- screenStore.showAlert({
|
|
|
- type: 'error',
|
|
|
- message: '登记失败,请重试'
|
|
|
- })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[WalkIn] submitRegistration failed:', error)
|
|
|
+ if (import.meta.env.DEV) {
|
|
|
+ const now = new Date()
|
|
|
+ const pad = (n) => String(n).padStart(2, '0')
|
|
|
+ const dateTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
|
|
+ visitorStore.registrationInfo = {
|
|
|
+ ...visitorStore.registrationInfo,
|
|
|
+ visitorName: formData.visitorName,
|
|
|
+ mobile: formData.mobile,
|
|
|
+ idCardNo: formData.idCardNo,
|
|
|
+ visitorCompany: formData.visitorCompany,
|
|
|
+ visitorSource: formData.visitorCompany,
|
|
|
+ visitedPerson: formData.visitedPerson,
|
|
|
+ visitReason: formData.visitReason,
|
|
|
+ visitType: 'walk_in',
|
|
|
+ registerTime: dateTime,
|
|
|
+ _mockSubmit: true
|
|
|
+ }
|
|
|
+ screenStore.showAlert({ type: 'success', message: '调试提交成功,正在跳转...', duration: 2000 })
|
|
|
+ setTimeout(() => router.push('/visitor/success'), 500)
|
|
|
+ } else {
|
|
|
+ screenStore.showAlert({
|
|
|
+ type: 'error',
|
|
|
+ message: '登记失败,请重试',
|
|
|
+ duration: 3000
|
|
|
+ })
|
|
|
+ }
|
|
|
} finally {
|
|
|
submitting.value = false
|
|
|
}
|
|
|
@@ -225,274 +495,793 @@ const handleSubmit = async () => {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.page-walk-in {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+/* ===== 布局结构 ===== */
|
|
|
+.walkin-layout {
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ background: linear-gradient(155deg, #e8f4fd 0%, #dbeafe 40%, #eff6ff 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.layout-main {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.walkin-page {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 动态背景层 ===== */
|
|
|
+.bg-layer {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.bg-orb {
|
|
|
+ position: absolute;
|
|
|
+ border-radius: 50%;
|
|
|
+ filter: blur(60px);
|
|
|
+ opacity: 0.45;
|
|
|
+}
|
|
|
+
|
|
|
+.orb-1 {
|
|
|
+ width: 380px;
|
|
|
+ height: 380px;
|
|
|
+ background: radial-gradient(circle, rgba(59, 130, 246, 0.35) 0%, transparent 70%);
|
|
|
+ top: -80px;
|
|
|
+ right: -80px;
|
|
|
+ animation: float1 18s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.orb-2 {
|
|
|
+ width: 300px;
|
|
|
+ height: 300px;
|
|
|
+ background: radial-gradient(circle, rgba(99, 102, 241, 0.30) 0%, transparent 70%);
|
|
|
+ bottom: -40px;
|
|
|
+ left: -60px;
|
|
|
+ animation: float2 22s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.orb-3 {
|
|
|
+ width: 240px;
|
|
|
+ height: 240px;
|
|
|
+ background: radial-gradient(circle, rgba(14, 165, 233, 0.28) 0%, transparent 70%);
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ animation: float3 16s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.bg-grid-overlay {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ background-image:
|
|
|
+ linear-gradient(rgba(147, 197, 253, 0.15) 1px, transparent 1px),
|
|
|
+ linear-gradient(90deg, rgba(147, 197, 253, 0.15) 1px, transparent 1px);
|
|
|
+ background-size: 48px 48px;
|
|
|
+ mask-image: radial-gradient(ellipse 80% 80% at 50% 50%, black 40%, transparent 100%);
|
|
|
+ -webkit-mask-image: radial-gradient(ellipse 80% 80% at 50% 50%, black 40%, transparent 100%);
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes float1 {
|
|
|
+ 0%, 100% { transform: translate(0, 0) scale(1); }
|
|
|
+ 33% { transform: translate(-20px, 25px) scale(1.05); }
|
|
|
+ 66% { transform: translate(15px, -15px) scale(0.96); }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes float2 {
|
|
|
+ 0%, 100% { transform: translate(0, 0) scale(1); }
|
|
|
+ 40% { transform: translate(25px, -20px) scale(1.08); }
|
|
|
+ 70% { transform: translate(-10px, 15px) scale(0.95); }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes float3 {
|
|
|
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); }
|
|
|
+ 50% { transform: translate(-45%, -55%) scale(1.1); }
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 内容区 ===== */
|
|
|
+.walkin-content {
|
|
|
+ flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
- padding: 20px 0;
|
|
|
+ padding: 0 32px 28px;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
-.page-title {
|
|
|
- font-size: 32px;
|
|
|
- font-weight: 600;
|
|
|
- color: var(--text-primary);
|
|
|
- margin: 0 0 24px;
|
|
|
+/* ===== 标题区 ===== */
|
|
|
+.walkin-hero {
|
|
|
+ text-align: center;
|
|
|
+ margin: 16px 0 14px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.form-container {
|
|
|
- width: 100%;
|
|
|
- max-width: 500px;
|
|
|
- padding: 0 16px;
|
|
|
+.page-title {
|
|
|
+ font-size: 44px;
|
|
|
+ font-weight: 900;
|
|
|
+ color: var(--text-primary);
|
|
|
+ margin: 0 0 8px;
|
|
|
+ letter-spacing: 3px;
|
|
|
}
|
|
|
|
|
|
-.idcard-section {
|
|
|
- margin-bottom: 24px;
|
|
|
+.page-subtitle {
|
|
|
+ font-size: 22px;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ margin: 0;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.btn-idcard {
|
|
|
width: 100%;
|
|
|
- height: 80px;
|
|
|
+ height: 72px;
|
|
|
+ background: rgba(255, 255, 255, 0.75);
|
|
|
+ color: var(--primary);
|
|
|
+ border: 2px solid rgba(59, 130, 246, 0.30);
|
|
|
+ border-radius: 999px;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 700;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.18s ease;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- gap: 16px;
|
|
|
- background: var(--primary-soft);
|
|
|
- color: var(--primary);
|
|
|
- border: 2px dashed var(--primary);
|
|
|
- border-radius: var(--radius-lg);
|
|
|
- font-size: 20px;
|
|
|
- font-weight: 500;
|
|
|
- cursor: pointer;
|
|
|
- transition: all var(--transition-fast);
|
|
|
+ gap: 12px;
|
|
|
}
|
|
|
|
|
|
.btn-idcard:hover:not(:disabled) {
|
|
|
- background: var(--primary);
|
|
|
- color: white;
|
|
|
- border-style: solid;
|
|
|
+ background: rgba(255, 255, 255, 0.92);
|
|
|
+ border-color: rgba(59, 130, 246, 0.45);
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-idcard:active {
|
|
|
+ transform: scale(0.97);
|
|
|
}
|
|
|
|
|
|
.btn-idcard:disabled {
|
|
|
- opacity: 0.6;
|
|
|
+ opacity: 0.5;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
|
|
|
.idcard-icon {
|
|
|
- width: 32px;
|
|
|
- height: 32px;
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.idcard-icon svg {
|
|
|
+/* ===== 登记信息卡片 ===== */
|
|
|
+.form-card {
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
+ max-width: 700px;
|
|
|
+ background: rgba(255, 255, 255, 0.88);
|
|
|
+ border-radius: 32px;
|
|
|
+ padding: 6px 0;
|
|
|
+ box-shadow:
|
|
|
+ 0 24px 64px rgba(30, 64, 175, 0.10),
|
|
|
+ 0 8px 24px rgba(0, 0, 0, 0.06);
|
|
|
+ backdrop-filter: blur(20px);
|
|
|
+ -webkit-backdrop-filter: blur(20px);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.60);
|
|
|
+ margin-bottom: 18px;
|
|
|
}
|
|
|
|
|
|
-.form-divider {
|
|
|
+.form-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 16px;
|
|
|
- margin: 24px 0;
|
|
|
+ padding: 0 32px;
|
|
|
+ min-height: 86px;
|
|
|
+ border-bottom: 1px solid rgba(226, 232, 240, 0.80);
|
|
|
+}
|
|
|
+
|
|
|
+.form-row:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.form-row-editable {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: background 0.15s;
|
|
|
}
|
|
|
|
|
|
-.form-divider::before,
|
|
|
-.form-divider::after {
|
|
|
- content: '';
|
|
|
+.form-row-editable:hover {
|
|
|
+ background: rgba(59, 130, 246, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.form-row-editable:active {
|
|
|
+ background: rgba(59, 130, 246, 0.08);
|
|
|
+}
|
|
|
+
|
|
|
+.form-label {
|
|
|
+ font-size: 25px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--text-secondary);
|
|
|
+ width: 150px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-value-wrapper {
|
|
|
flex: 1;
|
|
|
- height: 1px;
|
|
|
- background: var(--border-light);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--text-primary);
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-value-editable {
|
|
|
+ color: var(--primary);
|
|
|
+}
|
|
|
+
|
|
|
+.form-value-placeholder {
|
|
|
+ color: #94a3b8;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 25px;
|
|
|
+}
|
|
|
+
|
|
|
+.edit-icon {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ color: var(--primary);
|
|
|
+ flex-shrink: 0;
|
|
|
+ opacity: 0.65;
|
|
|
+ transition: opacity 0.15s;
|
|
|
}
|
|
|
|
|
|
-.form-divider span {
|
|
|
- font-size: 14px;
|
|
|
- color: var(--text-muted);
|
|
|
+.form-row-editable:hover .edit-icon {
|
|
|
+ opacity: 0.95;
|
|
|
}
|
|
|
|
|
|
-.form-fields {
|
|
|
+.edit-icon {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ color: var(--primary);
|
|
|
+ flex-shrink: 0;
|
|
|
+ opacity: 0.7;
|
|
|
+ transition: opacity 0.15s;
|
|
|
+}
|
|
|
+
|
|
|
+.form-row-editable:hover .edit-icon {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 底部操作区 ===== */
|
|
|
+.confirm-actions {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 20px;
|
|
|
- margin-bottom: 32px;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14px;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 700px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.primary-actions {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 2.2fr;
|
|
|
+ gap: 14px;
|
|
|
+ width: 100%;
|
|
|
+ align-items: stretch;
|
|
|
}
|
|
|
|
|
|
-.form-field {
|
|
|
+.btn-back,
|
|
|
+.btn-confirm {
|
|
|
+ height: 82px;
|
|
|
+ min-height: 82px;
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: 800;
|
|
|
+ border-radius: 999px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.22s ease;
|
|
|
+ letter-spacing: 1.5px;
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-back span,
|
|
|
+.btn-confirm span {
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-back {
|
|
|
+ background: rgba(255, 255, 255, 0.80);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ border: 2px solid var(--border-light);
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ -webkit-backdrop-filter: blur(10px);
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
-.form-field label {
|
|
|
- font-size: 16px;
|
|
|
+.btn-back:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border-color: var(--text-muted);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-back:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+}
|
|
|
+
|
|
|
+.back-icon {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
|
+ color: white;
|
|
|
+ border: 2px solid transparent;
|
|
|
+ box-shadow: 0 12px 28px rgba(37, 99, 235, 0.28);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm:hover:not(:disabled) {
|
|
|
+ box-shadow: 0 16px 36px rgba(37, 99, 235, 0.36);
|
|
|
+ transform: translateY(-2px);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm:disabled {
|
|
|
+ background: linear-gradient(135deg, #93c5fd 0%, #60a5fa 100%);
|
|
|
+ cursor: not-allowed;
|
|
|
+ box-shadow: none;
|
|
|
+ transform: none;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-spinner {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ border: 3px solid rgba(255, 255, 255, 0.35);
|
|
|
+ border-top-color: white;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes spin {
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 手机号/身份证号弹窗 ===== */
|
|
|
+.phone-modal {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.48);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 200;
|
|
|
+ animation: fadeIn 0.2s ease-out;
|
|
|
+ backdrop-filter: blur(6px);
|
|
|
+ -webkit-backdrop-filter: blur(6px);
|
|
|
+}
|
|
|
+
|
|
|
+.phone-modal .modal-card {
|
|
|
+ width: 800px;
|
|
|
+ max-width: calc(100vw - 48px);
|
|
|
+ max-height: calc(100vh - 48px);
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 44px 56px 40px;
|
|
|
+ background: rgba(255, 255, 255, 0.97);
|
|
|
+ border-radius: 40px;
|
|
|
+ box-shadow:
|
|
|
+ 0 40px 100px rgba(30, 64, 175, 0.22),
|
|
|
+ 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
|
+ backdrop-filter: blur(20px);
|
|
|
+ -webkit-backdrop-filter: blur(20px);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ scrollbar-width: thin;
|
|
|
+}
|
|
|
+
|
|
|
+.phone-modal .modal-header {
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 28px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.phone-modal .modal-header h2 {
|
|
|
+ font-size: 36px;
|
|
|
+ font-weight: 900;
|
|
|
+ color: var(--text-primary);
|
|
|
+ margin: 0 0 8px;
|
|
|
+ letter-spacing: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.phone-modal .modal-header p {
|
|
|
+ font-size: 20px;
|
|
|
color: var(--text-secondary);
|
|
|
+ margin: 0;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
-.input {
|
|
|
+/* 操作按钮 */
|
|
|
+.modal-actions {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1.5fr;
|
|
|
+ gap: 16px;
|
|
|
width: 100%;
|
|
|
- height: 56px;
|
|
|
- padding: 0 16px;
|
|
|
- font-size: 18px;
|
|
|
- background: var(--bg-card);
|
|
|
- border: 2px solid var(--border-light);
|
|
|
- border-radius: var(--radius-lg);
|
|
|
- transition: border-color var(--transition-fast);
|
|
|
+ margin-top: 24px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-cancel,
|
|
|
+.btn-confirm {
|
|
|
+ height: 74px;
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: 800;
|
|
|
+ border-radius: 999px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.22s ease;
|
|
|
+ letter-spacing: 1.5px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-cancel {
|
|
|
+ background: rgba(107, 114, 128, 0.10);
|
|
|
+ color: #4b5563;
|
|
|
+ border: 1px solid rgba(107, 114, 128, 0.15);
|
|
|
}
|
|
|
|
|
|
-.input:focus {
|
|
|
- border-color: var(--primary);
|
|
|
+.btn-cancel:hover {
|
|
|
+ background: rgba(107, 114, 128, 0.16);
|
|
|
}
|
|
|
|
|
|
-.input::placeholder {
|
|
|
- color: var(--text-light);
|
|
|
+.btn-cancel:active {
|
|
|
+ transform: scale(0.97);
|
|
|
}
|
|
|
|
|
|
-.input-with-keyboard {
|
|
|
- height: 56px;
|
|
|
- padding: 0 16px;
|
|
|
+.btn-confirm {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ box-shadow: 0 12px 28px rgba(37, 99, 235, 0.28);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background: var(--bg-card);
|
|
|
- border: 2px solid var(--border-light);
|
|
|
- border-radius: var(--radius-lg);
|
|
|
- cursor: pointer;
|
|
|
- transition: border-color var(--transition-fast);
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
-.input-with-keyboard:hover {
|
|
|
- border-color: var(--primary);
|
|
|
+.btn-confirm:hover {
|
|
|
+ box-shadow: 0 16px 36px rgba(37, 99, 235, 0.36);
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-.input-value {
|
|
|
- font-size: 18px;
|
|
|
- color: var(--text-light);
|
|
|
+.btn-confirm:active {
|
|
|
+ transform: scale(0.97);
|
|
|
}
|
|
|
|
|
|
-.input-with-keyboard:has(.input-value:not(:empty)) .input-value {
|
|
|
- color: var(--text-primary);
|
|
|
+/* ===== 来访事由选项弹窗 ===== */
|
|
|
+.reason-modal-overlay {
|
|
|
+ position: fixed;
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.50);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ -webkit-backdrop-filter: blur(4px);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 1000;
|
|
|
+ animation: fadeIn 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.reason-modal-card {
|
|
|
+ width: 560px;
|
|
|
+ background: rgba(255, 255, 255, 0.96);
|
|
|
+ border-radius: 32px;
|
|
|
+ padding: 40px 36px 36px;
|
|
|
+ box-shadow:
|
|
|
+ 0 40px 80px rgba(0, 0, 0, 0.20),
|
|
|
+ 0 16px 40px rgba(0, 0, 0, 0.12);
|
|
|
+ backdrop-filter: blur(20px);
|
|
|
+ -webkit-backdrop-filter: blur(20px);
|
|
|
+ animation: slideUp 0.22s ease;
|
|
|
}
|
|
|
|
|
|
.reason-options {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 10px;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 14px;
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.reason-btn {
|
|
|
- padding: 10px 18px;
|
|
|
- font-size: 15px;
|
|
|
- background: var(--bg-card);
|
|
|
+ height: 72px;
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--text-primary);
|
|
|
+ background: rgba(255, 255, 255, 0.80);
|
|
|
border: 2px solid var(--border-light);
|
|
|
- border-radius: var(--radius-full);
|
|
|
- color: var(--text-secondary);
|
|
|
+ border-radius: 20px;
|
|
|
cursor: pointer;
|
|
|
- transition: all var(--transition-fast);
|
|
|
+ transition: all 0.18s ease;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
.reason-btn:hover {
|
|
|
- border-color: var(--primary);
|
|
|
- color: var(--primary);
|
|
|
+ background: rgba(59, 130, 246, 0.06);
|
|
|
+ border-color: rgba(59, 130, 246, 0.30);
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-.reason-btn.active {
|
|
|
- background: var(--primary);
|
|
|
- border-color: var(--primary);
|
|
|
- color: white;
|
|
|
+.reason-btn:active {
|
|
|
+ transform: scale(0.97);
|
|
|
}
|
|
|
|
|
|
-.btn-submit {
|
|
|
- width: 100%;
|
|
|
- height: 64px;
|
|
|
- font-size: 22px;
|
|
|
- font-weight: 600;
|
|
|
- background: var(--primary);
|
|
|
+.reason-btn-active {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
|
color: white;
|
|
|
- border: none;
|
|
|
- border-radius: var(--radius-lg);
|
|
|
+ border-color: transparent;
|
|
|
+ box-shadow: 0 8px 20px rgba(37, 99, 235, 0.24);
|
|
|
+}
|
|
|
+
|
|
|
+.reason-btn-active:hover {
|
|
|
+ background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
|
|
|
+ border-color: transparent;
|
|
|
+ box-shadow: 0 10px 24px rgba(37, 99, 235, 0.30);
|
|
|
+}
|
|
|
+
|
|
|
+.reason-modal-actions {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1.5fr;
|
|
|
+ gap: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.reason-btn-cancel,
|
|
|
+.reason-btn-save {
|
|
|
+ height: 72px;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 800;
|
|
|
+ border-radius: 999px;
|
|
|
cursor: pointer;
|
|
|
- box-shadow: var(--shadow-md);
|
|
|
- transition: all var(--transition-fast);
|
|
|
+ transition: all 0.22s ease;
|
|
|
+ letter-spacing: 1.5px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
-.btn-submit:hover:not(:disabled) {
|
|
|
- background: var(--primary-dark);
|
|
|
- transform: translateY(-2px);
|
|
|
- box-shadow: var(--shadow-lg);
|
|
|
+.reason-btn-cancel {
|
|
|
+ background: rgba(255, 255, 255, 0.80);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ border: 2px solid var(--border-light);
|
|
|
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
|
|
|
}
|
|
|
|
|
|
-.btn-submit:disabled {
|
|
|
- opacity: 0.7;
|
|
|
- cursor: not-allowed;
|
|
|
+.reason-btn-cancel:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border-color: var(--text-muted);
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-.loading-spinner {
|
|
|
- width: 24px;
|
|
|
- height: 24px;
|
|
|
- border: 3px solid rgba(255, 255, 255, 0.3);
|
|
|
- border-top-color: white;
|
|
|
- border-radius: 50%;
|
|
|
- animation: spin 1s linear infinite;
|
|
|
+.reason-btn-save {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ box-shadow: 0 10px 24px rgba(37, 99, 235, 0.26);
|
|
|
}
|
|
|
|
|
|
-@keyframes spin {
|
|
|
- to {
|
|
|
- transform: rotate(360deg);
|
|
|
- }
|
|
|
+.reason-btn-save:hover {
|
|
|
+ box-shadow: 0 14px 32px rgba(37, 99, 235, 0.34);
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-/* 键盘弹窗 */
|
|
|
-.keyboard-modal {
|
|
|
+.reason-btn-cancel:active,
|
|
|
+.reason-btn-save:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 文本输入弹窗 ===== */
|
|
|
+.text-modal-overlay {
|
|
|
position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
+ inset: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.50);
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ -webkit-backdrop-filter: blur(4px);
|
|
|
display: flex;
|
|
|
- align-items: flex-end;
|
|
|
- z-index: 100;
|
|
|
- animation: fadeIn 0.2s ease-out;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 1000;
|
|
|
+ animation: fadeIn 0.2s ease;
|
|
|
}
|
|
|
|
|
|
-.keyboard-content {
|
|
|
- width: 100%;
|
|
|
- background: var(--bg-card);
|
|
|
- border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
|
|
- padding: 20px;
|
|
|
- padding-bottom: 40px;
|
|
|
+.text-modal-card {
|
|
|
+ width: 560px;
|
|
|
+ background: rgba(255, 255, 255, 0.96);
|
|
|
+ border-radius: 32px;
|
|
|
+ padding: 40px 36px 36px;
|
|
|
+ box-shadow:
|
|
|
+ 0 40px 80px rgba(0, 0, 0, 0.20),
|
|
|
+ 0 16px 40px rgba(0, 0, 0, 0.12);
|
|
|
+ backdrop-filter: blur(20px);
|
|
|
+ -webkit-backdrop-filter: blur(20px);
|
|
|
+ animation: slideUp 0.22s ease;
|
|
|
}
|
|
|
|
|
|
-.keyboard-header {
|
|
|
+.text-input-wrapper {
|
|
|
+ background: linear-gradient(160deg, #f0f7ff 0%, #e8f4fd 100%);
|
|
|
+ border: 2px solid rgba(59, 130, 246, 0.18);
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 0 24px;
|
|
|
+ height: 96px;
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
-.keyboard-header span {
|
|
|
- font-size: 18px;
|
|
|
- font-weight: 500;
|
|
|
+.text-modal-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ font-size: 30px;
|
|
|
+ font-weight: 700;
|
|
|
color: var(--text-primary);
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ outline: none;
|
|
|
+ letter-spacing: 1px;
|
|
|
}
|
|
|
|
|
|
-.keyboard-close {
|
|
|
- padding: 8px 20px;
|
|
|
- font-size: 16px;
|
|
|
- color: var(--primary);
|
|
|
- background: none;
|
|
|
- border: none;
|
|
|
+.text-modal-input::placeholder {
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #b0bec5;
|
|
|
+}
|
|
|
+
|
|
|
+.text-modal-actions {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1.5fr;
|
|
|
+ gap: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.text-btn-cancel,
|
|
|
+.text-btn-save {
|
|
|
+ height: 72px;
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 800;
|
|
|
+ border-radius: 999px;
|
|
|
cursor: pointer;
|
|
|
+ transition: all 0.22s ease;
|
|
|
+ letter-spacing: 1.5px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.text-btn-cancel {
|
|
|
+ background: rgba(255, 255, 255, 0.80);
|
|
|
+ color: var(--text-secondary);
|
|
|
+ border: 2px solid var(--border-light);
|
|
|
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.text-btn-cancel:hover {
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border-color: var(--text-muted);
|
|
|
+ transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
-@keyframes fadeIn {
|
|
|
- from {
|
|
|
- opacity: 0;
|
|
|
+.text-btn-save {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ box-shadow: 0 10px 24px rgba(37, 99, 235, 0.26);
|
|
|
+}
|
|
|
+
|
|
|
+.text-btn-save:hover {
|
|
|
+ box-shadow: 0 14px 32px rgba(37, 99, 235, 0.34);
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.text-btn-cancel:active,
|
|
|
+.text-btn-save:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 响应式适配 ===== */
|
|
|
+@media (max-height: 700px) {
|
|
|
+ .walkin-content {
|
|
|
+ padding: 0 24px 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .walkin-hero {
|
|
|
+ margin: 12px 0 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-title {
|
|
|
+ font-size: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-subtitle {
|
|
|
+ font-size: 18px;
|
|
|
}
|
|
|
- to {
|
|
|
- opacity: 1;
|
|
|
+
|
|
|
+ .form-row {
|
|
|
+ min-height: 68px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-label {
|
|
|
+ font-size: 20px;
|
|
|
+ width: 110px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-value,
|
|
|
+ .form-value-editable {
|
|
|
+ font-size: 22px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-value-placeholder {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-card {
|
|
|
+ max-width: 700px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .edit-icon {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .confirm-actions {
|
|
|
+ max-width: 700px;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .primary-actions {
|
|
|
+ grid-template-columns: 1fr 2fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-back,
|
|
|
+ .btn-confirm {
|
|
|
+ height: 70px;
|
|
|
+ min-height: 70px;
|
|
|
+ font-size: 22px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-confirm {
|
|
|
+ border: 2px solid transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-idcard {
|
|
|
+ height: 62px;
|
|
|
+ font-size: 22px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .idcard-icon {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|