feat(添加参观者): 重构表单界面并添加证件类型选择功能
重构添加参观者页面表单布局,使用卡片式设计提升用户体验 添加证件类型选择器,支持身份证和其他证件类型 移除历史参观者列表功能以简化页面 添加表单验证和温馨提示信息
Showing
2 changed files
with
151 additions
and
104 deletions
| ... | @@ -11,9 +11,8 @@ declare module 'vue' { | ... | @@ -11,9 +11,8 @@ declare module 'vue' { |
| 11 | NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox'] | 11 | NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox'] |
| 12 | NutCheckboxGroup: typeof import('@nutui/nutui-taro')['CheckboxGroup'] | 12 | NutCheckboxGroup: typeof import('@nutui/nutui-taro')['CheckboxGroup'] |
| 13 | NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker'] | 13 | NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker'] |
| 14 | - NutForm: typeof import('@nutui/nutui-taro')['Form'] | ||
| 15 | - NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] | ||
| 16 | NutInput: typeof import('@nutui/nutui-taro')['Input'] | 14 | NutInput: typeof import('@nutui/nutui-taro')['Input'] |
| 15 | + NutPicker: typeof import('@nutui/nutui-taro')['Picker'] | ||
| 17 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] | 16 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] |
| 18 | OfflineQrCode: typeof import('./src/components/offlineQrCode.vue')['default'] | 17 | OfflineQrCode: typeof import('./src/components/offlineQrCode.vue')['default'] |
| 19 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | 18 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2024-01-15 16:35:10 | 2 | * @Date: 2024-01-15 16:35:10 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-06 23:28:40 | 4 | + * @LastEditTime: 2026-01-07 22:54:19 |
| 5 | * @FilePath: /xyxBooking-weapp/src/pages/addVisitor/index.vue | 5 | * @FilePath: /xyxBooking-weapp/src/pages/addVisitor/index.vue |
| 6 | * @Description: 添加参观者 | 6 | * @Description: 添加参观者 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <view class="add-visitor-page"> | 9 | <view class="add-visitor-page"> |
| 10 | - <nut-form> | 10 | + <view class="content"> |
| 11 | - <nut-form-item label="姓名"> | 11 | + <view class="form-card"> |
| 12 | - <nut-input v-model="name" placeholder="请输入参观者姓名" type="text" /> | 12 | + <view class="form-row"> |
| 13 | - </nut-form-item> | 13 | + <view class="label">姓名</view> |
| 14 | - <nut-form-item label="证件类型"> | 14 | + <nut-input v-model="name" class="field-input" placeholder="请输入参观者真实姓名" type="text" input-align="right" :border="false" /> |
| 15 | - <view style="padding: 20rpx 0;">身份证</view> | 15 | + </view> |
| 16 | - </nut-form-item> | 16 | + <view class="form-row"> |
| 17 | - <nut-form-item label="证件号"> | 17 | + <view class="label">证件类型</view> |
| 18 | - <nut-input v-model="id_number" placeholder="请输入参观者证件号" type="idcard" /> | 18 | + <view class="field-value picker-value" @tap="open_id_type_picker"> |
| 19 | - </nut-form-item> | 19 | + <text>{{ id_type_label }}</text> |
| 20 | - </nut-form> | 20 | + <text class="picker-arrow">›</text> |
| 21 | - | 21 | + </view> |
| 22 | - <view style="padding: 32rpx;"> | 22 | + </view> |
| 23 | - <nut-button type="primary" block color="#A67939" @click="save">保存</nut-button> | 23 | + <view class="form-row"> |
| 24 | - </view> | 24 | + <view class="label">证件号码</view> |
| 25 | - | 25 | + <nut-input v-model="id_number" class="field-input" placeholder="请输入证件号码" :type="id_number_type" input-align="right" :border="false" /> |
| 26 | - <view v-if="visitorList.length" class="history-list"> | ||
| 27 | - <view class="title">历史参观者</view> | ||
| 28 | - <view v-for="(item, index) in visitorList" :key="index" class="item"> | ||
| 29 | - <view class="info"> | ||
| 30 | - <view class="name">{{ item.name }}</view> | ||
| 31 | - <view class="id">{{ formatId(item.id_number) }}</view> | ||
| 32 | </view> | 26 | </view> |
| 33 | - <view class="action" @tap="delVisitor(item.id)">删除</view> | 27 | + </view> |
| 28 | + | ||
| 29 | + <view class="tip"> | ||
| 30 | + <IconFont name="tips" size="14" color="#C7A46D" /> | ||
| 31 | + <text class="tip-text">温馨提示:账号实名认证信息一经填写将无法修改</text> | ||
| 34 | </view> | 32 | </view> |
| 35 | </view> | 33 | </view> |
| 34 | + | ||
| 35 | + <view class="footer"> | ||
| 36 | + <view class="save-btn" @tap="save">保存</view> | ||
| 37 | + </view> | ||
| 38 | + | ||
| 39 | + <nut-popup v-model:visible="show_id_type_picker" position="bottom" safe-area-inset-bottom> | ||
| 40 | + <nut-picker | ||
| 41 | + v-model="id_type_picker_value" | ||
| 42 | + :columns="id_type_columns" | ||
| 43 | + title="选择证件类型" | ||
| 44 | + @confirm="on_id_type_confirm" | ||
| 45 | + @cancel="show_id_type_picker = false" | ||
| 46 | + ></nut-picker> | ||
| 47 | + </nut-popup> | ||
| 36 | </view> | 48 | </view> |
| 37 | </template> | 49 | </template> |
| 38 | 50 | ||
| 39 | <script setup> | 51 | <script setup> |
| 40 | -import { ref } from 'vue' | 52 | +import { ref, computed } from 'vue' |
| 41 | -import Taro, { useDidShow } from '@tarojs/taro' | 53 | +import Taro from '@tarojs/taro' |
| 42 | -import { personListAPI, addPersonAPI, delPersonAPI } from '@/api/index' | 54 | +import { addPersonAPI } from '@/api/index' |
| 55 | +import { IconFont } from '@nutui/icons-vue-taro' | ||
| 43 | 56 | ||
| 44 | const name = ref(''); | 57 | const name = ref(''); |
| 45 | const id_number = ref(''); | 58 | const id_number = ref(''); |
| 46 | -const visitorList = ref([]); | 59 | +const show_id_type_picker = ref(false); |
| 60 | +const id_type_options = [ | ||
| 61 | + { label: '身份证', value: 1 }, | ||
| 62 | + { label: '其他', value: 3 } | ||
| 63 | +]; | ||
| 64 | +const id_type = ref(id_type_options[0].value); | ||
| 65 | +const id_type_picker_value = ref([String(id_type.value)]); | ||
| 66 | + | ||
| 67 | +const id_type_columns = computed(() => { | ||
| 68 | + return id_type_options.map(item => ({ | ||
| 69 | + text: item.label, | ||
| 70 | + value: String(item.value) | ||
| 71 | + })); | ||
| 72 | +}); | ||
| 73 | +const id_type_label = computed(() => { | ||
| 74 | + return id_type_options.find(item => item.value === id_type.value)?.label || id_type_options[0].label; | ||
| 75 | +}); | ||
| 76 | +const id_number_type = computed(() => (id_type.value === 1 ? 'idcard' : 'text')); | ||
| 77 | + | ||
| 78 | +const open_id_type_picker = () => { | ||
| 79 | + id_type_picker_value.value = [String(id_type.value)]; | ||
| 80 | + show_id_type_picker.value = true; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +const on_id_type_confirm = ({ selectedValue }) => { | ||
| 84 | + const value = selectedValue?.[0]; | ||
| 85 | + id_type.value = Number(value) || 1; | ||
| 86 | + show_id_type_picker.value = false; | ||
| 87 | +} | ||
| 47 | 88 | ||
| 48 | // 身份证校验 | 89 | // 身份证校验 |
| 49 | const checkIDCard = (idcode) => { | 90 | const checkIDCard = (idcode) => { |
| ... | @@ -51,31 +92,16 @@ const checkIDCard = (idcode) => { | ... | @@ -51,31 +92,16 @@ const checkIDCard = (idcode) => { |
| 51 | return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idcode); | 92 | return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idcode); |
| 52 | } | 93 | } |
| 53 | 94 | ||
| 54 | -function replaceMiddleCharacters(inputString) { | ||
| 55 | - if (!inputString || inputString.length < 15) { | ||
| 56 | - return inputString; | ||
| 57 | - } | ||
| 58 | - const start = Math.floor((inputString.length - 8) / 2); | ||
| 59 | - const end = start + 8; | ||
| 60 | - const replacement = '*'.repeat(8); | ||
| 61 | - return inputString.substring(0, start) + replacement + inputString.substring(end); | ||
| 62 | -} | ||
| 63 | - | ||
| 64 | -const formatId = (id) => replaceMiddleCharacters(id); | ||
| 65 | - | ||
| 66 | -const loadList = async () => { | ||
| 67 | - const { code, data } = await personListAPI({}); | ||
| 68 | - if (code) { | ||
| 69 | - visitorList.value = data || []; | ||
| 70 | - } | ||
| 71 | -} | ||
| 72 | - | ||
| 73 | const save = async () => { | 95 | const save = async () => { |
| 74 | if (!name.value) { | 96 | if (!name.value) { |
| 75 | Taro.showToast({ title: '请输入姓名', icon: 'none' }); | 97 | Taro.showToast({ title: '请输入姓名', icon: 'none' }); |
| 76 | return; | 98 | return; |
| 77 | } | 99 | } |
| 78 | - if (!checkIDCard(id_number.value)) { | 100 | + if (!id_number.value) { |
| 101 | + Taro.showToast({ title: '请输入证件号码', icon: 'none' }); | ||
| 102 | + return; | ||
| 103 | + } | ||
| 104 | + if (id_type.value === 1 && !checkIDCard(id_number.value)) { | ||
| 79 | Taro.showToast({ title: '请输入正确的身份证号', icon: 'none' }); | 105 | Taro.showToast({ title: '请输入正确的身份证号', icon: 'none' }); |
| 80 | return; | 106 | return; |
| 81 | } | 107 | } |
| ... | @@ -83,7 +109,7 @@ const save = async () => { | ... | @@ -83,7 +109,7 @@ const save = async () => { |
| 83 | Taro.showLoading({ title: '保存中' }); | 109 | Taro.showLoading({ title: '保存中' }); |
| 84 | const { code, msg } = await addPersonAPI({ | 110 | const { code, msg } = await addPersonAPI({ |
| 85 | name: name.value, | 111 | name: name.value, |
| 86 | - id_type: 1, // 身份证 | 112 | + id_type: id_type.value, |
| 87 | id_number: id_number.value | 113 | id_number: id_number.value |
| 88 | }); | 114 | }); |
| 89 | Taro.hideLoading(); | 115 | Taro.hideLoading(); |
| ... | @@ -92,30 +118,11 @@ const save = async () => { | ... | @@ -92,30 +118,11 @@ const save = async () => { |
| 92 | Taro.showToast({ title: '添加成功' }); | 118 | Taro.showToast({ title: '添加成功' }); |
| 93 | name.value = ''; | 119 | name.value = ''; |
| 94 | id_number.value = ''; | 120 | id_number.value = ''; |
| 95 | - loadList(); | ||
| 96 | - // 自动返回上一页? H5 没有自动返回 | ||
| 97 | Taro.navigateBack(); | 121 | Taro.navigateBack(); |
| 98 | } else { | 122 | } else { |
| 99 | Taro.showToast({ title: msg || '添加失败', icon: 'none' }); | 123 | Taro.showToast({ title: msg || '添加失败', icon: 'none' }); |
| 100 | } | 124 | } |
| 101 | } | 125 | } |
| 102 | - | ||
| 103 | -const delVisitor = async (id) => { | ||
| 104 | - const { confirm } = await Taro.showModal({ title: '提示', content: '确定删除该参观者吗?' }); | ||
| 105 | - if (confirm) { | ||
| 106 | - const { code, msg } = await delPersonAPI({ person_id: id }); | ||
| 107 | - if (code) { | ||
| 108 | - Taro.showToast({ title: '删除成功' }); | ||
| 109 | - loadList(); | ||
| 110 | - } else { | ||
| 111 | - Taro.showToast({ title: msg || '删除失败', icon: 'none' }); | ||
| 112 | - } | ||
| 113 | - } | ||
| 114 | -} | ||
| 115 | - | ||
| 116 | -useDidShow(() => { | ||
| 117 | - loadList(); | ||
| 118 | -}) | ||
| 119 | </script> | 126 | </script> |
| 120 | 127 | ||
| 121 | <style lang="less"> | 128 | <style lang="less"> |
| ... | @@ -124,48 +131,89 @@ useDidShow(() => { | ... | @@ -124,48 +131,89 @@ useDidShow(() => { |
| 124 | background-color: #F6F6F6; | 131 | background-color: #F6F6F6; |
| 125 | padding-top: 2rpx; | 132 | padding-top: 2rpx; |
| 126 | 133 | ||
| 127 | - .history-list { | 134 | + .content { |
| 128 | - margin-top: 32rpx; | ||
| 129 | - background-color: #FFF; | ||
| 130 | padding: 32rpx; | 135 | padding: 32rpx; |
| 136 | + padding-bottom: 220rpx; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + .form-card { | ||
| 140 | + background-color: #FFF; | ||
| 141 | + border-radius: 16rpx; | ||
| 142 | + overflow: hidden; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + .form-row { | ||
| 146 | + display: flex; | ||
| 147 | + align-items: center; | ||
| 148 | + padding: 0 32rpx; | ||
| 149 | + height: 112rpx; | ||
| 150 | + | ||
| 151 | + &:not(:last-child) { | ||
| 152 | + border-bottom: 2rpx solid #F2F2F2; | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + .label { | ||
| 156 | + width: 160rpx; | ||
| 157 | + color: #333; | ||
| 158 | + font-size: 30rpx; | ||
| 159 | + } | ||
| 131 | 160 | ||
| 132 | - .title { | 161 | + .field-value { |
| 133 | - font-size: 32rpx; | 162 | + flex: 1; |
| 163 | + text-align: right; | ||
| 134 | color: #333; | 164 | color: #333; |
| 135 | - margin-bottom: 32rpx; | 165 | + font-size: 30rpx; |
| 136 | - border-left: 6rpx solid #A67939; | ||
| 137 | - padding-left: 16rpx; | ||
| 138 | } | 166 | } |
| 139 | 167 | ||
| 140 | - .item { | 168 | + .field-input { |
| 169 | + flex: 1; | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + .picker-value { | ||
| 141 | display: flex; | 173 | display: flex; |
| 142 | - justify-content: space-between; | ||
| 143 | align-items: center; | 174 | align-items: center; |
| 144 | - padding: 32rpx 0; | 175 | + justify-content: flex-end; |
| 145 | - border-bottom: 2rpx solid #EEE; | 176 | + } |
| 146 | - | 177 | + |
| 147 | - &:last-child { | 178 | + .picker-arrow { |
| 148 | - border-bottom: none; | 179 | + margin-left: 10rpx; |
| 149 | - } | 180 | + color: #BBB; |
| 150 | - | 181 | + font-size: 28rpx; |
| 151 | - .info { | 182 | + } |
| 152 | - .name { | 183 | + } |
| 153 | - font-size: 32rpx; | 184 | + |
| 154 | - color: #333; | 185 | + .tip { |
| 155 | - margin-bottom: 10rpx; | 186 | + margin-top: 28rpx; |
| 156 | - } | 187 | + display: flex; |
| 157 | - | 188 | + align-items: center; |
| 158 | - .id { | 189 | + color: #C7A46D; |
| 159 | - font-size: 29rpx; | 190 | + font-size: 24rpx; |
| 160 | - color: #999; | 191 | + |
| 161 | - } | 192 | + .tip-text { |
| 162 | - } | 193 | + margin-left: 10rpx; |
| 163 | - | ||
| 164 | - .action { | ||
| 165 | - color: #FF0000; | ||
| 166 | - font-size: 29rpx; | ||
| 167 | - } | ||
| 168 | } | 194 | } |
| 169 | } | 195 | } |
| 196 | + | ||
| 197 | + .footer { | ||
| 198 | + position: fixed; | ||
| 199 | + left: 0; | ||
| 200 | + right: 0; | ||
| 201 | + bottom: 0; | ||
| 202 | + padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); | ||
| 203 | + background-color: #F6F6F6; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + .save-btn { | ||
| 207 | + width: 686rpx; | ||
| 208 | + height: 96rpx; | ||
| 209 | + background-color: #A67939; | ||
| 210 | + border-radius: 12rpx; | ||
| 211 | + display: flex; | ||
| 212 | + align-items: center; | ||
| 213 | + justify-content: center; | ||
| 214 | + color: #FFF; | ||
| 215 | + font-size: 34rpx; | ||
| 216 | + font-weight: 600; | ||
| 217 | + } | ||
| 170 | } | 218 | } |
| 171 | </style> | 219 | </style> | ... | ... |
-
Please register or login to post a comment