hookehuyr

feat(添加参观者): 重构表单界面并添加证件类型选择功能

重构添加参观者页面表单布局,使用卡片式设计提升用户体验
添加证件类型选择器,支持身份证和其他证件类型
移除历史参观者列表功能以简化页面
添加表单验证和温馨提示信息
...@@ -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 class="label">证件号码</view>
25 + <nut-input v-model="id_number" class="field-input" placeholder="请输入证件号码" :type="id_number_type" input-align="right" :border="false" />
26 + </view>
24 </view> 27 </view>
25 28
26 - <view v-if="visitorList.length" class="history-list"> 29 + <view class="tip">
27 - <view class="title">历史参观者</view> 30 + <IconFont name="tips" size="14" color="#C7A46D" />
28 - <view v-for="(item, index) in visitorList" :key="index" class="item"> 31 + <text class="tip-text">温馨提示:账号实名认证信息一经填写将无法修改</text>
29 - <view class="info">
30 - <view class="name">{{ item.name }}</view>
31 - <view class="id">{{ formatId(item.id_number) }}</view>
32 </view> 32 </view>
33 - <view class="action" @tap="delVisitor(item.id)">删除</view>
34 </view> 33 </view>
34 +
35 + <view class="footer">
36 + <view class="save-btn" @tap="save">保存</view>
35 </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 + }
131 138
132 - .title { 139 + .form-card {
133 - font-size: 32rpx; 140 + background-color: #FFF;
134 - color: #333; 141 + border-radius: 16rpx;
135 - margin-bottom: 32rpx; 142 + overflow: hidden;
136 - border-left: 6rpx solid #A67939;
137 - padding-left: 16rpx;
138 } 143 }
139 144
140 - .item { 145 + .form-row {
141 display: flex; 146 display: flex;
142 - justify-content: space-between;
143 align-items: center; 147 align-items: center;
144 - padding: 32rpx 0; 148 + padding: 0 32rpx;
145 - border-bottom: 2rpx solid #EEE; 149 + height: 112rpx;
146 150
147 - &:last-child { 151 + &:not(:last-child) {
148 - border-bottom: none; 152 + border-bottom: 2rpx solid #F2F2F2;
149 } 153 }
150 154
151 - .info { 155 + .label {
152 - .name { 156 + width: 160rpx;
153 - font-size: 32rpx;
154 color: #333; 157 color: #333;
155 - margin-bottom: 10rpx; 158 + font-size: 30rpx;
159 + }
160 +
161 + .field-value {
162 + flex: 1;
163 + text-align: right;
164 + color: #333;
165 + font-size: 30rpx;
166 + }
167 +
168 + .field-input {
169 + flex: 1;
170 + }
171 +
172 + .picker-value {
173 + display: flex;
174 + align-items: center;
175 + justify-content: flex-end;
156 } 176 }
157 177
158 - .id { 178 + .picker-arrow {
159 - font-size: 29rpx; 179 + margin-left: 10rpx;
160 - color: #999; 180 + color: #BBB;
181 + font-size: 28rpx;
161 } 182 }
162 } 183 }
163 184
164 - .action { 185 + .tip {
165 - color: #FF0000; 186 + margin-top: 28rpx;
166 - font-size: 29rpx; 187 + display: flex;
188 + align-items: center;
189 + color: #C7A46D;
190 + font-size: 24rpx;
191 +
192 + .tip-text {
193 + margin-left: 10rpx;
167 } 194 }
168 } 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;
169 } 217 }
170 } 218 }
171 </style> 219 </style>
......