<template>
|
<main class="config-page">
|
<section class="config-card">
|
<aside class="config-nav" aria-label="信息配置菜单">
|
<h1>信息配置</h1>
|
<button
|
v-for="tab in tabs"
|
:key="tab.value"
|
class="config-nav-item"
|
:class="{ active: activeTab === tab.value }"
|
type="button"
|
@click="activeTab = tab.value"
|
>
|
<svg-icon class-name="config-nav-icon" :icon-class="tab.icon" />
|
<span>{{ tab.label }}</span>
|
</button>
|
</aside>
|
|
<form class="config-workspace" @submit.prevent="submitForm">
|
<section v-show="activeTab === 'register'">
|
<div class="config-section">
|
<h2>登记模式设置</h2>
|
<label class="config-switch-row">
|
<svg-icon class-name="row-icon" icon-class="config-onsite-register" />
|
<span class="row-label">现场登记</span>
|
<input v-model="form.onsiteRegistrationEnabled" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
<label class="config-switch-row">
|
<svg-icon class-name="row-icon" icon-class="config-appointment-register" />
|
<span class="row-label">预约登记</span>
|
<input v-model="form.appointmentRegistrationEnabled" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
<div v-if="form.appointmentRegistrationEnabled" class="appointment-time-row">
|
<svg-icon class-name="row-icon" icon-class="config-appointment-time" />
|
<span class="row-label">预约登记默认访问时间</span>
|
<span class="time-day">当天</span>
|
<el-time-picker
|
v-model="form.appointmentDefaultVisitTime"
|
class="appointment-time-input"
|
value-format="HH:mm"
|
format="HH:mm"
|
placeholder="请选择时间"
|
:clearable="false"
|
/>
|
</div>
|
</div>
|
|
<div class="config-section">
|
<h2>接待人验证开关</h2>
|
<p>设置登记、签离时是否需要接待人确认。若关闭,访客认证后直接打印凭条/签离时自动回收门禁权限。</p>
|
<label v-for="item in receiverSwitches" :key="item.prop" class="config-switch-row">
|
<svg-icon class-name="row-icon" :icon-class="item.icon" />
|
<span class="row-label">{{ item.label }}</span>
|
<input v-model="form[item.prop]" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
</div>
|
|
<div class="config-section">
|
<h2>人脸验证方式</h2>
|
<p>人像对比即身份证和人脸对比确保为同一人(手动录入身份证除外),人脸采集则采集访客人脸照片。</p>
|
<label v-for="item in faceSwitches" :key="item.prop" class="config-switch-row">
|
<svg-icon class-name="row-icon" :icon-class="item.icon" />
|
<span class="row-label">{{ item.label }}</span>
|
<input v-model="form[item.prop]" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
</div>
|
|
<div class="config-section">
|
<h2>身份证手动输入</h2>
|
<p>关闭后仅可刷身份证登记/签离。强制人脸比对时,开启则支持手动输入身份证并做人脸采集。</p>
|
<label class="config-switch-row">
|
<svg-icon class-name="row-icon" icon-class="config-id-card" />
|
<span class="row-label">允许手动输入身份证</span>
|
<input v-model="form.manualIdCardEnabled" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
</div>
|
</section>
|
|
<section v-show="activeTab === 'print'">
|
<div class="print-layout">
|
<div class="print-settings">
|
<div class="config-section">
|
<h2>凭条样式配置</h2>
|
<p>勾选需要在凭条上打印的字段,支持拖拽排序(至少勾选1个字段)</p>
|
<draggable v-model="printFieldOptions" class="print-field-list" ghost-class="print-field-ghost" chosen-class="print-field-chosen" :delay="120" :animation="160" @end="syncPrintFieldsOrder">
|
<label v-for="item in printFieldOptions" :key="item.fieldCode" class="print-field-item">
|
<input v-model="form.printFields" type="checkbox" :value="item.fieldCode">
|
<span>{{ item.fieldName }}</span>
|
</label>
|
</draggable>
|
<button class="sort-hint" type="button">
|
<i class="el-icon-s-opportunity sort-hint-icon" aria-hidden="true" />
|
<span>长按选项拖拽排序</span>
|
</button>
|
</div>
|
</div>
|
|
<aside class="print-preview">
|
<div class="preview-title">
|
<span>效果预览</span>
|
<button type="button" title="刷新预览" aria-label="刷新预览" @click="refreshPreview">
|
<i class="el-icon-refresh" aria-hidden="true" />
|
</button>
|
</div>
|
<div class="preview-stage">
|
<section class="ticket-preview" aria-label="登记凭证预览">
|
<h3>登记凭证</h3>
|
<div class="ticket-rows">
|
<div v-for="row in ticketRows" :key="row.label" class="ticket-row">
|
<strong>{{ row.label }}</strong>
|
<span>{{ row.value }}</span>
|
</div>
|
</div>
|
</section>
|
</div>
|
</aside>
|
</div>
|
</section>
|
|
<section v-show="activeTab === 'system'">
|
<div class="system-grid">
|
<div class="config-section system-basic">
|
<h2>系统信息配置</h2>
|
<div class="field-grid">
|
<label class="input-field">
|
<span>系统名称</span>
|
<input v-model="form.systemName" type="text" placeholder="请输入系统名称">
|
</label>
|
<div class="input-field">
|
<span>Logo</span>
|
<label class="logo-upload">
|
<input v-hasPermi="['visitor:config:edit']" type="file" accept="image/*" :disabled="uploadingLogo" @change="handleLogoChange">
|
<span class="logo-upload-button">选择文件</span>
|
<span class="logo-file-name">{{ uploadingLogo ? '上传中' : logoFileName }}</span>
|
</label>
|
</div>
|
</div>
|
</div>
|
|
<div class="config-section system-info">
|
<h2>信息配置</h2>
|
<div class="field-grid">
|
<label class="input-field">
|
<span>服务厂家</span>
|
<input v-model="form.serviceProvider" type="text" placeholder="请输入服务厂家">
|
</label>
|
<label class="input-field">
|
<span>技术支持电话</span>
|
<input v-model="form.supportPhone" type="text" placeholder="请输入技术支持电话">
|
</label>
|
</div>
|
</div>
|
|
<div class="config-section system-entry">
|
<h2>入口配置</h2>
|
<p>关闭后入口不显示,但是仍可以点击该区域进入。</p>
|
<label class="config-switch-row">
|
<svg-icon class-name="row-icon" icon-class="config-device" />
|
<span class="row-label">访客机管理侧入口</span>
|
<input v-model="form.visitorMachineEntryEnabled" type="checkbox">
|
<span class="switch-control" />
|
</label>
|
<label class="input-field center-path-field">
|
<span>中台地址</span>
|
<input v-model="form.centerPath" type="text" placeholder="请输入中台地址">
|
</label>
|
</div>
|
</div>
|
</section>
|
|
<footer class="config-actions">
|
<button v-hasPermi="['visitor:config:edit']" class="config-save-button" type="submit" :disabled="saving">{{ saving ? '保存中' : '保存' }}</button>
|
</footer>
|
</form>
|
</section>
|
</main>
|
</template>
|
|
<script>
|
import draggable from 'vuedraggable'
|
import { getVisitorConfig, saveVisitorConfig, uploadVisitorLogo } from '@/api/visitor-admin/config'
|
import { getResponseData, normalizeBoolean } from '@/utils/visitor-admin'
|
import { setVisitorBrand } from '@/utils/visitor-brand'
|
|
const defaultForm = {
|
onsiteRegistrationEnabled: true,
|
appointmentRegistrationEnabled: true,
|
appointmentDefaultVisitTime: '',
|
registerReceiverConfirmEnabled: false,
|
signReceiverConfirmEnabled: false,
|
faceCompareEnabled: false,
|
faceCaptureEnabled: false,
|
manualIdCardEnabled: true,
|
visitorMachineEntryEnabled: true,
|
printFields: ['visitorName', 'idCard', 'intervieweeName', 'visitAreaNames', 'validTime'],
|
systemName: '',
|
logoUrl: '',
|
serviceProvider: '',
|
supportPhone: '',
|
centerPath: ''
|
}
|
|
export default {
|
name: 'VisitorSystemConfig',
|
components: { draggable },
|
// 目的:初始化系统配置表单、配置页签、开关项和打印字段选项。
|
data() {
|
return {
|
saving: false,
|
uploadingLogo: false,
|
activeTab: 'register',
|
logoFileName: '未选择任何文件',
|
form: { ...defaultForm },
|
tabs: [
|
{ value: 'register', label: '登记配置', icon: 'config-register' },
|
{ value: 'print', label: '打印凭条配置', icon: 'config-print' },
|
{ value: 'system', label: '系统信息', icon: 'config-system' }
|
],
|
receiverSwitches: [
|
{ prop: 'registerReceiverConfirmEnabled', label: '登记需接待人确认', icon: 'config-receiver-confirm' },
|
{ prop: 'signReceiverConfirmEnabled', label: '签离需接待人确认', icon: 'config-sign-confirm' }
|
],
|
faceSwitches: [
|
{ prop: 'faceCompareEnabled', label: '人像对比', icon: 'config-face-compare' },
|
{ prop: 'faceCaptureEnabled', label: '人脸采集', icon: 'config-face-capture' }
|
],
|
printFieldOptions: [
|
{ fieldCode: 'visitorName', fieldName: '访客姓名' },
|
{ fieldCode: 'idCard', fieldName: '证件号码' },
|
{ fieldCode: 'intervieweeName', fieldName: '被访人' },
|
{ fieldCode: 'visitAreaNames', fieldName: '访问区域' },
|
{ fieldCode: 'validTime', fieldName: '有效期' },
|
{ fieldCode: 'plateNo', fieldName: '车牌号' },
|
{ fieldCode: 'visitReason', fieldName: '来访事由' },
|
{ fieldCode: 'receiverName', fieldName: '接待人' },
|
{ fieldCode: 'registerTime', fieldName: '登记时间' }
|
]
|
}
|
},
|
computed: {
|
// 目的:根据勾选的打印字段生成右侧访客凭条预览行。
|
ticketRows() {
|
const sample = {
|
visitorName: '张三',
|
idCard: '320***********001',
|
intervieweeName: '李四',
|
visitAreaNames: 'A区 / 办公楼',
|
validTime: '2026-07-07 09:00~18:00',
|
plateNo: '苏A12345',
|
visitReason: '商务拜访',
|
receiverName: '王五',
|
registerTime: '2026-07-23 09:00'
|
}
|
return this.printFieldOptions
|
.filter(item => this.form.printFields.indexOf(item.fieldCode) !== -1)
|
.map(item => ({ label: item.fieldName, value: sample[item.fieldCode] || '-' }))
|
}
|
},
|
// 目的:页面创建时加载当前访客系统配置。
|
created() {
|
this.getDetail()
|
},
|
methods: {
|
// 目的:把后端布尔兼容值转换为开关组件使用的布尔值。
|
boolValue(value, fallback) {
|
return value === undefined || value === null ? fallback : normalizeBoolean(value)
|
},
|
// 目的:加载系统配置详情,并用默认配置兜底缺失字段。
|
getDetail() {
|
getVisitorConfig().then(res => {
|
const data = getResponseData(res) || {}
|
const printFields = Array.isArray(data.printFields) && data.printFields.length ? data.printFields : defaultForm.printFields
|
this.form = {
|
...defaultForm,
|
...data,
|
centerPath: data.centerPath || '',
|
printFields,
|
onsiteRegistrationEnabled: this.boolValue(data.onsiteRegistrationEnabled, defaultForm.onsiteRegistrationEnabled),
|
appointmentRegistrationEnabled: this.boolValue(data.appointmentRegistrationEnabled, defaultForm.appointmentRegistrationEnabled),
|
registerReceiverConfirmEnabled: this.boolValue(data.registerReceiverConfirmEnabled, defaultForm.registerReceiverConfirmEnabled),
|
signReceiverConfirmEnabled: this.boolValue(data.signReceiverConfirmEnabled, defaultForm.signReceiverConfirmEnabled),
|
faceCompareEnabled: this.boolValue(data.faceCompareEnabled, defaultForm.faceCompareEnabled),
|
faceCaptureEnabled: this.boolValue(data.faceCaptureEnabled, defaultForm.faceCaptureEnabled),
|
manualIdCardEnabled: this.boolValue(data.manualIdCardEnabled, defaultForm.manualIdCardEnabled),
|
visitorMachineEntryEnabled: this.boolValue(data.visitorMachineEntryEnabled, defaultForm.visitorMachineEntryEnabled)
|
}
|
if (data.printFieldOptions && data.printFieldOptions.length) {
|
this.printFieldOptions = this.applyPrintFieldOrder(printFields, data.printFieldOptions)
|
} else {
|
this.printFieldOptions = this.applyPrintFieldOrder(printFields, this.printFieldOptions)
|
}
|
this.syncPrintFieldsOrder()
|
this.syncBrand(this.form)
|
})
|
},
|
// 目的:按已保存的打印字段顺序重排字段选项,保证页面初始顺序和实际打印顺序一致。
|
applyPrintFieldOrder(printFields, options) {
|
const optionMap = new Map((options || []).map(item => [item.fieldCode, item]))
|
const ordered = (printFields || []).map(fieldCode => optionMap.get(fieldCode)).filter(Boolean)
|
const remaining = (options || []).filter(item => (printFields || []).indexOf(item.fieldCode) === -1)
|
return ordered.concat(remaining)
|
},
|
// 目的:把当前拖拽后的字段顺序同步到表单,保存时后端按该数组顺序打印。
|
syncPrintFieldsOrder() {
|
this.form.printFields = this.printFieldOptions
|
.filter(item => this.form.printFields.indexOf(item.fieldCode) !== -1)
|
.map(item => item.fieldCode)
|
},
|
// 目的:校验并上传 Logo 图片,成功后同步系统品牌预览。
|
handleLogoChange(event) {
|
const file = event.target.files && event.target.files[0]
|
this.logoFileName = file ? file.name : '未选择任何文件'
|
if (!file) {
|
return
|
}
|
if (!file.type || file.type.indexOf('image/') !== 0) {
|
event.target.value = ''
|
this.logoFileName = '未选择任何文件'
|
this.$modal.msgError('请选择图片文件')
|
return
|
}
|
const formData = new FormData()
|
formData.append('file', file)
|
this.uploadingLogo = true
|
uploadVisitorLogo(formData).then(res => {
|
const data = getResponseData(res) || {}
|
const logoUrl = data.fileName || data.url
|
if (!logoUrl) {
|
this.$modal.msgError('Logo上传失败,未返回文件地址')
|
return
|
}
|
this.form.logoUrl = logoUrl
|
this.syncBrand(this.form)
|
this.$modal.msgSuccess('Logo上传成功')
|
}).finally(() => {
|
this.uploadingLogo = false
|
event.target.value = ''
|
})
|
},
|
// 目的:提示当前配置预览已刷新,保留显式刷新入口。
|
refreshPreview() {
|
this.$modal.msgSuccess('预览已刷新')
|
},
|
// 目的:校验并保存系统配置,同时同步页面品牌信息。
|
submitForm() {
|
if (!this.form.printFields.length) {
|
this.$modal.msgError('至少选择一个打印字段')
|
this.activeTab = 'print'
|
return
|
}
|
this.saving = true
|
const payload = {
|
...this.form,
|
printFields: this.printFieldOptions
|
.filter(item => this.form.printFields.indexOf(item.fieldCode) !== -1)
|
.map(item => item.fieldCode)
|
}
|
saveVisitorConfig(payload).then(res => {
|
const data = getResponseData(res) || this.form
|
this.form = { ...this.form, ...data }
|
this.syncBrand(this.form)
|
this.$modal.msgSuccess('保存成功')
|
}).finally(() => {
|
this.saving = false
|
})
|
},
|
// 目的:把系统名称和 Logo 写入全局访客后台品牌缓存。
|
syncBrand(config) {
|
setVisitorBrand({
|
systemName: config.systemName,
|
logoUrl: config.logoUrl
|
})
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.center-path-field {
|
grid-template-columns: auto minmax(0, 1fr);
|
align-items: center;
|
max-width: 520px;
|
margin: 20px 0 0 12px;
|
}
|
</style>
|