feat(settings): implement system settings
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
copy: []
|
||||
rotateKey: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="setting-card">
|
||||
<div class="card-heading">
|
||||
<h2>第三方系统嵌入(免登录接口页)</h2>
|
||||
</div>
|
||||
|
||||
<p class="description">
|
||||
签署工作台可作为 iframe 嵌入 HIS / LIS / 体检等第三方系统,URL 携带签名参数免登录直达:
|
||||
</p>
|
||||
<div class="code-block embed-url">
|
||||
https://medisign.hospital.cn<strong>/embed/workbench</strong>?uid=<strong>{操作员ID}</strong>&pid=<strong>{患者ID}</strong>&visitNo=<strong>{就诊号}</strong>&ts=<strong>{时间戳}</strong>&sign=<strong
|
||||
>{HMAC签名}</strong
|
||||
>
|
||||
</div>
|
||||
<p class="description rule-description">
|
||||
生成规则:HMAC-SHA256(密钥, uid+pid+visitNo+ts),有效期 ≤ 5 分钟,防篡改防重放。
|
||||
</p>
|
||||
<div class="button-row">
|
||||
<button type="button" class="outline-button" @click="emit('copy')">复制嵌入 URL</button>
|
||||
<button type="button" class="outline-button" @click="emit('rotateKey')">密钥轮换</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-card {
|
||||
min-width: 0;
|
||||
padding: 16px 18px;
|
||||
background: var(--card);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
}
|
||||
|
||||
.card-heading {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 10px;
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
padding: 10px 12px;
|
||||
color: var(--ink);
|
||||
font-family: Consolas, 'Microsoft YaHei', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
word-break: break-all;
|
||||
background: #f4f8fa;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.code-block strong {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.outline-button {
|
||||
padding: 4px 10px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.outline-button:hover {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import type { SettingToggleKey, SignatureSettings } from '../types'
|
||||
|
||||
defineProps<{
|
||||
settings: SignatureSettings
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:linkExpireHours': [value: number]
|
||||
toggle: [key: SettingToggleKey]
|
||||
}>()
|
||||
|
||||
function handleHoursInput(event: Event) {
|
||||
emit('update:linkExpireHours', Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="setting-card">
|
||||
<div class="card-heading">
|
||||
<h2>签署参数</h2>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label">线上链接有效期</span>
|
||||
<input
|
||||
:value="settings.linkExpireHours"
|
||||
class="hours-input"
|
||||
type="number"
|
||||
min="1"
|
||||
aria-label="线上链接有效期"
|
||||
@input="handleHoursInput"
|
||||
/>
|
||||
<span class="setting-hint">小时</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label">链接超时自动提醒</span>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button"
|
||||
:class="{ active: settings.timeoutReminder }"
|
||||
role="switch"
|
||||
:aria-checked="settings.timeoutReminder"
|
||||
aria-label="链接超时自动提醒"
|
||||
@click="emit('toggle', 'timeoutReminder')"
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label">签名原图双留存</span>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button"
|
||||
:class="{ active: settings.dualRetention }"
|
||||
role="switch"
|
||||
:aria-checked="settings.dualRetention"
|
||||
aria-label="签名原图双留存"
|
||||
@click="emit('toggle', 'dualRetention')"
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
</button>
|
||||
<span class="setting-hint">PDF 原件 + 签名图片</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label">预留 CA 数字证书</span>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-button"
|
||||
:class="{ active: settings.caCertificate }"
|
||||
role="switch"
|
||||
:aria-checked="settings.caCertificate"
|
||||
aria-label="预留 CA 数字证书"
|
||||
@click="emit('toggle', 'caCertificate')"
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
</button>
|
||||
<span class="setting-hint">扩展位,本期未启用</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-card {
|
||||
min-width: 0;
|
||||
padding: 16px 18px;
|
||||
background: var(--card);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
}
|
||||
|
||||
.card-heading {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 34px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.setting-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
flex: 0 0 150px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hours-input {
|
||||
width: 80px;
|
||||
padding: 7px 10px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.hours-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.setting-hint {
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
background: #cbd8de;
|
||||
border-radius: 20px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-button::after {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: '';
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: left 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-button.active {
|
||||
background: var(--ok);
|
||||
}
|
||||
|
||||
.toggle-button.active::after {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
.toggle-button:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.setting-row {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
import type { SmsChannel, SmsChannelOption } from '../types'
|
||||
|
||||
defineProps<{
|
||||
channel: SmsChannel
|
||||
channelOptions: SmsChannelOption[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:channel': [value: SmsChannel]
|
||||
test: []
|
||||
}>()
|
||||
|
||||
function handleChannelChange(event: Event) {
|
||||
emit('update:channel', (event.target as HTMLSelectElement).value as SmsChannel)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="setting-card">
|
||||
<div class="card-heading">
|
||||
<h2>短信模板</h2>
|
||||
</div>
|
||||
|
||||
<div class="code-block sms-template">
|
||||
【xx医院】尊敬的<strong>{患者姓名}</strong>,您有一份《<strong>{文档名称}</strong>》待签署,请点击
|
||||
<strong>{签署链接}</strong> 完成签署,链接 {有效期} 小时内有效。
|
||||
</div>
|
||||
|
||||
<hr class="divider" />
|
||||
|
||||
<label class="channel-row">
|
||||
<span>短信通道</span>
|
||||
<select :value="channel" aria-label="选择短信通道" @change="handleChannelChange">
|
||||
<option v-for="option in channelOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<button type="button" class="outline-button" @click="emit('test')">发送测试短信</button>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-card {
|
||||
min-width: 0;
|
||||
padding: 16px 18px;
|
||||
background: var(--card);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
}
|
||||
|
||||
.card-heading {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
padding: 10px 12px;
|
||||
color: var(--ink);
|
||||
font-family: Consolas, 'Microsoft YaHei', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
word-break: break-all;
|
||||
background: #f4f8fa;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.code-block strong {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 12px 0;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.channel-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.channel-row > span {
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.channel-row select {
|
||||
width: 200px;
|
||||
padding: 7px 10px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.channel-row select:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.outline-button {
|
||||
padding: 4px 10px;
|
||||
margin-top: 14px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.outline-button:hover {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.channel-row {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.channel-row > span {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.channel-row select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import type { DeviceStatus, TabletDevice } from '../types'
|
||||
|
||||
defineProps<{
|
||||
devices: TabletDevice[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
reconnect: [device: TabletDevice]
|
||||
}>()
|
||||
|
||||
const statusLabels: Record<DeviceStatus, string> = {
|
||||
connected: '已连接',
|
||||
disconnected: '未连接',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="setting-card">
|
||||
<div class="card-heading">
|
||||
<h2>手写板设备</h2>
|
||||
</div>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table class="device-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>设备</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="device in devices" :key="device.id">
|
||||
<td>{{ device.name }}</td>
|
||||
<td>
|
||||
<span class="status-tag" :class="'status-' + device.status">
|
||||
{{ statusLabels[device.status] }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="outline-button" @click="emit('reconnect', device)">
|
||||
重连
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-card {
|
||||
min-width: 0;
|
||||
padding: 16px 18px;
|
||||
background: var(--card);
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
}
|
||||
|
||||
.card-heading {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.device-table {
|
||||
width: 100%;
|
||||
min-width: 430px;
|
||||
font-size: 13px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.device-table th {
|
||||
padding: 9px 12px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
background: #f5f9fa;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.device-table td {
|
||||
padding: 9px 12px;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid #eef4f6;
|
||||
}
|
||||
|
||||
.device-table tbody tr:hover td {
|
||||
background: #f7fbfc;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
color: var(--ok);
|
||||
background: var(--ok-l);
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
color: var(--info);
|
||||
background: var(--info-l);
|
||||
}
|
||||
|
||||
.outline-button {
|
||||
padding: 4px 10px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.outline-button:hover {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import PagePlaceholder from '@/components/PagePlaceholder.vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import EmbedSettingsCard from './components/EmbedSettingsCard.vue'
|
||||
import SignatureSettingsCard from './components/SignatureSettingsCard.vue'
|
||||
import SmsSettingsCard from './components/SmsSettingsCard.vue'
|
||||
import TabletDevicesCard from './components/TabletDevicesCard.vue'
|
||||
import {
|
||||
defaultSmsChannel,
|
||||
mockSignatureSettings,
|
||||
mockTabletDevices,
|
||||
smsChannelOptions,
|
||||
} from './mock'
|
||||
import type { SettingToggleKey, SignatureSettings, SmsChannel, TabletDevice } from './types'
|
||||
|
||||
const loading = ref(true)
|
||||
const signatureSettings = reactive<SignatureSettings>({ ...mockSignatureSettings })
|
||||
const smsChannel = ref<SmsChannel>(defaultSmsChannel)
|
||||
const tabletDevices = ref<TabletDevice[]>([])
|
||||
|
||||
async function loadSettings() {
|
||||
loading.value = true
|
||||
await Promise.resolve()
|
||||
tabletDevices.value = mockTabletDevices.map((device) => ({ ...device }))
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function updateLinkExpireHours(value: number) {
|
||||
signatureSettings.linkExpireHours = value
|
||||
}
|
||||
|
||||
function toggleSetting(key: SettingToggleKey) {
|
||||
signatureSettings[key] = !signatureSettings[key]
|
||||
|
||||
if (key === 'caCertificate' && signatureSettings.caCertificate) {
|
||||
ElMessage.info('已开启 CA 扩展位(当前版本未启用)')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.info('设置已更新')
|
||||
}
|
||||
|
||||
function updateSmsChannel(value: SmsChannel) {
|
||||
smsChannel.value = value
|
||||
}
|
||||
|
||||
function sendTestSms() {
|
||||
ElMessage.success('测试短信已发送至 138****5678')
|
||||
}
|
||||
|
||||
function reconnectDevice(device: TabletDevice) {
|
||||
if (device.status === 'connected') {
|
||||
ElMessage.success('已重新连接')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.info('正在重连…')
|
||||
}
|
||||
|
||||
function copyEmbedUrl() {
|
||||
ElMessage.success('原型演示:已复制嵌入 URL')
|
||||
}
|
||||
|
||||
function rotateKey() {
|
||||
ElMessage.success('原型演示:密钥轮换成功')
|
||||
}
|
||||
|
||||
onMounted(loadSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PagePlaceholder
|
||||
group="管理"
|
||||
title="系统设置"
|
||||
icon="⚙"
|
||||
description="系统设置页面占位,待根据 Web 端需求实现。"
|
||||
<section class="settings-page">
|
||||
<div v-if="loading" class="loading-grid" aria-label="正在加载系统设置">
|
||||
<span v-for="index in 4" :key="index" />
|
||||
</div>
|
||||
|
||||
<div v-else class="settings-grid">
|
||||
<SignatureSettingsCard
|
||||
:settings="signatureSettings"
|
||||
@update:link-expire-hours="updateLinkExpireHours"
|
||||
@toggle="toggleSetting"
|
||||
/>
|
||||
<SmsSettingsCard
|
||||
:channel="smsChannel"
|
||||
:channel-options="smsChannelOptions"
|
||||
@update:channel="updateSmsChannel"
|
||||
@test="sendTestSms"
|
||||
/>
|
||||
<TabletDevicesCard :devices="tabletDevices" @reconnect="reconnectDevice" />
|
||||
<EmbedSettingsCard @copy="copyEmbedUrl" @rotate-key="rotateKey" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings-page {
|
||||
width: 100%;
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-grid,
|
||||
.loading-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.loading-grid span {
|
||||
display: block;
|
||||
height: 240px;
|
||||
background: linear-gradient(90deg, #edf3f5 25%, #f7fafb 37%, #edf3f5 63%);
|
||||
background-size: 400% 100%;
|
||||
border-radius: var(--r);
|
||||
box-shadow: var(--sh);
|
||||
animation: skeleton-shimmer 1.4s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.settings-grid,
|
||||
.loading-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
33
clinical-web/src/views/management/settings/mock.ts
Normal file
33
clinical-web/src/views/management/settings/mock.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { SignatureSettings, SmsChannel, SmsChannelOption, TabletDevice } from './types'
|
||||
|
||||
export const mockSignatureSettings: SignatureSettings = {
|
||||
linkExpireHours: 48,
|
||||
timeoutReminder: true,
|
||||
dualRetention: true,
|
||||
caCertificate: false,
|
||||
}
|
||||
|
||||
export const smsChannelOptions: SmsChannelOption[] = [
|
||||
{ value: 'hospital-primary', label: '医院短信网关(主)' },
|
||||
{ value: 'third-party-backup', label: '第三方短信通道(备)' },
|
||||
]
|
||||
|
||||
export const defaultSmsChannel: SmsChannel = 'hospital-primary'
|
||||
|
||||
export const mockTabletDevices: TabletDevice[] = [
|
||||
{
|
||||
id: 'tablet-01',
|
||||
name: '汉王 GP-1108U(护士站01)',
|
||||
status: 'connected',
|
||||
},
|
||||
{
|
||||
id: 'tablet-02',
|
||||
name: '汉王 GP-1108U(护士站02)',
|
||||
status: 'connected',
|
||||
},
|
||||
{
|
||||
id: 'tablet-03',
|
||||
name: 'Wacom STU-430(放射登记台)',
|
||||
status: 'disconnected',
|
||||
},
|
||||
]
|
||||
@@ -1,10 +1,32 @@
|
||||
export type SettingValue = string | number | boolean
|
||||
export type SettingToggleKey = 'timeoutReminder' | 'dualRetention' | 'caCertificate'
|
||||
|
||||
export type DeviceStatus = 'connected' | 'disconnected'
|
||||
|
||||
export type SmsChannel = 'hospital-primary' | 'third-party-backup'
|
||||
|
||||
export interface SignatureSettings {
|
||||
linkExpireHours: number
|
||||
timeoutReminder: boolean
|
||||
dualRetention: boolean
|
||||
caCertificate: boolean
|
||||
}
|
||||
|
||||
export interface TabletDevice {
|
||||
id: string
|
||||
name: string
|
||||
status: DeviceStatus
|
||||
}
|
||||
|
||||
export interface SmsChannelOption {
|
||||
value: SmsChannel
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface SettingFormItem {
|
||||
key: string
|
||||
label: string
|
||||
description: string
|
||||
value: SettingValue
|
||||
value: string | number | boolean
|
||||
valueType: 'string' | 'number' | 'boolean'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user