generated from kgod/ai-review-template
feat: add resume agent MVP
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { ComponentSubmission } from '../types/resumeAgent'
|
||||
import {
|
||||
booleanValue,
|
||||
initialValue,
|
||||
optionList,
|
||||
stringValue,
|
||||
} from '../utils/componentData'
|
||||
import FormCard from './shared/FormCard.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: Record<string, unknown>
|
||||
value?: unknown
|
||||
readOnly?: boolean
|
||||
pending?: boolean
|
||||
inputOnly?: boolean
|
||||
}>(),
|
||||
{ value: undefined, readOnly: false, pending: false, inputOnly: false },
|
||||
)
|
||||
|
||||
const emit = defineEmits<{ submit: [submission: ComponentSubmission] }>()
|
||||
const accountOptions = computed(() => {
|
||||
if (props.inputOnly) return []
|
||||
const explicit = optionList(props.data.options ?? props.data.phones ?? props.data.choices)
|
||||
if (explicit.length) return explicit
|
||||
return [
|
||||
...(booleanValue(props.data.has_account_phone)
|
||||
? [{
|
||||
value: 'account',
|
||||
label: stringValue(props.data.masked_phone, '使用登录手机号'),
|
||||
description: '将登录手机号作为简历联系电话',
|
||||
}]
|
||||
: []),
|
||||
{ value: 'manual', label: '输入其他手机号', description: '仅校验格式,不发送验证码' },
|
||||
]
|
||||
})
|
||||
const sourceValue = computed(() =>
|
||||
typeof props.value === 'string' ? props.value : initialValue(props.data),
|
||||
)
|
||||
const selected = ref(sourceValue.value)
|
||||
const manualPhone = ref(props.inputOnly ? sourceValue.value : '')
|
||||
const validationError = ref('')
|
||||
|
||||
watch(sourceValue, (next) => {
|
||||
selected.value = next
|
||||
if (props.inputOnly) manualPhone.value = next
|
||||
})
|
||||
|
||||
const resolvedValue = computed(() => (props.inputOnly ? manualPhone.value.trim() : selected.value))
|
||||
const resolvedLabel = computed(
|
||||
() => accountOptions.value.find((option) => option.value === sourceValue.value)?.label || sourceValue.value,
|
||||
)
|
||||
|
||||
function selectPhone(value: string) {
|
||||
selected.value = value
|
||||
validationError.value = ''
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!props.inputOnly) {
|
||||
if (!selected.value) {
|
||||
validationError.value = '请选择手机号来源。'
|
||||
return
|
||||
}
|
||||
const source = ['manual', 'other'].includes(selected.value) ? 'manual' : 'account'
|
||||
emit('submit', {
|
||||
event: 'select',
|
||||
payload: { value: source, source },
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const phone = resolvedValue.value.trim()
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
validationError.value = '手机号格式不正确,请检查后重试。'
|
||||
return
|
||||
}
|
||||
emit('submit', {
|
||||
event: 'submit',
|
||||
payload: { value: phone, phone },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormCard
|
||||
eyebrow="简历定位"
|
||||
:title="stringValue(data.title, inputOnly ? '输入其他简历手机号' : '选择简历联系电话')"
|
||||
:description="stringValue(data.description, '可以使用登录手机号,也可以填写其他中国大陆手机号。')"
|
||||
:read-only="readOnly"
|
||||
:pending="pending"
|
||||
>
|
||||
<div v-if="readOnly" class="read-only-value">{{ resolvedLabel || '手机号已记录' }}</div>
|
||||
<form v-else novalidate @submit.prevent="submit">
|
||||
<ul v-if="!inputOnly" class="phone-list">
|
||||
<li v-for="phone in accountOptions" :key="phone.value">
|
||||
<button
|
||||
class="phone-option"
|
||||
:class="{ 'phone-option--selected': selected === phone.value }"
|
||||
type="button"
|
||||
:disabled="pending || phone.disabled"
|
||||
:aria-pressed="selected === phone.value"
|
||||
@click="selectPhone(phone.value)"
|
||||
>
|
||||
<span class="phone-option__avatar" aria-hidden="true">{{ phone.label.slice(-2) }}</span>
|
||||
<span>
|
||||
<strong>{{ phone.label }}</strong>
|
||||
<small>{{ phone.description || '已有简历记录' }}</small>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="inputOnly" class="field-group">
|
||||
<label class="field-label" for="resume-phone">手机号</label>
|
||||
<input
|
||||
id="resume-phone"
|
||||
v-model="manualPhone"
|
||||
class="text-input"
|
||||
type="tel"
|
||||
inputmode="tel"
|
||||
autocomplete="tel"
|
||||
:placeholder="stringValue(data.placeholder, '例如:13800138000')"
|
||||
:disabled="pending"
|
||||
:aria-invalid="Boolean(validationError)"
|
||||
aria-describedby="resume-phone-help resume-phone-error"
|
||||
@input="validationError = ''"
|
||||
/>
|
||||
<p id="resume-phone-help" class="field-hint">仅校验 11 位中国大陆手机号格式,不发送验证码。</p>
|
||||
<p v-if="validationError" id="resume-phone-error" class="validation-error" role="alert">
|
||||
{{ validationError }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="component-actions">
|
||||
<button class="primary-button" type="submit" :disabled="!resolvedValue || pending">
|
||||
继续
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</FormCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.phone-list {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.phone-option {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 70px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 11px 13px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
color: var(--ink);
|
||||
background: #f9fcfb;
|
||||
text-align: left;
|
||||
transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.phone-option:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
border-color: #91cbc7;
|
||||
}
|
||||
|
||||
.phone-option--selected {
|
||||
border-color: var(--brand);
|
||||
background: var(--brand-soft);
|
||||
}
|
||||
|
||||
.phone-option__avatar {
|
||||
display: grid;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
border-radius: 13px;
|
||||
color: var(--brand-dark);
|
||||
background: #fff;
|
||||
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.phone-option strong,
|
||||
.phone-option small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.phone-option strong {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.phone-option small {
|
||||
margin-top: 3px;
|
||||
color: var(--ink-faint);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
margin: 0;
|
||||
color: var(--danger);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user