Files
daywork/intern-log/src/components/CredentialDialog.vue
T

119 lines
4.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { nextTick, onUnmounted, ref, watch } from 'vue'
import Icon from './Icon.vue'
const props = defineProps<{
open: boolean
title: string
initial: { system: string; account: string; password: string; remark: string }
}>()
const emit = defineEmits<{
(e: 'cancel'): void
(e: 'save', payload: { system: string; account: string; password: string; remark: string }): void
}>()
const fSystem = ref('')
const fAccount = ref('')
const fPassword = ref('')
const fRemark = ref('')
const fError = ref('')
const systemInput = ref<HTMLInputElement | null>(null)
function onKey(e: KeyboardEvent) {
if (!props.open) return
if (e.key === 'Escape') emit('cancel')
}
watch(() => props.open, async v => {
if (v) {
fSystem.value = props.initial.system
fAccount.value = props.initial.account
fPassword.value = props.initial.password
fRemark.value = props.initial.remark
fError.value = ''
window.addEventListener('keydown', onKey)
await nextTick()
systemInput.value?.focus()
} else {
window.removeEventListener('keydown', onKey)
}
})
onUnmounted(() => window.removeEventListener('keydown', onKey))
// 生成 15 位随机密码:大小写字母与数字为主体,末尾追加一个符号
function genPassword() {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
const symbols = '!@#$%^&*'
const arr = crypto.getRandomValues(new Uint8Array(14))
let s = ''
for (let i = 0; i < arr.length; i++) s += chars[arr[i] % chars.length]
s += symbols[crypto.getRandomValues(new Uint8Array(1))[0] % symbols.length]
fPassword.value = s
fError.value = ''
}
function save() {
if (!fSystem.value.trim() || !fAccount.value.trim() || !fPassword.value) {
fError.value = '系统名称、账号、密码为必填项'
return
}
emit('save', {
system: fSystem.value.trim(),
account: fAccount.value.trim(),
password: fPassword.value,
remark: fRemark.value.trim()
})
}
</script>
<template>
<teleport to="body">
<transition name="modal">
<div v-if="open" class="mask" @click.self="emit('cancel')">
<div class="dialog" role="dialog" :aria-label="title">
<div class="dialog-head">
<span class="dialog-title">{{ title }}</span>
<button class="icon-btn" title="关闭" @click="emit('cancel')">
<Icon name="close" :size="16" />
</button>
</div>
<div class="dialog-body">
<div class="form-grid">
<div class="field span2">
<label>系统名称(必填)</label>
<input ref="systemInput" class="input" v-model="fSystem" placeholder="如:企业邮箱 / OA / VPN" />
</div>
<div class="field span2">
<label>账号(必填)</label>
<input class="input" v-model="fAccount" placeholder="用户名、工号或邮箱" />
</div>
<div class="field span2">
<label>密码(必填)</label>
<div class="pwd-row">
<input class="input" v-model="fPassword" placeholder="输入或点击生成" />
<button class="btn btn-sm" @click="genPassword">生成</button>
</div>
</div>
<div class="field span2">
<label>备注</label>
<textarea class="textarea" v-model="fRemark" placeholder="用途、到期时间、负责人等(选填)"></textarea>
</div>
</div>
<div v-if="fError" class="form-error">{{ fError }}</div>
</div>
<div class="dialog-btns">
<button class="btn btn-sm" @click="emit('cancel')">取消</button>
<button class="btn btn-primary btn-sm" @click="save">保存</button>
</div>
</div>
</div>
</transition>
</teleport>
</template>
<style scoped>
.dialog { width: 480px; }
.pwd-row { display: flex; gap: 8px; }
.pwd-row .input { flex: 1; }
</style>