Добавил редактор property. Дополнил генерацию spec.
This commit is contained in:
parent
3d6b3d8059
commit
db88b2cf28
|
|
@ -118,14 +118,13 @@ public class SpecGenerator {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var classAnnotation = type.getAnnotation(GenerateApiSpec.class);
|
var classAnnotation = type.getAnnotation(GenerateApiSpec.class);
|
||||||
if (classAnnotation == null || !type.isInterface()) {
|
if (classAnnotation == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var aClass : classAnnotation.directInheritors()) {
|
for (var aClass : classAnnotation.directInheritors()) {
|
||||||
if (!types.contains(aClass)) {
|
if (!types.contains(aClass)) {
|
||||||
types.add(aClass);
|
typesQueue.add(registerType(aClass));
|
||||||
typesQueue.add(aClass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,8 +134,8 @@ public class SpecGenerator {
|
||||||
var parents = typeDescriptor.putArray("parents");
|
var parents = typeDescriptor.putArray("parents");
|
||||||
for (var aClass : type.getInterfaces()) {
|
for (var aClass : type.getInterfaces()) {
|
||||||
if (!types.contains(aClass)) {
|
if (!types.contains(aClass)) {
|
||||||
types.add(aClass);
|
|
||||||
typesQueue.add(aClass);
|
typesQueue.add(registerType(aClass));
|
||||||
}
|
}
|
||||||
if (!aClass.isAnnotationPresent(GenerateApiSpec.class)) {
|
if (!aClass.isAnnotationPresent(GenerateApiSpec.class)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -145,6 +144,29 @@ public class SpecGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fields = typeDescriptor.putArray("fields");
|
var fields = typeDescriptor.putArray("fields");
|
||||||
|
if (!type.isInterface()) {
|
||||||
|
for (var field : type.getDeclaredFields()) {
|
||||||
|
var annotation = field.getAnnotation(GenerateApiSpec.class);
|
||||||
|
if (annotation == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var returnType = annotation.type();
|
||||||
|
if (returnType == void.class) {
|
||||||
|
returnType = field.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!types.contains(returnType)) {
|
||||||
|
typesQueue.put(registerType(returnType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldDescriptor = fields.addObject();
|
||||||
|
fieldDescriptor.put("name", annotation.alias().isEmpty() ? field.getName() : annotation.alias());
|
||||||
|
fieldDescriptor.put("type", getTypeName(returnType));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
for (var method : type.getMethods()) {
|
for (var method : type.getMethods()) {
|
||||||
var methodAnnotation = method.getAnnotation(GenerateApiSpec.class);
|
var methodAnnotation = method.getAnnotation(GenerateApiSpec.class);
|
||||||
if (methodAnnotation == null) {
|
if (methodAnnotation == null) {
|
||||||
|
|
@ -157,8 +179,7 @@ public class SpecGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!types.contains(returnType)) {
|
if (!types.contains(returnType)) {
|
||||||
types.add(returnType);
|
typesQueue.put(registerType(returnType));
|
||||||
typesQueue.put(returnType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var methodName = method.getName();
|
var methodName = method.getName();
|
||||||
|
|
@ -195,18 +216,20 @@ public class SpecGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerType(Class<?> type) {
|
private Class<?> registerType(Class<?> type) {
|
||||||
if (type.isArray()) {
|
if (type.isArray()) {
|
||||||
types.add(type.getComponentType());
|
types.add(type.getComponentType());
|
||||||
|
return type.getComponentType();
|
||||||
} else {
|
} else {
|
||||||
types.add(type);
|
types.add(type);
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTypeName(Class<?> type) {
|
private String getTypeName(Class<?> type) {
|
||||||
if (type == null) return "unknown";
|
if (type == null) return "unknown";
|
||||||
if (type == boolean.class) return "boolean";
|
if (type == boolean.class) return "boolean";
|
||||||
if (type == int.class || type == long.class) return "number";
|
if (type == int.class || type == long.class || type == double.class || type == float.class) return "number";
|
||||||
if (type == String.class) return "string";
|
if (type == String.class) return "string";
|
||||||
if (type == void.class || type == Void.class) return "void";
|
if (type == void.class || type == Void.class) return "void";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
|
||||||
public @interface GenerateApiSpec {
|
public @interface GenerateApiSpec {
|
||||||
String alias() default "";
|
String alias() default "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ package ru.kirillius.XCP.Persistence.Entities;
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||||
|
import ru.kirillius.XCP.Properties.Constraint;
|
||||||
import ru.kirillius.XCP.Properties.Constraints;
|
import ru.kirillius.XCP.Properties.Constraints;
|
||||||
import ru.kirillius.XCP.Properties.PropertyType;
|
import ru.kirillius.XCP.Properties.PropertyType;
|
||||||
|
|
||||||
@GenerateApiSpec
|
@GenerateApiSpec
|
||||||
public interface PropertyDescriptor extends PersistenceEntity {
|
public interface PropertyDescriptor extends PersistenceEntity {
|
||||||
@GenerateApiSpec
|
@GenerateApiSpec(type = Constraint[].class)
|
||||||
Constraints getConstraints();
|
Constraints getConstraints();
|
||||||
|
|
||||||
void setConstraints(Constraints constraints);
|
void setConstraints(Constraints constraints);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
||||||
})
|
})
|
||||||
public interface Constraint {
|
public interface Constraint {
|
||||||
@JsonProperty(value = "type")
|
@JsonProperty(value = "type")
|
||||||
default String type() {
|
@GenerateApiSpec
|
||||||
|
default String getType() {
|
||||||
return getClass().getSimpleName();
|
return getClass().getSimpleName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public final class NumberConstraint implements Constraint {
|
public final class NumberConstraint implements Constraint {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
private double min;
|
private double min;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
private double max;
|
private double max;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
private double step;
|
private double step;
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
private boolean integer;
|
private boolean integer;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,14 @@ import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public final class StringConstraint implements Constraint {
|
public final class StringConstraint implements Constraint {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
private int maxLength;
|
private int maxLength;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
|
private boolean multiline;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@GenerateApiSpec
|
||||||
|
private String regexp;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package ru.kirillius.XCP.Properties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
|
@ -14,5 +15,6 @@ import java.util.Collection;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public final class ValueListConstraint implements Constraint {
|
public final class ValueListConstraint implements Constraint {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@GenerateApiSpec(type = ArrayNode.class)
|
||||||
private Collection<String> values;
|
private Collection<String> values;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,22 @@
|
||||||
"uuid": "00000000-0000-0000-0000-000000000000",
|
"uuid": "00000000-0000-0000-0000-000000000000",
|
||||||
"passwordHash": "$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ"
|
"passwordHash": "$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PropertyDescriptor",
|
||||||
|
"entity": {
|
||||||
|
"constraints": [
|
||||||
|
{
|
||||||
|
"type": "StringConstraint",
|
||||||
|
"maxLength": 15,
|
||||||
|
"multiline": false,
|
||||||
|
"regexp": "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"array": false,
|
||||||
|
"propertyType": "Text",
|
||||||
|
"name": "host.ip",
|
||||||
|
"uuid": "00bd0f8a-e9d8-4789-83e5-d2f2eb94f876"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -63,7 +63,8 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
||||||
InputRepositoryImpl.class,
|
InputRepositoryImpl.class,
|
||||||
OutputRepositoryImpl.class,
|
OutputRepositoryImpl.class,
|
||||||
TagRepositoryImpl.class,
|
TagRepositoryImpl.class,
|
||||||
UserRepositoryImpl.class
|
UserRepositoryImpl.class,
|
||||||
|
PropertyDescriptorRepositoryImpl.class
|
||||||
);
|
);
|
||||||
configuration = new Configuration();
|
configuration = new Configuration();
|
||||||
configuration.configure();
|
configuration.configure();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import ru.kirillius.XCP.Commons.Context;
|
||||||
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcServlet;
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcServlet;
|
||||||
import ru.kirillius.XCP.RPC.Services.Auth;
|
import ru.kirillius.XCP.RPC.Services.Auth;
|
||||||
import ru.kirillius.XCP.RPC.Services.Profile;
|
import ru.kirillius.XCP.RPC.Services.Profile;
|
||||||
|
import ru.kirillius.XCP.RPC.Services.Properties;
|
||||||
import ru.kirillius.XCP.RPC.Services.Users;
|
import ru.kirillius.XCP.RPC.Services.Users;
|
||||||
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
||||||
import ru.kirillius.XCP.Services.WebService;
|
import ru.kirillius.XCP.Services.WebService;
|
||||||
|
|
@ -30,7 +31,8 @@ public class WebServiceImpl implements WebService {
|
||||||
jsonRpc.registerRpcService(
|
jsonRpc.registerRpcService(
|
||||||
Users.class,
|
Users.class,
|
||||||
Auth.class,
|
Auth.class,
|
||||||
Profile.class
|
Profile.class,
|
||||||
|
Properties.class
|
||||||
);
|
);
|
||||||
var config = context.getConfig();
|
var config = context.getConfig();
|
||||||
server = new Server(new InetSocketAddress(config.getHost(), config.getHttpPort()));
|
server = new Server(new InetSocketAddress(config.getHost(), config.getHttpPort()));
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ interface ApiModule {
|
||||||
interface ApiType {
|
interface ApiType {
|
||||||
name: string
|
name: string
|
||||||
type: 'class' | 'enum'
|
type: 'class' | 'enum'
|
||||||
|
parents?: string[]
|
||||||
fields?: Array<{
|
fields?: Array<{
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -77,8 +78,12 @@ ${values.map((value) => ` ${value} = "${value}"`).join(',\n')}
|
||||||
}`
|
}`
|
||||||
} else if (apiType.type === 'class') {
|
} else if (apiType.type === 'class') {
|
||||||
const fields = apiType.fields || []
|
const fields = apiType.fields || []
|
||||||
const fieldDefinitions = fields.map((field) => ` ${field.name}: ${field.type};`).join('\n')
|
const fieldDefinitions = fields
|
||||||
return `export interface ${apiType.name} {
|
.map((field) => ` ${field.name}: ${field.type === 'array' ? '[]' : field.type};`)
|
||||||
|
.join('\n')
|
||||||
|
const parents = apiType.parents || []
|
||||||
|
const extendsClause = parents.length > 0 ? ` extends ${parents.join(', ')}` : ''
|
||||||
|
return `export interface ${apiType.name}${extendsClause} {
|
||||||
${fieldDefinitions}
|
${fieldDefinitions}
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,369 @@
|
||||||
|
<template>
|
||||||
|
<v-card elevation="2" class="property-editor">
|
||||||
|
<v-card-title class="text-h6"> Свойства </v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="propertyForm" v-model="isFormValid">
|
||||||
|
<v-table density="compact">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 35%">Свойство</th>
|
||||||
|
<th style="width: 55%">Значение</th>
|
||||||
|
<th style="width: 10%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(value, key) in modelValue" :key="key">
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="font-weight-medium">{{ key }}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- Edit mode - show widgets -->
|
||||||
|
<template v-if="!disabled">
|
||||||
|
<PropertyWidget
|
||||||
|
v-if="getDescriptor(key)"
|
||||||
|
v-model="modelValue[key]"
|
||||||
|
:descriptor="getDescriptor(key)"
|
||||||
|
:disabled="disabled"
|
||||||
|
@update:model-value="updateValue(key, $event)"
|
||||||
|
/>
|
||||||
|
<!-- Fallback for unknown properties -->
|
||||||
|
<v-text-field
|
||||||
|
v-else
|
||||||
|
v-model="modelValue[key]"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Введите значение"
|
||||||
|
@update:model-value="updateValue(key, $event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- View mode - show display values -->
|
||||||
|
<template v-else>
|
||||||
|
<div v-if="getDescriptor(key)">
|
||||||
|
<span v-if="getDescriptor(key)?.propertyType === 'Boolean'">
|
||||||
|
<v-icon
|
||||||
|
:icon="modelValue[key] ? 'mdi-check' : 'mdi-close'"
|
||||||
|
:color="modelValue[key] ? 'success' : 'error'"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else-if="getDescriptor(key)?.propertyType === 'JSON'">
|
||||||
|
<div class="json-display" style="max-height: 100px; overflow-y: auto">
|
||||||
|
{{ formatJsonDisplay(modelValue[key]) }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ formatValueDisplay(key) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ formatValueDisplay(key) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<v-btn
|
||||||
|
v-if="!disabled"
|
||||||
|
icon="mdi-close"
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
@click="removeProperty(key)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Add new property row -->
|
||||||
|
<tr v-if="!disabled">
|
||||||
|
<td class="align-middle">
|
||||||
|
<v-combobox
|
||||||
|
v-model="propertyInput"
|
||||||
|
:items="availableForCombobox"
|
||||||
|
:error-messages="propertyInputError"
|
||||||
|
item-title="displayName"
|
||||||
|
item-value="name"
|
||||||
|
placeholder="Имя нового свойства"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
@update:model-value="onPropertyInputChange"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
:disabled="!getInputValue().trim() || !!propertyInputError"
|
||||||
|
size="small"
|
||||||
|
@click="addPropertyFromInput"
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</v-btn>
|
||||||
|
</td>
|
||||||
|
<td class="text-center align-middle">
|
||||||
|
<!-- Empty cell for actions column -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
|
||||||
|
<v-alert v-if="isLoading" type="info" variant="tonal" class="mt-4">
|
||||||
|
Загрузка дескрипторов свойств...
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-alert v-if="error" type="error" variant="tonal" class="mt-4">
|
||||||
|
{{ error }}
|
||||||
|
</v-alert>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
|
import type {
|
||||||
|
PropertyDescriptor,
|
||||||
|
NumberConstraint,
|
||||||
|
ValueListConstraint,
|
||||||
|
StringConstraint
|
||||||
|
} from '@/generated/RpcClient'
|
||||||
|
import PropertyWidget from '@/components/PropertyWidget.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: Record<string, any>
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: Record<string, any>): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const propertyForm = ref()
|
||||||
|
const isFormValid = ref(false)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const descriptors = ref<PropertyDescriptor[]>([])
|
||||||
|
const propertyInput = ref<string>('')
|
||||||
|
const propertyInputError = ref<string>('')
|
||||||
|
|
||||||
|
const hasPropertyInputError = computed(() => {
|
||||||
|
return !!propertyInputError.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const availableForCombobox = computed(() => {
|
||||||
|
const existingKeys = Object.keys(props.modelValue || {})
|
||||||
|
return availableDescriptors.value.filter((d) => !existingKeys.includes(d.name))
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonDisabled = computed(() => {
|
||||||
|
const hasText = propertyInputText.value && propertyInputText.value.trim().length > 0
|
||||||
|
const hasError = !!propertyInputError.value && propertyInputError.value.trim().length > 0
|
||||||
|
|
||||||
|
console.log('Debug button:', {
|
||||||
|
text: propertyInputText.value,
|
||||||
|
hasText: hasText,
|
||||||
|
error: propertyInputError.value,
|
||||||
|
hasError: hasError,
|
||||||
|
disabled: !hasText || hasError
|
||||||
|
})
|
||||||
|
|
||||||
|
return !hasText || hasError
|
||||||
|
})
|
||||||
|
|
||||||
|
const getDescriptor = (key: string): PropertyDescriptor | undefined => {
|
||||||
|
return descriptors.value.find((d) => d.name === key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableDescriptors = computed(() => {
|
||||||
|
const existingKeys = Object.keys(props.modelValue || {})
|
||||||
|
return descriptors.value
|
||||||
|
.filter((d) => !existingKeys.includes(d.name))
|
||||||
|
.map((d) => ({
|
||||||
|
...d,
|
||||||
|
displayName: d.name
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
})
|
||||||
|
|
||||||
|
const getInputValue = (): string => {
|
||||||
|
if (typeof propertyInput.value === 'object' && propertyInput.value?.name) {
|
||||||
|
return propertyInput.value.name
|
||||||
|
}
|
||||||
|
return String(propertyInput.value || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPropertyInputChange = (newValue: any) => {
|
||||||
|
propertyInput.value = newValue
|
||||||
|
validateInput(getInputValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateInput = (input: string) => {
|
||||||
|
const existingKeys = Object.keys(props.modelValue || {})
|
||||||
|
const trimmedName = input.trim()
|
||||||
|
|
||||||
|
if (!trimmedName) {
|
||||||
|
propertyInputError.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingKeys.includes(trimmedName)) {
|
||||||
|
propertyInputError.value = 'Свойство с таким именем уже существует'
|
||||||
|
} else {
|
||||||
|
propertyInputError.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch input for validation
|
||||||
|
watch(
|
||||||
|
() => getInputValue(),
|
||||||
|
(newValue) => {
|
||||||
|
validateInput(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateValue = (key: string, newValue: any) => {
|
||||||
|
const updatedValues = { ...props.modelValue }
|
||||||
|
updatedValues[key] = newValue
|
||||||
|
emit('update:modelValue', updatedValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addPropertyFromInput = () => {
|
||||||
|
const propertyName = getInputValue().trim()
|
||||||
|
if (!propertyName || propertyInputError.value) return
|
||||||
|
|
||||||
|
const existingKeys = Object.keys(props.modelValue || {})
|
||||||
|
if (existingKeys.includes(propertyName)) return
|
||||||
|
|
||||||
|
const descriptor = getDescriptor(propertyName)
|
||||||
|
let defaultValue: any = ''
|
||||||
|
|
||||||
|
if (descriptor) {
|
||||||
|
// Set default values based on property type
|
||||||
|
switch (descriptor.propertyType) {
|
||||||
|
case 'Boolean':
|
||||||
|
defaultValue = false
|
||||||
|
break
|
||||||
|
case 'Number':
|
||||||
|
const numberConstraint = descriptor.constraints?.find((c) => c.type === 'NumberConstraint')
|
||||||
|
if (numberConstraint) {
|
||||||
|
defaultValue = numberConstraint.min || 0
|
||||||
|
} else {
|
||||||
|
defaultValue = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ValueList':
|
||||||
|
const valueListConstraint = descriptor.constraints?.find(
|
||||||
|
(c) => c.type === 'ValueListConstraint'
|
||||||
|
)
|
||||||
|
if (valueListConstraint && valueListConstraint.values?.length > 0) {
|
||||||
|
defaultValue = valueListConstraint.values[0]
|
||||||
|
} else {
|
||||||
|
defaultValue = ''
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'JSON':
|
||||||
|
defaultValue = {}
|
||||||
|
break
|
||||||
|
case 'Text':
|
||||||
|
default:
|
||||||
|
defaultValue = ''
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle arrays
|
||||||
|
if (descriptor.array) {
|
||||||
|
defaultValue = [defaultValue]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Custom property - empty string
|
||||||
|
defaultValue = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedValues = { ...props.modelValue }
|
||||||
|
updatedValues[propertyName] = defaultValue
|
||||||
|
emit('update:modelValue', updatedValues)
|
||||||
|
|
||||||
|
// Reset input
|
||||||
|
propertyInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeProperty = (key: string) => {
|
||||||
|
const updatedValues = { ...props.modelValue }
|
||||||
|
delete updatedValues[key]
|
||||||
|
emit('update:modelValue', updatedValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatValueDisplay = (key: string): string => {
|
||||||
|
const value = props.modelValue[key]
|
||||||
|
const descriptor = getDescriptor(key)
|
||||||
|
|
||||||
|
if (descriptor?.propertyType === 'ValueList') {
|
||||||
|
const valueListConstraint = descriptor.constraints?.find(
|
||||||
|
(c) => c.type === 'ValueListConstraint'
|
||||||
|
)
|
||||||
|
const option = valueListConstraint?.values?.find((v: any) => v.value === value)
|
||||||
|
return option?.label || value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return `[${value.length} элементов]`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
return '[Объект]'
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatJsonDisplay = (value: any): string => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value, null, 2)
|
||||||
|
} catch {
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDescriptors = async () => {
|
||||||
|
// Import app store dynamically to avoid circular dependency
|
||||||
|
const { useAppStore } = await import('@/stores/app')
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
if (!appStore.api) {
|
||||||
|
error.value = 'API недоступен'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
descriptors.value = await appStore.api.Properties.getAll()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading property descriptors:', err)
|
||||||
|
error.value = 'Не удалось загрузить дескрипторы свойств'
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadDescriptors()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.property-editor {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
<template>
|
||||||
|
<div class="property-widget">
|
||||||
|
<!-- Boolean type - checkbox -->
|
||||||
|
<v-checkbox
|
||||||
|
v-if="descriptor?.propertyType === 'Boolean'"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Array type - multiple widgets -->
|
||||||
|
<div v-else-if="descriptor?.array">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in modelValue || []"
|
||||||
|
:key="String(index)"
|
||||||
|
class="d-flex align-center mb-2"
|
||||||
|
>
|
||||||
|
<PropertyWidget
|
||||||
|
:model-value="item"
|
||||||
|
:descriptor="{ ...descriptor, array: false }"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="flex-grow-1"
|
||||||
|
@update:model-value="updateArrayItem(Number(index), $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column ml-2">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-chevron-up"
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
:disabled="disabled || index === 0"
|
||||||
|
@click="moveUp(Number(index))"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-chevron-down"
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
:disabled="disabled || index === (modelValue?.length || 0) - 1"
|
||||||
|
@click="moveDown(Number(index))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
size="x-small"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="ml-1"
|
||||||
|
@click="removeItem(Number(index))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="addItem"
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text type -->
|
||||||
|
<v-textarea
|
||||||
|
v-if="descriptor?.propertyType === 'Text' && stringConstraint?.multiline"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
:rules="textValidationRules"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
:maxlength="stringConstraint?.maxLength"
|
||||||
|
rows="3"
|
||||||
|
auto-grow
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-else-if="descriptor?.propertyType === 'Text'"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
:rules="textValidationRules"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
:maxlength="stringConstraint?.maxLength"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Number type -->
|
||||||
|
<div v-else-if="descriptor?.propertyType === 'Number'">
|
||||||
|
<!-- Range slider for numbers with step > 0 -->
|
||||||
|
<v-slider
|
||||||
|
v-if="useSlider"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
:min="numberConstraint?.min"
|
||||||
|
:max="numberConstraint?.max"
|
||||||
|
:step="numberConstraint?.step"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<v-text-field
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
style="width: 100px"
|
||||||
|
type="number"
|
||||||
|
:min="numberConstraint?.min"
|
||||||
|
:max="numberConstraint?.max"
|
||||||
|
:step="numberConstraint?.step"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-slider>
|
||||||
|
|
||||||
|
<!-- Text field for other numbers -->
|
||||||
|
<v-text-field
|
||||||
|
v-else
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
:min="numberConstraint?.min"
|
||||||
|
:max="numberConstraint?.max"
|
||||||
|
:step="numberConstraint?.step || (numberConstraint?.integer ? 1 : 0.1)"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ValueList type - dropdown -->
|
||||||
|
<v-select
|
||||||
|
v-else-if="descriptor?.propertyType === 'ValueList'"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
:items="valueListConstraint?.values || []"
|
||||||
|
item-title="label"
|
||||||
|
item-value="value"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- JSON type - textarea -->
|
||||||
|
<v-textarea
|
||||||
|
v-else-if="descriptor?.propertyType === 'JSON'"
|
||||||
|
:model-value="
|
||||||
|
typeof modelValue === 'object' ? JSON.stringify(modelValue, null, 2) : modelValue
|
||||||
|
"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
rows="5"
|
||||||
|
@update:model-value="updateJsonValue"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Fallback for unknown types -->
|
||||||
|
<v-text-field
|
||||||
|
v-else
|
||||||
|
:model-value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Unknown type"
|
||||||
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type {
|
||||||
|
PropertyDescriptor,
|
||||||
|
NumberConstraint,
|
||||||
|
ValueListConstraint,
|
||||||
|
StringConstraint
|
||||||
|
} from '@/generated/RpcClient'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: any
|
||||||
|
descriptor?: PropertyDescriptor
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: any): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const stringConstraint = computed((): StringConstraint | undefined => {
|
||||||
|
return props.descriptor?.constraints?.find(
|
||||||
|
(c) => c.type === 'StringConstraint'
|
||||||
|
) as StringConstraint
|
||||||
|
})
|
||||||
|
|
||||||
|
const numberConstraint = computed((): NumberConstraint | undefined => {
|
||||||
|
return props.descriptor?.constraints?.find(
|
||||||
|
(c) => c.type === 'NumberConstraint'
|
||||||
|
) as NumberConstraint
|
||||||
|
})
|
||||||
|
|
||||||
|
const valueListConstraint = computed((): ValueListConstraint | undefined => {
|
||||||
|
return props.descriptor?.constraints?.find(
|
||||||
|
(c) => c.type === 'ValueListConstraint'
|
||||||
|
) as ValueListConstraint
|
||||||
|
})
|
||||||
|
|
||||||
|
const useSlider = computed((): boolean => {
|
||||||
|
const constraint = numberConstraint.value
|
||||||
|
return constraint ? constraint.step > 0 : false
|
||||||
|
})
|
||||||
|
|
||||||
|
const textValidationRules = computed(() => {
|
||||||
|
const rules: any[] = []
|
||||||
|
|
||||||
|
if (stringConstraint.value?.regexp) {
|
||||||
|
const regex = new RegExp(stringConstraint.value.regexp)
|
||||||
|
rules.push((value: string) => {
|
||||||
|
if (!value) return true // Allow empty values
|
||||||
|
return regex.test(value) || 'Значение не соответствует требуемому формату'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateJsonValue = (newValue: string) => {
|
||||||
|
try {
|
||||||
|
if (!newValue.trim()) {
|
||||||
|
emit('update:modelValue', null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(newValue)
|
||||||
|
emit('update:modelValue', parsed)
|
||||||
|
} catch (e) {
|
||||||
|
// Keep invalid JSON as string for now
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateArrayItem = (index: number, newValue: any) => {
|
||||||
|
const newArray = [...(props.modelValue || [])]
|
||||||
|
newArray[index] = newValue
|
||||||
|
emit('update:modelValue', newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
const newArray = [...(props.modelValue || [])]
|
||||||
|
|
||||||
|
// Determine default value based on property type
|
||||||
|
let defaultValue: any = ''
|
||||||
|
if (props.descriptor?.propertyType === 'Boolean') {
|
||||||
|
defaultValue = false
|
||||||
|
} else if (props.descriptor?.propertyType === 'Number') {
|
||||||
|
defaultValue = 0
|
||||||
|
} else if (props.descriptor?.propertyType === 'JSON') {
|
||||||
|
defaultValue = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
newArray.push(defaultValue)
|
||||||
|
emit('update:modelValue', newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = (index: number) => {
|
||||||
|
const newArray = [...(props.modelValue || [])]
|
||||||
|
newArray.splice(index, 1)
|
||||||
|
emit('update:modelValue', newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveUp = (index: number) => {
|
||||||
|
if (index === 0) return
|
||||||
|
const newArray = [...(props.modelValue || [])]
|
||||||
|
const temp = newArray[index]
|
||||||
|
newArray[index] = newArray[index - 1]
|
||||||
|
newArray[index - 1] = temp
|
||||||
|
emit('update:modelValue', newArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveDown = (index: number) => {
|
||||||
|
const array = props.modelValue || []
|
||||||
|
if (index === array.length - 1) return
|
||||||
|
const newArray = [...array]
|
||||||
|
const temp = newArray[index]
|
||||||
|
newArray[index] = newArray[index + 1]
|
||||||
|
newArray[index + 1] = temp
|
||||||
|
emit('update:modelValue', newArray)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.property-widget {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -128,34 +128,10 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- Карта с дополнительной информацией -->
|
<!-- Редактор свойств пользователя -->
|
||||||
<v-card elevation="2" class="mt-4">
|
<v-card elevation="2" class="mt-4">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-list density="compact">
|
<property-editor v-model="userValues" :disabled="!isEditing" />
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon>mdi-fingerprint</v-icon>
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>ID пользователя</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>{{ currentUser?.id }}</v-list-item-subtitle>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon>mdi-identifier</v-icon>
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>UUID</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>{{ currentUser?.uuid }}</v-list-item-subtitle>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon>mdi-shield-account</v-icon>
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>Роль</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>{{ userRole }}</v-list-item-subtitle>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
@ -168,6 +144,7 @@
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import { useNotificationStore } from '@/stores/notification'
|
import { useNotificationStore } from '@/stores/notification'
|
||||||
|
import PropertyEditor from '@/components/PropertyEditor.vue'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { showSuccess, showError } = useNotificationStore()
|
const { showSuccess, showError } = useNotificationStore()
|
||||||
|
|
@ -199,6 +176,9 @@ const profileData = ref({
|
||||||
confirmPassword: ''
|
confirmPassword: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Свойства пользователя для редактора
|
||||||
|
const userValues = ref<Record<string, any>>({})
|
||||||
|
|
||||||
const originalData = ref({
|
const originalData = ref({
|
||||||
login: '',
|
login: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -227,7 +207,8 @@ const passwordRules = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const confirmPasswordRules = [
|
const confirmPasswordRules = [
|
||||||
(v: string) => !profileData.newPassword || v === profileData.newPassword || 'Пароли не совпадают'
|
(v: string) =>
|
||||||
|
!profileData.value.newPassword || v === profileData.value.newPassword || 'Пароли не совпадают'
|
||||||
]
|
]
|
||||||
|
|
||||||
const getUserDisplayName = () => {
|
const getUserDisplayName = () => {
|
||||||
|
|
@ -239,10 +220,7 @@ const getUserDisplayName = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const startEditing = () => {
|
const startEditing = () => {
|
||||||
originalData.value = {
|
originalData.value = { ...profileData.value }
|
||||||
login: profileData.value.login,
|
|
||||||
name: profileData.value.name
|
|
||||||
}
|
|
||||||
isEditing.value = true
|
isEditing.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +250,7 @@ const saveProfile = async () => {
|
||||||
const success = await appStore.api.Profile.save(
|
const success = await appStore.api.Profile.save(
|
||||||
profileData.value.login,
|
profileData.value.login,
|
||||||
profileData.value.name,
|
profileData.value.name,
|
||||||
currentUser.value?.values || {},
|
userValues.value,
|
||||||
password,
|
password,
|
||||||
profileData.value.currentPassword
|
profileData.value.currentPassword
|
||||||
)
|
)
|
||||||
|
|
@ -312,6 +290,7 @@ const loadProfileData = () => {
|
||||||
confirmPassword: ''
|
confirmPassword: ''
|
||||||
}
|
}
|
||||||
originalData.value = { ...profileData.value }
|
originalData.value = { ...profileData.value }
|
||||||
|
userValues.value = user.values || {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue