Bladeren bron

完成访客记录页面优化

yawuga 3 weken geleden
bovenliggende
commit
a317ff78c1

+ 125 - 276
src/views/base/visitorRecord/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px">
       <el-form-item label="访客姓名" prop="visitorName">
         <el-input
           v-model="queryParams.visitorName"
@@ -9,90 +9,62 @@
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="访客手机号" prop="mobile">
+      <el-form-item label="手机号" prop="mobile">
         <el-input
           v-model="queryParams.mobile"
-          placeholder="请输入访客手机号"
+          placeholder="请输入手机号"
           clearable
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="证" prop="idCardNo">
+      <el-form-item label="身份证号" prop="idCardNo">
         <el-input
           v-model="queryParams.idCardNo"
-          placeholder="请输入证"
+          placeholder="请输入身份证号"
           clearable
           @keyup.enter="handleQuery"
         />
       </el-form-item>
       <el-form-item label="到访类型" prop="visitType">
-        <el-select v-model="queryParams.visitType" placeholder="请选择到访类型" clearable>
+        <el-select v-model="queryParams.visitType" placeholder="请选择到访类型" clearable style="width: 180px">
           <el-option
             v-for="dict in visitor_visit_type"
             :key="dict.value"
             :label="dict.label"
-            :value="dict.value"
+            :value="String(dict.value)"
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="访客来源,如公司、单位、亲友、外卖、快递、供应商等" prop="visitorSource">
-        <el-input
-          v-model="queryParams.visitorSource"
-          placeholder="请输入访客来源,如公司、单位、亲友、外卖、快递、供应商等"
-          clearable
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等" prop="visitReason">
-        <el-input
-          v-model="queryParams.visitReason"
-          placeholder="请输入来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等"
-          clearable
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="访客照片地址" prop="visitorPhoto">
-        <el-input
-          v-model="queryParams.visitorPhoto"
-          placeholder="请输入访客照片地址"
-          clearable
-          @keyup.enter="handleQuery"
-        />
+      <el-form-item label="登记方式" prop="registerType">
+        <el-select v-model="queryParams.registerType" placeholder="请选择登记方式" clearable style="width: 180px">
+          <el-option
+            v-for="dict in visitor_register_type"
+            :key="dict.value"
+            :label="dict.label"
+            :value="String(dict.value)"
+          />
+        </el-select>
       </el-form-item>
-      <el-form-item label="关联预约单号,现场登记可为空" prop="appointmentNo">
+      <el-form-item label="被访对象" prop="visitedPerson">
         <el-input
-          v-model="queryParams.appointmentNo"
-          placeholder="请输入关联预约单号,现场登记可为空"
+          v-model="queryParams.visitedPerson"
+          placeholder="请输入被访对象"
           clearable
           @keyup.enter="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="被访人/被访对象" prop="visitedPerson">
-        <el-input
-          v-model="queryParams.visitedPerson"
-          placeholder="请输入被访人/被访对象"
+      <el-form-item label="来访时间" prop="visitTimeRange">
+        <el-date-picker
+          v-model="visitTimeRange"
+          type="datetimerange"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
           clearable
-          @keyup.enter="handleQuery"
+          style="width: 360px"
         />
       </el-form-item>
-      <el-form-item label="来访/登记时间" prop="visitTime">
-        <el-date-picker clearable
-          v-model="queryParams.visitTime"
-          type="date"
-          value-format="YYYY-MM-DD"
-          placeholder="请选择来访/登记时间">
-        </el-date-picker>
-      </el-form-item>
-      <el-form-item label="登记方式" prop="registerType">
-        <el-select v-model="queryParams.registerType" placeholder="请选择登记方式" clearable>
-          <el-option
-            v-for="dict in visitor_register_type"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
         <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -100,35 +72,6 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          icon="Plus"
-          @click="handleAdd"
-          v-hasPermi="['base:visitorRecord:add']"
-        >新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="success"
-          plain
-          icon="Edit"
-          :disabled="single"
-          @click="handleUpdate"
-          v-hasPermi="['base:visitorRecord:edit']"
-        >修改</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="danger"
-          plain
-          icon="Delete"
-          :disabled="multiple"
-          @click="handleDelete"
-          v-hasPermi="['base:visitorRecord:remove']"
-        >删除</el-button>
-      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -141,41 +84,47 @@
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="visitorRecordList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="主键ID" align="center" prop="id" />
-      <el-table-column label="访客姓名" align="center" prop="visitorName" />
-      <el-table-column label="访客手机号" align="center" prop="mobile" />
-      <el-table-column label="证件号码" align="center" prop="idCardNo" />
-      <el-table-column label="到访类型" align="center" prop="visitType">
+    <el-table v-loading="loading" :data="visitorRecordList">
+      <el-table-column label="访客姓名" align="center" prop="visitorName" min-width="120" show-overflow-tooltip />
+      <el-table-column label="手机号" align="center" prop="mobile" width="140" />
+      <el-table-column label="身份证号" align="center" prop="idCardNo" min-width="180" show-overflow-tooltip />
+      <el-table-column label="访客照片" align="center" prop="visitorPhoto" width="100">
+        <template #default="scope">
+          <image-preview
+            v-if="scope.row.visitorPhoto"
+            :src="scope.row.visitorPhoto"
+            :width="44"
+            :height="44"
+          />
+          <el-tag v-else type="info">无照片</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="到访类型" align="center" prop="visitType" width="120">
         <template #default="scope">
-          <dict-tag :options="visitor_visit_type" :value="scope.row.visitType"/>
+          <dict-tag :options="visitor_visit_type" :value="String(scope.row.visitType)" />
         </template>
       </el-table-column>
-      <el-table-column label="访客来源,如公司、单位、亲友、外卖、快递、供应商等" align="center" prop="visitorSource" />
-      <el-table-column label="来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等" align="center" prop="visitReason" />
-      <el-table-column label="访客照片地址" align="center" prop="visitorPhoto" />
-      <el-table-column label="关联预约单号,现场登记可为空" align="center" prop="appointmentNo" />
-      <el-table-column label="被访人/被访对象" align="center" prop="visitedPerson" />
-      <el-table-column label="来访/登记时间" align="center" prop="visitTime" width="180">
+      <el-table-column label="登记方式" align="center" prop="registerType" width="140">
         <template #default="scope">
-          <span>{{ parseTime(scope.row.visitTime, '{y}-{m}-{d}') }}</span>
+          <dict-tag :options="visitor_register_type" :value="String(scope.row.registerType)" />
         </template>
       </el-table-column>
-      <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="登记方式" align="center" prop="registerType">
+      <el-table-column label="访客来源" align="center" prop="visitorSource" min-width="140" show-overflow-tooltip />
+      <el-table-column label="来访事由" align="center" prop="visitReason" min-width="160" show-overflow-tooltip />
+      <el-table-column label="预约单号" align="center" prop="appointmentNo" min-width="150" show-overflow-tooltip />
+      <el-table-column label="被访对象" align="center" prop="visitedPerson" min-width="120" show-overflow-tooltip />
+      <el-table-column label="来访时间" align="center" prop="visitTime" width="170">
         <template #default="scope">
-          <dict-tag :options="visitor_register_type" :value="scope.row.registerType"/>
+          <span>{{ parseTime(scope.row.visitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" width="100" fixed="right" class-name="small-padding fixed-width">
         <template #default="scope">
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['base:visitorRecord:edit']">修改</el-button>
-          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['base:visitorRecord:remove']">删除</el-button>
+          <el-button link type="primary" icon="View" @click="handleDetail(scope.row)">详情</el-button>
         </template>
       </el-table-column>
     </el-table>
-    
+
     <pagination
       v-show="total>0"
       :total="total"
@@ -184,95 +133,37 @@
       @pagination="getList"
     />
 
-    <!-- 添加或修改访客登记记录对话框 -->
-    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-      <el-form ref="visitorRecordRef" :model="form" :rules="rules" label-width="100px">
-        <el-row>
-          <el-col :span="24">
-            <el-form-item label="访客姓名" prop="visitorName">
-              <el-input v-model="form.visitorName" placeholder="请输入访客姓名" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="访客手机号" prop="mobile">
-              <el-input v-model="form.mobile" placeholder="请输入访客手机号" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="证件号码" prop="idCardNo">
-              <el-input v-model="form.idCardNo" placeholder="请输入证件号码" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="到访类型" prop="visitType">
-              <el-select v-model="form.visitType" placeholder="请选择到访类型">
-                <el-option
-                  v-for="dict in visitor_visit_type"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="访客来源,如公司、单位、亲友、外卖、快递、供应商等" prop="visitorSource">
-              <el-input v-model="form.visitorSource" placeholder="请输入访客来源,如公司、单位、亲友、外卖、快递、供应商等" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等" prop="visitReason">
-              <el-input v-model="form.visitReason" placeholder="请输入来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="访客照片地址" prop="visitorPhoto">
-              <el-input v-model="form.visitorPhoto" placeholder="请输入访客照片地址" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="关联预约单号,现场登记可为空" prop="appointmentNo">
-              <el-input v-model="form.appointmentNo" placeholder="请输入关联预约单号,现场登记可为空" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="被访人/被访对象" prop="visitedPerson">
-              <el-input v-model="form.visitedPerson" placeholder="请输入被访人/被访对象" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="来访/登记时间" prop="visitTime">
-              <el-date-picker clearable
-                v-model="form.visitTime"
-                type="date"
-                value-format="YYYY-MM-DD"
-                placeholder="请选择来访/登记时间">
-              </el-date-picker>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="登记方式" prop="registerType">
-              <el-select v-model="form.registerType" placeholder="请选择登记方式">
-                <el-option
-                  v-for="dict in visitor_register_type"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+    <!-- 访客记录详情 -->
+    <el-dialog :title="title" v-model="open" width="760px" append-to-body>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="访客姓名">{{ form.visitorName || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ form.mobile || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="身份证号">{{ form.idCardNo || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="到访类型">
+          <dict-tag :options="visitor_visit_type" :value="String(form.visitType)" />
+        </el-descriptions-item>
+        <el-descriptions-item label="登记方式">
+          <dict-tag :options="visitor_register_type" :value="String(form.registerType)" />
+        </el-descriptions-item>
+        <el-descriptions-item label="访客来源">{{ form.visitorSource || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="来访事由">{{ form.visitReason || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="预约单号">{{ form.appointmentNo || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="被访对象">{{ form.visitedPerson || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="来访时间">{{ parseTime(form.visitTime, "{y}-{m}-{d} {h}:{i}:{s}") || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ form.remark || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="访客照片" :span="2">
+          <image-preview
+            v-if="form.visitorPhoto"
+            :src="form.visitorPhoto"
+            :width="120"
+            :height="120"
+          />
+          <span v-else>-</span>
+        </el-descriptions-item>
+      </el-descriptions>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
+          <el-button @click="open = false">关 闭</el-button>
         </div>
       </template>
     </el-dialog>
@@ -280,7 +171,7 @@
 </template>
 
 <script setup name="VisitorRecord">
-import { listVisitorRecord, getVisitorRecord, delVisitorRecord, addVisitorRecord, updateVisitorRecord } from "@/api/base/visitorRecord"
+import { listVisitorRecord, getVisitorRecord } from "@/api/base/visitorRecord"
 
 const { proxy } = getCurrentInstance()
 const { visitor_register_type, visitor_visit_type } = useDict('visitor_register_type', 'visitor_visit_type')
@@ -289,11 +180,9 @@ const visitorRecordList = ref([])
 const open = ref(false)
 const loading = ref(true)
 const showSearch = ref(true)
-const ids = ref([])
-const single = ref(true)
-const multiple = ref(true)
 const total = ref(0)
 const title = ref("")
+const visitTimeRange = ref([])
 
 const data = reactive({
   form: {},
@@ -304,42 +193,25 @@ const data = reactive({
     mobile: undefined,
     idCardNo: undefined,
     visitType: undefined,
-    visitorSource: undefined,
-    visitReason: undefined,
-    visitorPhoto: undefined,
-    appointmentNo: undefined,
+    registerType: undefined,
     visitedPerson: undefined,
-    visitTime: undefined,
-    registerType: undefined
-  },
-  rules: {
-    visitType: [
-      { required: true, message: "到访类型不能为空", trigger: "change" }
-    ],
-    registerType: [
-      { required: true, message: "登记方式不能为空", trigger: "change" }
-    ]
+    visitTimeStart: undefined,
+    visitTimeEnd: undefined
   }
 })
 
-const { queryParams, form, rules } = toRefs(data)
+const { queryParams, form } = toRefs(data)
 
 /** 查询访客登记记录列表 */
 function getList() {
   loading.value = true
   listVisitorRecord(queryParams.value).then(response => {
-    visitorRecordList.value = response.rows
-    total.value = response.total
+    visitorRecordList.value = response.rows || []
+    total.value = response.total || 0
     loading.value = false
   })
 }
 
-/** 取消按钮 */
-function cancel() {
-  open.value = false
-  reset()
-}
-
 /** 表单重置 */
 function reset() {
   form.value = {
@@ -348,6 +220,7 @@ function reset() {
     mobile: null,
     idCardNo: null,
     visitType: null,
+    registerType: null,
     visitorSource: null,
     visitReason: null,
     visitorPhoto: null,
@@ -356,87 +229,63 @@ function reset() {
     visitTime: null,
     remark: null,
     createTime: null,
-    updateTime: null,
-    registerType: null
+    updateTime: null
   }
-  proxy.resetForm("visitorRecordRef")
 }
 
 /** 搜索按钮操作 */
 function handleQuery() {
   queryParams.value.pageNum = 1
+  if (visitTimeRange.value && visitTimeRange.value.length === 2) {
+    queryParams.value.visitTimeStart = visitTimeRange.value[0]
+    queryParams.value.visitTimeEnd = visitTimeRange.value[1]
+  } else {
+    queryParams.value.visitTimeStart = undefined
+    queryParams.value.visitTimeEnd = undefined
+  }
   getList()
 }
 
 /** 重置按钮操作 */
 function resetQuery() {
+  visitTimeRange.value = []
+  queryParams.value.visitTimeStart = undefined
+  queryParams.value.visitTimeEnd = undefined
   proxy.resetForm("queryRef")
   handleQuery()
 }
 
-/** 多选框选中数据 */
-function handleSelectionChange(selection) {
-  ids.value = selection.map(item => item.id)
-  single.value = selection.length != 1
-  multiple.value = !selection.length
-}
-
-/** 新增按钮操作 */
-function handleAdd() {
+/** 查看详情 */
+function handleDetail(row) {
   reset()
-  open.value = true
-  title.value = "添加访客登记记录"
-}
-
-/** 修改按钮操作 */
-function handleUpdate(row) {
-  reset()
-  const _id = row.id || ids.value
+  const _id = row.id
   getVisitorRecord(_id).then(response => {
-    form.value = response.data
-    open.value = true
-    title.value = "修改访客登记记录"
-  })
-}
-
-/** 提交按钮 */
-function submitForm() {
-  proxy.$refs["visitorRecordRef"].validate(valid => {
-    if (valid) {
-      if (form.value.id != null) {
-        updateVisitorRecord(form.value).then(() => {
-          proxy.$modal.msgSuccess("修改成功")
-          open.value = false
-          getList()
-        })
-      } else {
-        addVisitorRecord(form.value).then(() => {
-          proxy.$modal.msgSuccess("新增成功")
-          open.value = false
-          getList()
-        })
-      }
+    const _data = response.data || {}
+    form.value = {
+      ..._data,
+      visitType: _data.visitType != null ? String(_data.visitType) : null,
+      registerType: _data.registerType != null ? String(_data.registerType) : null
     }
+    open.value = true
+    title.value = "访客记录详情"
   })
 }
 
-/** 删除按钮操作 */
-function handleDelete(row) {
-  const _ids = row.id || ids.value
-  proxy.$modal.confirm('是否确认删除访客登记记录编号为"' + _ids + '"的数据项?').then(function() {
-    return delVisitorRecord(_ids)
-  }).then(() => {
-    getList()
-    proxy.$modal.msgSuccess("删除成功")
-  }).catch(() => {})
-}
-
 /** 导出按钮操作 */
 function handleExport() {
   proxy.download('base/visitorRecord/export', {
     ...queryParams.value
-  }, `visitorRecord_${new Date().getTime()}.xlsx`)
+  }, `访客记录_${new Date().getTime()}.xlsx`)
 }
 
 getList()
 </script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+:deep(.el-table .cell) {
+  line-height: 22px;
+}
+</style>

+ 48 - 18
迎宾巡逻安防机器人运维端Web管理系统详细设计开发文档_V2.1.html

@@ -106,7 +106,7 @@
         <tr><td>播报内容管理</td><td>RuoYi 生成后微调</td><td>适合</td><td>典型单表 CRUD,可基于 robot_ops_broadcast_content 生成,再补充测试播报按钮。</td></tr>
         <tr><td>播报任务管理</td><td>RuoYi 生成后定制</td><td>部分适合</td><td>基础 CRUD 可生成;时间段、频率、循环规则、复制任务等需要定制表单校验和交互。</td></tr>
         <tr><td>展示主题配置</td><td>RuoYi 生成后定制</td><td>部分适合</td><td>基础 CRUD 可生成;Logo/背景上传、颜色选择器、主题预览、设为启用需要定制。</td></tr>
-        <tr><td rowspan="3">访客管理</td><td>访客记录</td><td>RuoYi 生成后微调</td><td>适合</td><td>典型查询列表和详情页面,可基于 robot_ops_visitor_record 生成,导出和照片预览需轻微调整。</td></tr>
+        <tr><td rowspan="3">访客管理</td><td>访客记录</td><td>RuoYi 生成后定制</td><td>部分适合</td><td>可基于 robot_ops_visitor_record 生成只读列表和详情页面;需去掉新增、编辑、删除,补充到访类型、登记方式、访客来源、来访事由、照片预览、时间范围查询、详情弹窗和导出字段优化。列表中访客照片前置到身份证号后,便于快速识别访客身份。</td></tr>
         <tr><td>预约记录</td><td>RuoYi 生成后微调</td><td>适合</td><td>典型查询列表和详情页面,可基于 robot_ops_appointment_record 生成,数据来源为主控平台同步。</td></tr>
         <tr><td>白名单管理</td><td>RuoYi 生成后定制</td><td>部分适合</td><td>可基于 robot_ops_whitelist 生成基础 CRUD;需补充身份证号、人脸照片上传/预览、有效期、人员类型、启用/停用、导入导出,以及手机号/身份证号/人脸照片至少填写一种的表单校验。</td></tr>
         <tr><td rowspan="4">监控管理</td><td>视频预览</td><td>定制开发</td><td>否</td><td>实时视频播放、重连、全屏、状态提示依赖视频流接口和播放器组件,需要定制开发。</td></tr>
@@ -145,7 +145,24 @@
     <h4>6.3.7 展示主题配置页面</h4><table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody><tr><td>查询条件</td><td>主题名称、启用状态。</td></tr><tr><td>列表字段</td><td>主题名称、Logo、背景资源、主色、启用状态、当前启用标识、更新时间、操作。</td></tr><tr><td>编辑字段</td><td>主题名称(themeName)、Logo 地址(logoUrl)、背景类型(backgroundType)、背景资源地址(backgroundUrl)、主色(primaryColor)、辅助色(secondaryColor)、欢迎标题(welcomeTitle)、欢迎副标题(welcomeSubTitle)、启用状态(status)。</td></tr><tr><td>操作按钮</td><td>新增、编辑、删除、设为启用、预览。</td></tr><tr><td>业务规则</td><td>同一时刻仅允许一个主题处于“当前启用”状态。</td></tr></tbody></table>
 
     <h3>6.4 访客管理</h3>
-    <h4>6.4.1 访客记录页面</h4><table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody><tr><td>查询条件</td><td>访客姓名、手机号、证件号、登记方式、登记结果、来访时间范围。</td></tr><tr><td>列表字段</td><td>访客姓名、手机号、证件号、登记方式、被访对象、来访时间、登记结果、来源、操作。</td></tr><tr><td>详情字段</td><td>访客姓名(visitorName)、手机号(mobile)、证件号(idCardNo)、登记方式(registerType)、访客照片(visitorPhoto)、预约单号(appointmentNo)、被访对象(visitedPerson)、来访时间(visitTime)、登记结果(resultStatus)、来源类型(sourceType)、备注(remark)。</td></tr><tr><td>操作按钮</td><td>查看详情、导出。</td></tr><tr><td>导出字段</td><td>导出列表全部字段,不导出照片文件,仅导出照片链接。</td></tr></tbody></table>
+    <h4>6.4.1 访客记录页面</h4>
+<table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody>
+<tr><td>页面目标</td><td>展示已完成登记的访客到访记录。访客记录由机器人屏幕登记流程或扫码 H5 登记流程产生,运维端仅负责查看、详情和导出,不提供新增、编辑、删除。</td></tr>
+<tr><td>查询条件</td><td>访客姓名、手机号、身份证号、到访类型、登记方式、被访对象、来访时间范围。</td></tr>
+<tr><td>列表字段</td><td>访客姓名、手机号、身份证号、访客照片、到访类型、登记方式、访客来源、来访事由、预约单号、被访对象、来访时间、操作。</td></tr>
+<tr><td>详情字段</td><td>访客姓名(visitorName)、手机号(mobile)、身份证号(idCardNo)、到访类型(visitType)、登记方式(registerType)、访客来源(visitorSource)、来访事由(visitReason)、访客照片(visitorPhoto)、预约单号(appointmentNo)、被访对象(visitedPerson)、来访时间(visitTime)、备注(remark)。</td></tr>
+<tr><td>到访类型</td><td>用于区分访客到访业务类型,建议字典项为:APPOINTMENT=预约到访,WALK_IN=现场登记。</td></tr>
+<tr><td>登记方式</td><td>用于区分访客完成登记的入口,建议字典项为:SCREEN=机器人端,H5=手机端。机器人端指访客在机器人屏幕完成登记;手机端指访客扫码后在 H5 页面完成登记。身份证读卡、手机号输入、人脸拍照等可作为机器人端登记流程中的信息采集方式,不单独作为登记方式。</td></tr>
+<tr><td>访客来源</td><td>用于描述访客来自哪里或属于什么来源,字段为 visitorSource / visitor_source,适配公司、酒店、小区、园区、展厅等多场景,例如公司名称、合作方、亲友、外卖、快递、供应商、施工单位、旅行团等。该字段建议选填。</td></tr>
+<tr><td>来访事由</td><td>用于描述访客本次到访目的,字段为 visitReason / visit_reason,例如业务接洽、走亲访友、酒店入住、外卖配送、快递投递、设备维修、参观接待、会议拜访、施工进场等。该字段建议选填,一期采用文本输入,不强制枚举。</td></tr>
+<tr><td>访客照片</td><td>由机器人端摄像头或登记流程采集上传,运维端只做展示和预览,不支持手动上传修改。列表中访客照片放在身份证号之后,便于运维人员优先查看访客身份信息;详情中可展示较大尺寸照片预览。</td></tr>
+<tr><td>预约关联</td><td>预约到访记录通过 appointmentNo 关联预约记录;现场登记记录 appointmentNo 可为空。</td></tr>
+<tr><td>操作按钮</td><td>查看详情、导出。</td></tr>
+<tr><td>业务规则</td><td>访客记录代表已经完成登记的到访记录,不设置登记结果字段。登记失败、身份证读取失败、扫码失败、预约匹配失败等过程异常,不进入访客记录,应进入日志中心或后续扩展的登记异常日志。</td></tr>
+<tr><td>与白名单关系</td><td>访客记录与白名单识别记录分开管理。命中白名单不作为访客登记结果;白名单命中属于识别或通行逻辑,可后续在识别日志、通行记录或对话/安防日志中体现。</td></tr>
+<tr><td>与预约记录关系</td><td>预约记录由主控平台同步,表示计划来访;访客到现场后通过机器人屏幕或扫码 H5 完成登记确认,并生成访客记录。</td></tr>
+<tr><td>导出字段</td><td>导出列表主要字段;访客照片建议导出"有照片/无照片"或照片链接,不直接导出图片文件。</td></tr>
+</tbody></table>
     <h4>6.4.2 预约记录页面</h4><table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody><tr><td>查询条件</td><td>预约单号、访客姓名、手机号、预约状态、预约时间范围。</td></tr><tr><td>列表字段</td><td>预约单号、访客姓名、手机号、被访人、预约时间、状态、同步时间、操作。</td></tr><tr><td>详情字段</td><td>预约单号(appointmentNo)、访客姓名(visitorName)、手机号(mobile)、被访人(visitedPerson)、预约时间(appointmentTime)、预约状态(status)、同步时间(syncTime)、来源平台(sourcePlatform)、备注(remark)。</td></tr><tr><td>操作按钮</td><td>查看详情。</td></tr><tr><td>数据来源</td><td>主控平台同步;本地端仅展示,不发起预约流程。</td></tr></tbody></table>
     <h4>6.4.3 白名单管理页面</h4>
 <table><thead><tr><th>模块</th><th>详细设计</th></tr></thead><tbody>
@@ -383,9 +400,9 @@
 
     <h3>7.5 访客管理接口</h3>
     <table><thead><tr><th>接口</th><th>方法</th><th>说明</th><th>请求参数</th><th>返回/处理字段</th><th>数据库表</th></tr></thead><tbody>
-      <tr><td>/robot-ops/visitor/record/page</td><td>GET</td><td>访客记录分页</td><td>visitorName、mobile、idCardNo、registerType、resultStatus、visitTimeStart、visitTimeEnd、pageNum、pageSize</td><td>id、visitorName、mobile、idCardNo、registerType、visitedPerson、visitTime、resultStatus、sourceType</td><td>robot_ops_visitor_record</td></tr>
-      <tr><td>/robot-ops/visitor/record/{id}</td><td>GET</td><td>访客详情</td><td>记录ID(id)</td><td>visitorName、mobile、idCardNo、registerType、visitorPhoto、appointmentNo、visitedPerson、visitTime、resultStatus、sourceType、remark</td><td>robot_ops_visitor_record</td></tr>
-      <tr><td>/robot-ops/visitor/record/export</td><td>GET</td><td>导出访客记录</td><td>同分页查询条件</td><td>Excel文件</td><td>robot_ops_visitor_record</td></tr>
+      <tr><td>/robot-ops/visitor/record/page</td><td>GET</td><td>访客记录分页</td><td>visitorName、mobile、idCardNo、visitType、registerType、visitedPerson、visitTimeStart、visitTimeEnd、pageNum、pageSize</td><td>id、visitorName、mobile、idCardNo、visitorPhoto、visitType、registerType、visitorSource、visitReason、appointmentNo、visitedPerson、visitTime</td><td>robot_ops_visitor_record</td></tr>
+      <tr><td>/robot-ops/visitor/record/{id}</td><td>GET</td><td>访客详情</td><td>记录ID(id)</td><td>visitorName、mobile、idCardNo、visitType、registerType、visitorSource、visitReason、visitorPhoto、appointmentNo、visitedPerson、visitTime、remark</td><td>robot_ops_visitor_record</td></tr>
+      <tr><td>/robot-ops/visitor/record/export</td><td>GET</td><td>导出访客记录</td><td>同分页查询条件:visitorName、mobile、idCardNo、visitType、registerType、visitedPerson、visitTimeStart、visitTimeEnd</td><td>Excel文件;导出字段建议包括访客姓名、手机号、身份证号、到访类型、登记方式、访客来源、来访事由、预约单号、被访对象、来访时间、访客照片状态</td><td>robot_ops_visitor_record</td></tr>
       <tr><td>/robot-ops/visitor/appointment/page</td><td>GET</td><td>预约记录分页</td><td>appointmentNo、visitorName、mobile、status、appointmentTimeStart、appointmentTimeEnd、pageNum、pageSize</td><td>appointmentNo、visitorName、mobile、visitedPerson、appointmentTime、status、syncTime</td><td>robot_ops_appointment_record</td></tr>
       <tr><td>/robot-ops/visitor/appointment/{id}</td><td>GET</td><td>预约详情</td><td>预约记录ID(id)</td><td>appointmentNo、visitorName、mobile、visitedPerson、appointmentTime、status、syncTime、sourcePlatform、remark</td><td>robot_ops_appointment_record</td></tr>
       <tr><td>/robot-ops/visitor/whitelist/page</td><td>GET</td><td>白名单分页</td><td>姓名(name)、手机号(mobile)、身份证号(idCardNo)、人员类型(whitelistType)、来源类型(sourceType)、启用状态(status)、pageNum、pageSize</td><td>白名单ID(id)、姓名(name)、手机号(mobile)、身份证号(idCardNo)、人员类型(whitelistType)、人脸照片地址(faceImageUrl)、是否有人脸照片(hasFaceImage)、来源类型(sourceType)、有效开始时间(validStartTime)、有效结束时间(validEndTime)、启用状态(status)、更新时间(updateTime)</td><td>robot_ops_whitelist</td></tr>
@@ -397,6 +414,9 @@
       <tr><td>/robot-ops/visitor/whitelist/import</td><td>POST</td><td>导入白名单</td><td>Excel文件(file);导入字段包括姓名、人员类型、手机号、身份证号、人脸照片地址、有效开始时间、有效结束时间、启用状态、备注;运维后台导入的数据 sourceType 默认写入 1,表示本地录入</td><td>导入总数、成功数、失败数、失败明细;导入时需校验 mobile、idCardNo、faceImageUrl 三者至少填写一种;手机号和身份证号需校验格式</td><td>robot_ops_whitelist</td></tr>
       <tr><td>/robot-ops/visitor/whitelist/export</td><td>GET</td><td>导出白名单</td><td>同分页查询条件</td><td>Excel文件;导出字段包括姓名、手机号、身份证号、人员类型、人脸照片地址、来源类型、有效开始时间、有效结束时间、启用状态、更新时间、备注</td><td>robot_ops_whitelist</td></tr>
     </tbody></table>
+    <div class="note">访客记录不设置 resultStatus 和 sourceType 作为页面字段。访客记录代表已完成登记的到访记录;登记失败、扫码失败、读卡失败、预约匹配失败等异常应进入日志中心或后续扩展的登记异常日志。</div>
+    <div class="note">访客记录支持两类到访类型:APPOINTMENT=预约到访,WALK_IN=现场登记;登记方式支持 SCREEN=机器人端,H5=手机端。机器人端指访客在机器人屏幕完成登记;手机端指访客扫码后在 H5 页面完成登记。</div>
+    <div class="note">预约记录由主控平台同步;预约到访访客在现场完成登记后生成访客记录,并通过 appointmentNo 关联预约记录。现场登记访客可不关联预约单号。</div>
     <div class="note">白名单中的 whitelistType 字段在页面上显示为"人员类型",用于表示人员身份,如内部人员、访客、VIP、其他。人脸识别不作为人员类型,而是白名单匹配方式之一。</div>
     <div class="note">白名单不单独设置识别方式字段。机器人侧可根据当前采集到的身份信息匹配白名单:人脸识别时通过 faceImageUrl 对应的人脸照片进行比对;刷身份证或输入身份证时匹配 idCardNo;输入手机号时匹配 mobile。任一方式匹配到启用且在有效期内的白名单人员,即视为白名单命中。</div>
     <div class="note">新增或编辑白名单时,mobile、idCardNo、faceImageUrl 三者至少填写一种;手机号填写时需符合大陆手机号格式,身份证号填写时需符合 18 位身份证基础格式;一期不保存人脸特征ID。</div>
@@ -661,23 +681,33 @@
     <h4>8.3.1 访客记录表 robot_ops_visitor_record</h4>
     <div class="code">CREATE TABLE `robot_ops_visitor_record` (
   `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-  `visitor_name` VARCHAR(100) DEFAULT NULL COMMENT '访客姓名',
-  `mobile` VARCHAR(20) DEFAULT NULL COMMENT '访客手机号',
-  `id_card_no` VARCHAR(50) DEFAULT NULL COMMENT '证件号码',
-  `register_type` VARCHAR(50) DEFAULT NULL COMMENT '登记方式:读卡器、人工、本地同步等',
-  `visitor_photo` VARCHAR(255) DEFAULT NULL COMMENT '访客照片地址',
-  `appointment_no` VARCHAR(50) DEFAULT NULL COMMENT '关联预约单号',
-  `visited_person` VARCHAR(100) DEFAULT NULL COMMENT '被访人/被访对象',
-  `visit_time` DATETIME DEFAULT NULL COMMENT '来访时间/登记时间',
-  `result_status` VARCHAR(20) DEFAULT NULL COMMENT '登记结果:SUCCESS成功,FAIL失败,WHITELIST命中白名单',
-  `source_type` VARCHAR(20) DEFAULT NULL COMMENT '来源类型:LOCAL本地,PLATFORM平台同步',
+  `visitor_name` VARCHAR(100) NOT NULL COMMENT '访客姓名',
+  `mobile` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
+  `id_card_no` VARCHAR(50) DEFAULT NULL COMMENT '身份证号',
+  `visit_type` VARCHAR(50) NOT NULL COMMENT '到访类型:APPOINTMENT预约到访,WALK_IN现场登记',
+  `register_type` VARCHAR(50) NOT NULL COMMENT '登记方式:SCREEN机器人端,H5手机端',
+  `visitor_source` VARCHAR(100) DEFAULT NULL COMMENT '访客来源,如公司、单位、亲友、外卖、快递、供应商等',
+  `visit_reason` VARCHAR(200) DEFAULT NULL COMMENT '来访事由,如业务接洽、走亲访友、酒店入住、配送、维修、参观等',
+  `visitor_photo` VARCHAR(255) DEFAULT NULL COMMENT '访客照片地址,由机器人端采集上传',
+  `appointment_no` VARCHAR(100) DEFAULT NULL COMMENT '关联预约单号,现场登记可为空',
+  `visited_person` VARCHAR(100) DEFAULT NULL COMMENT '被访对象,可为被访人、房号、部门、接待单位等',
+  `visit_time` DATETIME NOT NULL COMMENT '来访/登记时间',
   `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
   `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   PRIMARY KEY (`id`),
-  KEY `idx_robot_ops_visitor_record_visit_time` (`visit_time`),
+  KEY `idx_robot_ops_visitor_record_name` (`visitor_name`),
   KEY `idx_robot_ops_visitor_record_mobile` (`mobile`),
-  KEY `idx_robot_ops_visitor_record_appointment_no` (`appointment_no`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='访客登记记录表';</div>
+  KEY `idx_robot_ops_visitor_record_id_card_no` (`id_card_no`),
+  KEY `idx_robot_ops_visitor_record_visit_type` (`visit_type`),
+  KEY `idx_robot_ops_visitor_record_register_type` (`register_type`),
+  KEY `idx_robot_ops_visitor_record_appointment_no` (`appointment_no`),
+  KEY `idx_robot_ops_visitor_record_visit_time` (`visit_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='访客记录表';</div>
+    <div class="note">说明:访客记录表只保存已完成登记的到访记录,不保存登记失败结果。登记失败、扫码失败、身份证读取失败、预约匹配失败等异常,进入日志中心或后续扩展的登记异常日志。</div>
+    <div class="note">说明:visit_type 建议使用 RuoYi 字典 visitor_visit_type,字典项为 APPOINTMENT=预约到访,WALK_IN=现场登记。</div>
+    <div class="note">说明:register_type 建议使用 RuoYi 字典 visitor_register_type,字典项为 SCREEN=机器人端,H5=手机端。机器人端指访客在机器人屏幕完成登记;手机端指访客扫码后在 H5 页面完成登记。</div>
+    <div class="note">说明:visitor_source 和 visit_reason 为通用文本字段,用于适配公司、酒店、小区、园区、展厅等多种场景,不强制做枚举。</div>
 
     <h4>8.3.2 预约记录表 robot_ops_appointment_record</h4>
     <div class="code">CREATE TABLE `robot_ops_appointment_record` (