replace el-form

This commit is contained in:
pa
2026-01-12 22:42:54 +09:00
committed by Natsumi
parent 82bd985142
commit c814f8f60c
34 changed files with 1419 additions and 736 deletions
+72 -59
View File
@@ -51,65 +51,77 @@
<p class="mutual-graph__force-description">
{{ t('view.charts.mutual_friend.force_dialog.description') }}
</p>
<el-form label-position="top" size="small" class="mutual-graph__force-form">
<el-form-item :label="t('view.charts.mutual_friend.force_dialog.repulsion')">
<NumberField
v-model="forceForm.repulsion"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<div class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.repulsion_help') }}
</div>
</el-form-item>
<el-form-item :label="t('view.charts.mutual_friend.force_dialog.edge_length_min')">
<NumberField
v-model="forceForm.edgeLengthMin"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<div class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_min_help') }}
</div>
</el-form-item>
<el-form-item :label="t('view.charts.mutual_friend.force_dialog.edge_length_max')">
<NumberField
v-model="forceForm.edgeLengthMax"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<div class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_max_help') }}
</div>
</el-form-item>
<el-form-item :label="t('view.charts.mutual_friend.force_dialog.gravity')">
<NumberField
v-model="forceForm.gravity"
:max="1"
:step="0.1"
:format-options="{ maximumFractionDigits: 1 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<div class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.gravity_help') }}
</div>
</el-form-item>
</el-form>
<FieldGroup class="mutual-graph__force-form">
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.repulsion') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.repulsion"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.repulsion_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_min') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMin"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_min_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.edge_length_max') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.edgeLengthMax"
:step="1"
:format-options="{ maximumFractionDigits: 0 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.edge_length_max_help') }}
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>{{ t('view.charts.mutual_friend.force_dialog.gravity') }}</FieldLabel>
<FieldContent>
<NumberField
v-model="forceForm.gravity"
:max="1"
:step="0.1"
:format-options="{ maximumFractionDigits: 1 }"
class="mutual-graph__number-input">
<NumberFieldContent>
<NumberFieldInput />
</NumberFieldContent>
</NumberField>
<FieldDescription class="mutual-graph__helper">
{{ t('view.charts.mutual_friend.force_dialog.gravity_help') }}
</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
<template #footer>
<div class="mutual-graph__dialog-footer">
@@ -127,6 +139,7 @@
<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field';
import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
+152 -72
View File
@@ -16,71 +16,101 @@
<div class="x-login-form-container">
<div>
<h2 style="font-weight: bold; text-align: center; margin: 0">{{ t('view.login.login') }}</h2>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginForm.rules"
@submit.prevent="handleLogin()">
<el-form-item
:label="t('view.login.field.username')"
prop="username"
required
style="display: block">
<InputGroupField
v-model="loginForm.username"
name="username"
:placeholder="t('view.login.field.username')"
clearable />
</el-form-item>
<el-form-item
:label="t('view.login.field.password')"
prop="password"
required
style="display: block; margin-top: 10px">
<InputGroupField
v-model="loginForm.password"
type="password"
name="password"
:placeholder="t('view.login.field.password')"
clearable
show-password />
</el-form-item>
<form id="login-form" @submit.prevent="onSubmit">
<FieldGroup class="gap-3">
<VeeField v-slot="{ field, errors }" name="username">
<Field :data-invalid="!!errors.length">
<FieldLabel for="login-form-username">
{{ t('view.login.field.username') }}
</FieldLabel>
<FieldContent>
<InputGroupField
id="login-form-username"
:model-value="field.value"
name="username"
:placeholder="t('view.login.field.username')"
:aria-invalid="!!errors.length"
clearable
@update:modelValue="field.onChange"
@blur="field.onBlur" />
<FieldError v-if="errors.length" :errors="errors" />
</FieldContent>
</Field>
</VeeField>
<VeeField v-slot="{ field, errors }" name="password">
<Field :data-invalid="!!errors.length">
<FieldLabel for="login-form-password">
{{ t('view.login.field.password') }}
</FieldLabel>
<FieldContent>
<InputGroupField
id="login-form-password"
:model-value="field.value"
type="password"
name="password"
:placeholder="t('view.login.field.password')"
:aria-invalid="!!errors.length"
clearable
show-password
@update:modelValue="field.onChange"
@blur="field.onBlur" />
<FieldError v-if="errors.length" :errors="errors" />
</FieldContent>
</Field>
</VeeField>
</FieldGroup>
<label class="inline-flex items-center gap-2 mr-2">
<Checkbox v-model="loginForm.saveCredentials" />
<span>{{ t('view.login.field.saveCredentials') }}</span>
</label>
<label class="inline-flex items-center gap-2" style="margin-top: 10px">
<Checkbox v-model="enableCustomEndpoint" @update:modelValue="toggleCustomEndpoint" />
<Checkbox v-model="enableCustomEndpoint" @update:modelValue="handleCustomEndpointToggle" />
<span>{{ t('view.login.field.devEndpoint') }}</span>
</label>
<el-form-item
v-if="enableCustomEndpoint"
:label="t('view.login.field.endpoint')"
prop="endpoint"
style="margin-top: 10px">
<InputGroupField
v-model="loginForm.endpoint"
name="endpoint"
:placeholder="AppDebug.endpointDomainVrchat"
clearable />
</el-form-item>
<el-form-item
v-if="enableCustomEndpoint"
:label="t('view.login.field.websocket')"
prop="websocket"
style="margin-top: 10px">
<InputGroupField
v-model="loginForm.websocket"
name="websocket"
:placeholder="AppDebug.websocketDomainVrchat"
clearable />
</el-form-item>
<el-form-item>
<Button class="mt-2" type="submit" size="lg" style="width: 100%">{{
t('view.login.login')
}}</Button>
</el-form-item>
</el-form>
<FieldGroup v-if="enableCustomEndpoint" class="mt-3 gap-3">
<VeeField v-slot="{ field, errors }" name="endpoint">
<Field :data-invalid="!!errors.length">
<FieldLabel for="login-form-endpoint">
{{ t('view.login.field.endpoint') }}
</FieldLabel>
<FieldContent>
<InputGroupField
id="login-form-endpoint"
:model-value="field.value"
name="endpoint"
:placeholder="AppDebug.endpointDomainVrchat"
:aria-invalid="!!errors.length"
clearable
@update:modelValue="field.onChange"
@blur="field.onBlur" />
<FieldError v-if="errors.length" :errors="errors" />
</FieldContent>
</Field>
</VeeField>
<VeeField v-slot="{ field, errors }" name="websocket">
<Field :data-invalid="!!errors.length">
<FieldLabel for="login-form-websocket">
{{ t('view.login.field.websocket') }}
</FieldLabel>
<FieldContent>
<InputGroupField
id="login-form-websocket"
:model-value="field.value"
name="websocket"
:placeholder="AppDebug.websocketDomainVrchat"
:aria-invalid="!!errors.length"
clearable
@update:modelValue="field.onChange"
@blur="field.onBlur" />
<FieldError v-if="errors.length" :errors="errors" />
</FieldContent>
</Field>
</VeeField>
</FieldGroup>
<Field class="mt-2">
<Button type="submit" size="lg" style="width: 100%">{{ t('view.login.login') }}</Button>
</Field>
</form>
<Button
variant="Secondary"
size="lg"
@@ -147,14 +177,18 @@
</template>
<script setup>
import { Field, FieldContent, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field';
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue';
import { CircleArrowDown, Route } from 'lucide-vue-next';
import { Field as VeeField, useForm } from 'vee-validate';
import { useRoute, useRouter } from 'vue-router';
import { Button } from '@/components/ui/button';
import { InputGroupField } from '@/components/ui/input-group';
import { Checkbox } from '@/components/ui/checkbox';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { toTypedSchema } from '@vee-validate/zod';
import { useI18n } from 'vue-i18n';
import { z } from 'zod';
import { useAuthStore, useGeneralSettingsStore, useVRCXUpdaterStore } from '../../stores';
import { openExternalLink, userImage } from '../../shared/utils';
@@ -171,8 +205,27 @@
const { t } = useI18n();
const loginFormRef = ref(null);
const savedCredentials = ref({});
const requiredMessage = 'Required';
const formSchema = toTypedSchema(
z.object({
username: z.string().min(1, requiredMessage),
password: z.string().min(1, requiredMessage),
endpoint: z.string().optional(),
websocket: z.string().optional()
})
);
const { handleSubmit, resetForm, setValues, values } = useForm({
validationSchema: formSchema,
initialValues: {
username: loginForm.value.username,
password: loginForm.value.password,
endpoint: loginForm.value.endpoint,
websocket: loginForm.value.websocket
}
});
async function clickDeleteSavedLogin(userId) {
await deleteSavedLogin(userId);
@@ -184,15 +237,22 @@
await updateSavedCredentials();
}
function handleLogin() {
if (loginFormRef.value) {
loginFormRef.value.validate(async (valid) => {
if (valid) {
await login();
await updateSavedCredentials();
}
});
}
const onSubmit = handleSubmit(async (formValues) => {
loginForm.value.username = formValues.username ?? '';
loginForm.value.password = formValues.password ?? '';
loginForm.value.endpoint = formValues.endpoint ?? '';
loginForm.value.websocket = formValues.websocket ?? '';
await login();
await updateSavedCredentials();
});
async function handleCustomEndpointToggle() {
await toggleCustomEndpoint();
setValues({
...values,
endpoint: loginForm.value.endpoint,
websocket: loginForm.value.websocket
});
}
async function updateSavedCredentials() {
@@ -224,9 +284,29 @@
});
onBeforeUnmount(() => {
if (loginFormRef.value) {
loginFormRef.value.resetFields();
}
resetForm({
values: {
username: '',
password: '',
endpoint: '',
websocket: ''
}
});
loginForm.value.username = '';
loginForm.value.password = '';
loginForm.value.endpoint = '';
loginForm.value.websocket = '';
savedCredentials.value = {};
});
watch(
values,
(formValues) => {
loginForm.value.username = formValues.username ?? '';
loginForm.value.password = formValues.password ?? '';
loginForm.value.endpoint = formValues.endpoint ?? '';
loginForm.value.websocket = formValues.websocket ?? '';
},
{ deep: true }
);
</script>
@@ -21,65 +21,83 @@
</Select>
</div>
<br />
<el-form label-position="top" label-width="120px" size="small" style="margin-bottom: 12px">
<el-form-item :label="t('dialog.translation_api.mode')">
<Select :model-value="form.translationApiType" @update:modelValue="handleTranslationApiTypeChange">
<SelectTrigger size="sm" style="width: 100%">
<SelectValue :placeholder="t('dialog.translation_api.mode')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="google" :text-value="t('dialog.translation_api.mode_google')">
{{ t('dialog.translation_api.mode_google') }}
</SelectItem>
<SelectItem value="openai" :text-value="t('dialog.translation_api.mode_openai')">
{{ t('dialog.translation_api.mode_openai') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</el-form-item>
</el-form>
<FieldGroup class="mb-3">
<Field>
<FieldLabel>{{ t('dialog.translation_api.mode') }}</FieldLabel>
<FieldContent>
<Select :model-value="form.translationApiType" @update:modelValue="handleTranslationApiTypeChange">
<SelectTrigger size="sm" style="width: 100%">
<SelectValue :placeholder="t('dialog.translation_api.mode')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="google" :text-value="t('dialog.translation_api.mode_google')">
{{ t('dialog.translation_api.mode_google') }}
</SelectItem>
<SelectItem value="openai" :text-value="t('dialog.translation_api.mode_openai')">
{{ t('dialog.translation_api.mode_openai') }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FieldContent>
</Field>
</FieldGroup>
<template v-if="form.translationApiType === 'google'">
<el-form label-position="top" label-width="120px" size="small">
<el-form-item :label="t('dialog.translation_api.description')">
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="AIzaSy..."
clearable />
</el-form-item>
</el-form>
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.description') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="AIzaSy..."
clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<template v-if="form.translationApiType === 'openai'">
<el-form label-position="top" label-width="120px" size="small">
<el-form-item :label="t('dialog.translation_api.openai.endpoint')">
<InputGroupField
v-model="form.translationApiEndpoint"
placeholder="https://api.openai.com/v1/chat/completions"
clearable />
</el-form-item>
<FieldGroup>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.endpoint') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiEndpoint"
placeholder="https://api.openai.com/v1/chat/completions"
clearable />
</FieldContent>
</Field>
<el-form-item :label="t('dialog.translation_api.openai.api_key')">
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="sk-..."
clearable />
</el-form-item>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.api_key') }}</FieldLabel>
<FieldContent>
<InputGroupField
v-model="form.translationApiKey"
type="password"
show-password
placeholder="sk-..."
clearable />
</FieldContent>
</Field>
<el-form-item :label="t('dialog.translation_api.openai.model')">
<InputGroupField v-model="form.translationApiModel" clearable />
</el-form-item>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.model') }}</FieldLabel>
<FieldContent>
<InputGroupField v-model="form.translationApiModel" clearable />
</FieldContent>
</Field>
<el-form-item :label="t('dialog.translation_api.openai.prompt_optional')">
<InputGroupTextareaField v-model="form.translationApiPrompt" :rows="3" clearable />
</el-form-item>
</el-form>
<Field>
<FieldLabel>{{ t('dialog.translation_api.openai.prompt_optional') }}</FieldLabel>
<FieldContent>
<InputGroupTextareaField v-model="form.translationApiPrompt" :rows="3" clearable />
</FieldContent>
</Field>
</FieldGroup>
</template>
<template #footer>
@@ -94,14 +112,14 @@
">
{{ t('dialog.translation_api.guide') }}
</Button>
<Button
variant="outline"
class="mr-2"
v-if="form.translationApiType === 'openai'"
@click="testOpenAiTranslation">
{{ t('dialog.translation_api.test') }}
</Button>
<div>
<Button
variant="secondary"
class="mr-2"
v-if="form.translationApiType === 'openai'"
@click="testOpenAiTranslation">
{{ t('dialog.translation_api.test') }}
</Button>
<Button style="margin-left: auto" @click="saveTranslationApiConfig">
{{ t('dialog.translation_api.save') }}
</Button>
@@ -113,9 +131,10 @@
<script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
import { reactive, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { InputGroupField, InputGroupTextareaField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
@@ -312,9 +312,9 @@
.x-link:hover {
text-decoration: none;
}
.x-link:hover span {
/* .x-link:hover span {
text-decoration: underline;
}
} */
.is-rotated {
transform: rotate(90deg);
}