😎1、优化条件必填参数验证特性 2、优化字典和下拉框组件 3、优化远程日志页面
This commit is contained in:
parent
83ca39fd9f
commit
801a31dfba
@ -64,11 +64,13 @@ public sealed class RequiredIFAttribute(
|
||||
|
||||
var instance = validationContext.ObjectInstance;
|
||||
var targetProperty = instance.GetType().GetProperty(PropertyName);
|
||||
// 判断校验字段内容是否为字典
|
||||
var dictAttr = targetProperty?.GetCustomAttribute<DictAttribute>();
|
||||
|
||||
if (targetProperty == null) return new ValidationResult($"找不到属性: {PropertyName}");
|
||||
var targetValue = targetProperty.GetValue(instance);
|
||||
|
||||
if (!ShouldValidate(targetValue)) return ValidationResult.Success;
|
||||
if (!ShouldValidate(targetValue, dictAttr)) return ValidationResult.Success;
|
||||
|
||||
return IsEmpty(value) ? new ValidationResult(ErrorMessage ?? $"{validationContext.MemberName}不能为空") : ValidationResult.Success;
|
||||
}
|
||||
@ -78,7 +80,7 @@ public sealed class RequiredIFAttribute(
|
||||
/// </summary>
|
||||
/// <param name="targetValue">依赖属性的值</param>
|
||||
/// <returns>是否需要验证</returns>
|
||||
private bool ShouldValidate(object targetValue)
|
||||
private bool ShouldValidate(object targetValue, DictAttribute dictAttr)
|
||||
{
|
||||
switch (Comparison)
|
||||
{
|
||||
@ -94,8 +96,26 @@ public sealed class RequiredIFAttribute(
|
||||
case Operator.LessThanOrEqual:
|
||||
case Operator.Contains:
|
||||
case Operator.NotContains:
|
||||
if (targetValue is IEnumerable enumerable) return enumerable.Cast<object>().Any(item => CompareValues(item, TargetValue, Comparison));
|
||||
return TargetValue == null ? !IsEmpty(targetValue) : CompareValues(targetValue, TargetValue, Comparison);
|
||||
// 多选字典
|
||||
if (dictAttr != null && targetValue is string targetString && targetString.Contains(','))
|
||||
{
|
||||
var values = targetString.Split(',')
|
||||
.Select(v => v.Trim())
|
||||
.Where(v => !string.IsNullOrEmpty(v))
|
||||
.ToArray();
|
||||
|
||||
return values.Any(value => CompareValues(value, TargetValue, Comparison));
|
||||
}
|
||||
// 处理其他集合情况
|
||||
else if (targetValue is IEnumerable enumerable && !(targetValue is string))
|
||||
{
|
||||
return enumerable.Cast<object>().Any(item => CompareValues(item, TargetValue, Comparison));
|
||||
}
|
||||
// 单选字典
|
||||
else
|
||||
{
|
||||
return TargetValue == null ? !IsEmpty(targetValue) : CompareValues(targetValue, TargetValue, Comparison);
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
@ -137,9 +157,28 @@ public sealed class RequiredIFAttribute(
|
||||
|
||||
case Operator.Contains:
|
||||
case Operator.NotContains:
|
||||
if (targetValue is not IEnumerable enumerable) return false;
|
||||
bool contains = enumerable.Cast<object>().Any(item => item != null && item.Equals(sourceValue));
|
||||
return comparison == Operator.Contains ? contains : !contains;
|
||||
if (targetValue == null) return false;
|
||||
if (sourceValue == null) return comparison == Operator.NotContains;
|
||||
|
||||
// 多选字典
|
||||
if (targetValue is string targetString)
|
||||
{
|
||||
string sourceString = sourceValue.ToString();
|
||||
bool stringContains = targetString.Equals(sourceString);
|
||||
return comparison == Operator.Contains ? stringContains : !stringContains;
|
||||
}
|
||||
// 其他集合类型处理
|
||||
else if (targetValue is IEnumerable enumerable && !(targetValue is string))
|
||||
{
|
||||
bool contains = enumerable.OfType<object>().Any(item =>
|
||||
item != null && item.Equals(sourceValue));
|
||||
return comparison == Operator.Contains ? contains : !contains;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool valuesEqual = targetValue.Equals(sourceValue);
|
||||
return comparison == Operator.Contains ? valuesEqual : !valuesEqual;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11012.119 d18.0
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36511.14 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Application", "Admin.NET.Application\Admin.NET.Application.csproj", "{C3F5AEC5-ACEE-4109-94E3-3F981DC18268}"
|
||||
EndProject
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.14.0" />
|
||||
<PackageReference Include="Rezero.Api" Version="1.8.26" />
|
||||
<PackageReference Include="Rezero.Api" Version="1.8.27" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "admin.net.pro",
|
||||
"type": "module",
|
||||
"version": "2.4.33",
|
||||
"lastBuildTime": "2025.09.22",
|
||||
"lastBuildTime": "2025.09.23",
|
||||
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
|
||||
"author": "zuohuaijun",
|
||||
"license": "MIT",
|
||||
@ -93,8 +93,8 @@
|
||||
"@types/node": "^22.18.6",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.0",
|
||||
"@typescript-eslint/parser": "^8.44.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||
"@typescript-eslint/parser": "^8.44.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"@vue/compiler-sfc": "^3.5.21",
|
||||
@ -103,15 +103,15 @@
|
||||
"colors": "^1.4.0",
|
||||
"dotenv": "^17.2.1",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"eslint-plugin-vue": "^10.5.0",
|
||||
"globals": "^16.4.0",
|
||||
"less": "^4.4.1",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"sass": "^1.93.0",
|
||||
"sass": "^1.93.1",
|
||||
"terser": "^5.44.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.6",
|
||||
"vite": "^7.1.7",
|
||||
"vite-auto-i18n-plugin": "^1.1.9",
|
||||
"vite-plugin-cdn-import": "^1.0.1",
|
||||
"vite-plugin-compression2": "^2.2.1",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<!-- 下拉选择组件,支持远程搜索、分页、自定义查询表单等功能 -->
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { inject, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { debounce } from 'xe-utils';
|
||||
|
||||
const props = defineProps({
|
||||
@ -258,7 +258,7 @@ const remoteMethod = debounce((query: any) => {
|
||||
state.tableData.items = [];
|
||||
state.tableData.total = 0;
|
||||
}
|
||||
}, 300);
|
||||
}, 800);
|
||||
|
||||
const handleQuery = () => {
|
||||
remoteMethod(state.tableQuery);
|
||||
@ -278,12 +278,15 @@ const handleChange = (row: any) => {
|
||||
// 去重
|
||||
state.selectedValues = props.multiple ? Array.from(new Set([...state.selectedValues, row[props.valueProp]])) : row[props.valueProp];
|
||||
|
||||
// 设置表格选中效果,折叠选择器
|
||||
if (!props.multiple || state.selectedValues) selectRef.value?.blur();
|
||||
tableRef.value?.setCurrentRow(row);
|
||||
|
||||
// 通知父组件更新值
|
||||
emit('update:modelValue', state.selectedValues);
|
||||
emit('change', state.selectedValues, row);
|
||||
|
||||
// 主动失焦,触发表单校验
|
||||
selectRef.value?.blur();
|
||||
|
||||
// 设置表格选中效果,折叠选择器
|
||||
tableRef.value?.setCurrentRow(row);
|
||||
};
|
||||
|
||||
// 选择器下拉框显示隐藏事件
|
||||
@ -388,7 +391,7 @@ defineExpose({
|
||||
ref="tableRef"
|
||||
@row-click="handleChange"
|
||||
:data="state.tableData?.items ?? []"
|
||||
:height="`calc(${dropdownHeight} - 175px${$slots.queryForm ? ` - ${queryHeightOffset}px` : ''}${state.tableQuery[keywordProp] && allowCreate ? ` - ${queryHeightOffset}px` : ''})`"
|
||||
:height="`calc(${dropdownHeight} - 175px${$slots.queryForm ? ` - ${queryHeightOffset}px` : ''}${state.tableQuery[keywordProp]})`"
|
||||
highlight-current-row
|
||||
>
|
||||
<template #empty><el-empty :image-size="25" /></template>
|
||||
@ -412,8 +415,7 @@ defineExpose({
|
||||
</div>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped>
|
||||
.query-form {
|
||||
z-index: 9999;
|
||||
}
|
||||
@ -430,7 +432,8 @@ defineExpose({
|
||||
:deep(.popper-class) :deep(.el-select-dropdown__wrap) {
|
||||
max-height: 600px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.popper-class .el-select-dropdown__wrap {
|
||||
max-height: 450px !important;
|
||||
}
|
||||
|
||||
@ -589,11 +589,13 @@ watch(() => [userStore.dictList, userStore.constList, props.data, state], initDa
|
||||
<!-- 渲染选择器 -->
|
||||
<el-select v-else-if="props.renderAs === 'select'" v-model="state.value" v-bind="$attrs" :multiple="props.multiple" @change="updateValue" filterable allow-create default-first-option clearable>
|
||||
<el-option v-for="(item, index) in formattedDictData" :key="index" :label="getDisplayText(item)" :value="item.value" :disabled="item.disabled" />
|
||||
<slot />
|
||||
</el-select>
|
||||
|
||||
<!-- 多选框(多选) -->
|
||||
<el-checkbox-group v-else-if="props.renderAs === 'checkbox'" v-model="state.value" v-bind="$attrs" @change="updateValue">
|
||||
<el-checkbox v-for="(item, index) in formattedDictData" :key="index" :value="item.value" :label="getDisplayText(item)" :disabled="item.disabled" />
|
||||
<slot />
|
||||
</el-checkbox-group>
|
||||
|
||||
<!-- 多选框-按钮(多选) -->
|
||||
@ -601,6 +603,7 @@ watch(() => [userStore.dictList, userStore.constList, props.data, state], initDa
|
||||
<el-checkbox-button v-for="(item, index) in formattedDictData" :key="index" :value="item.value" :disabled="item.disabled">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-checkbox-button>
|
||||
<slot />
|
||||
</el-checkbox-group>
|
||||
|
||||
<!-- 渲染单选框 -->
|
||||
@ -608,6 +611,7 @@ watch(() => [userStore.dictList, userStore.constList, props.data, state], initDa
|
||||
<el-radio v-for="(item, index) in formattedDictData" :key="index" :value="item.value">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-radio>
|
||||
<slot />
|
||||
</el-radio-group>
|
||||
|
||||
<!-- 渲染单选框按钮 -->
|
||||
@ -615,6 +619,7 @@ watch(() => [userStore.dictList, userStore.constList, props.data, state], initDa
|
||||
<el-radio-button v-for="(item, index) in formattedDictData" :key="index" :value="item.value">
|
||||
{{ getDisplayText(item) }}
|
||||
</el-radio-button>
|
||||
<slot />
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="请求头" class-name="row-line">
|
||||
<vue-json-pretty :data="data.requestHeaders" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.requestHeaders))" v-if="data.requestHeaders" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="请求参数" class-name="row-line" v-if="data.requestUrl?.indexOf('?') != -1">
|
||||
<el-row v-for="(value, key, index) in queryObject">
|
||||
@ -44,22 +45,28 @@
|
||||
</el-row>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="请求体" class-name="row-line">
|
||||
<vue-json-pretty :data="data.requestBody" showLength showIcon showLineNumber showSelectController />
|
||||
<c :data="data.requestBody" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.requestBody))" v-if="data.requestBody" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="请求明文" class-name="row-line" v-if="data.requestBodyPlaintext">
|
||||
<vue-json-pretty :data="data.requestBodyPlaintext" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.requestBodyPlaintext))" v-if="data.requestBodyPlaintext" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="响应头" class-name="row-line">
|
||||
<vue-json-pretty :data="data.responseHeaders" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.responseHeaders))" v-if="data.responseHeaders" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="响应体" class-name="row-line">
|
||||
<vue-json-pretty :data="data.responseBody" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.responseBody))" v-if="data.responseBody" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="响应明文" class-name="row-line" v-if="data.responseBodyPlaintext">
|
||||
<vue-json-pretty :data="data.responseBodyPlaintext" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.responseBodyPlaintext))" v-if="data.responseBodyPlaintext" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="异常信息" class-name="row-line" v-if="data.exception">
|
||||
<vue-json-pretty :data="data.exception" showLength showIcon showLineNumber showSelectController />
|
||||
<el-button @click="comFunc.copyText(JSON.stringify(data.exception))" v-if="data.exception" style="float: right" icon="ele-CopyDocument" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form>
|
||||
@ -76,7 +83,9 @@ import { ref, reactive, computed } from 'vue';
|
||||
import { StringToObj } from '/@/utils/json-utils';
|
||||
import { SysLogHttp } from '/@/api-services/system/models';
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import commonFunction from '/@/utils/commonFunction';
|
||||
|
||||
const comFunc = commonFunction();
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user