generated from kgod/ai-review-template
215 lines
5.9 KiB
Vue
215 lines
5.9 KiB
Vue
<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>
|