😎1、优化跨租户文件上传用户所属问题 2、优化条件必填参数验证特性 3、优化前端下来列表和字典组件 4、优化表单自定义验证规则

This commit is contained in:
zuohuaijun 2025-09-28 12:34:41 +08:00
parent be7cd177d1
commit c92881a1ef
10 changed files with 203 additions and 125 deletions

View File

@ -52,6 +52,10 @@ public sealed class RequiredIFAttribute(
/// </summary> /// </summary>
private Operator Comparison { get; set; } = comparison; private Operator Comparison { get; set; } = comparison;
public RequiredIFAttribute(string propertyName, object[] targetValues, Operator comparison = Operator.Equal) : this(propertyName, targetValues as object, comparison)
{
}
/// <summary> /// <summary>
/// 验证属性值是否符合要求 /// 验证属性值是否符合要求
/// </summary> /// </summary>

View File

@ -71,6 +71,11 @@ public class UploadFileInput
/// 业务数据Id /// 业务数据Id
/// </summary> /// </summary>
public long DataId { get; set; } public long DataId { get; set; }
/// <summary>
/// 上传用户Id解决跨租户上传时用户所属不一致问题
/// </summary>
public long UserId { get; set; }
} }
/// <summary> /// <summary>
@ -115,4 +120,9 @@ public class UploadFileFromBase64Input
/// 业务Id /// 业务Id
/// </summary> /// </summary>
public long? DataId { get; set; } public long? DataId { get; set; }
/// <summary>
/// 上传用户Id解决跨租户上传时用户所属不一致问题
/// </summary>
public long UserId { get; set; }
} }

View File

@ -174,12 +174,15 @@ public class SysFileService : IDynamicApiController, ITransient
/// <summary> /// <summary>
/// 下载指定文件Base64格式 🔖 /// 下载指定文件Base64格式 🔖
/// </summary> /// </summary>
/// <param name="id"></param>
/// <param name="url"></param> /// <param name="url"></param>
/// <returns></returns> /// <returns></returns>
[DisplayName("下载指定文件Base64格式")] [DisplayName("下载指定文件Base64格式")]
public async Task<string> DownloadFileBase64([FromBody] string url) public async Task<string> GetFileBase64([FromQuery] long id, [FromQuery] string url)
{ {
var sysFile = await _sysFileRep.AsQueryable().ClearFilter<ITenantIdFilter>().FirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); var sysFile = await _sysFileRep.AsQueryable().ClearFilter<ITenantIdFilter>()
.WhereIF(id > 0, u => u.Id == id)
.WhereIF(!string.IsNullOrWhiteSpace(url), u => u.Url == url).FirstAsync() ?? throw Oops.Oh($"文件不存在");
return await _customFileProvider.DownloadFileBase64Async(sysFile); return await _customFileProvider.DownloadFileBase64Async(sysFile);
} }
@ -322,8 +325,21 @@ public class SysFileService : IDynamicApiController, ITransient
newFile.FilePath = path; newFile.FilePath = path;
newFile.FileMd5 = fileMd5; newFile.FileMd5 = fileMd5;
var finalName = newFile.Id + suffix; // 文件最终名称 // 解决跨租户上传时用户所属不一致问题
if (input.UserId > 0)
{
var user = await _sysFileRep.Context.Queryable<SysUser>().ClearFilter<ITenantIdFilter>().FirstAsync(u => u.Id == input.UserId);
if (user != null)
{
newFile.CreateUserId = user.Id;
newFile.CreateUserName = user.RealName;
newFile.CreateOrgId = user.OrgId;
newFile.CreateOrgName = user.SysOrg?.Name;
newFile.TenantId = user.TenantId;
}
}
var finalName = newFile.Id + suffix; // 文件最终名称
newFile = await _customFileProvider.UploadFileAsync(input.File, newFile, path, finalName); newFile = await _customFileProvider.UploadFileAsync(input.File, newFile, path, finalName);
await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync(); await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync();
return newFile; return newFile;

View File

@ -83,13 +83,13 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
}, },
/** /**
* *
* @summary Base64格式 🔖 * @summary Id或Url下载 🔖
* @param {string} [body] * @param {SysFile} [body]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
apiSysFileDownloadFileBase64Post: async (body?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { apiSysFileDownloadFilePost: async (body?: SysFile, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysFile/downloadFileBase64`; const localVarPath = `/api/sysFile/downloadFile`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com'); const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions; let baseOptions;
@ -131,20 +131,21 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
}, },
/** /**
* *
* @summary Id或Url下载 🔖 * @summary Base64格式 🔖
* @param {SysFile} [body] * @param {number} [id]
* @param {string} [url]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
apiSysFileDownloadFilePost: async (body?: SysFile, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { apiSysFileFileBase64Get: async (id?: number, url?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysFile/downloadFile`; const localVarPath = `/api/sysFile/fileBase64`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com'); const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions; let baseOptions;
if (configuration) { if (configuration) {
baseOptions = configuration.baseOptions; baseOptions = configuration.baseOptions;
} }
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options}; const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any; const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any; const localVarQueryParameter = {} as any;
@ -157,7 +158,13 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken; localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
} }
localVarHeaderParameter['Content-Type'] = 'application/json-patch+json'; if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
if (url !== undefined) {
localVarQueryParameter['url'] = url;
}
const query = new URLSearchParams(localVarUrlObj.search); const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) { for (const key in localVarQueryParameter) {
@ -169,8 +176,6 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
localVarUrlObj.search = (new URLSearchParams(query)).toString(); localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return { return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
@ -634,10 +639,11 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
* @param {boolean} [isPublic] * @param {boolean} [isPublic]
* @param {string} [allowSuffix] * @param {string} [allowSuffix]
* @param {number} [dataId] * @param {number} [dataId]
* @param {number} [userId]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
apiSysFileUploadFilePostForm: async (file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { apiSysFileUploadFilePostForm: async (file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysFile/uploadFile`; const localVarPath = `/api/sysFile/uploadFile`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com'); const localVarUrlObj = new URL(localVarPath, 'https://example.com');
@ -684,6 +690,10 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati
localVarFormParams.append('DataId', dataId as any); localVarFormParams.append('DataId', dataId as any);
} }
if (userId !== undefined) {
localVarFormParams.append('UserId', userId as any);
}
localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
const query = new URLSearchParams(localVarUrlObj.search); const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) { for (const key in localVarQueryParameter) {
@ -941,20 +951,6 @@ export const SysFileApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs); return axios.request(axiosRequestArgs);
}; };
}, },
/**
*
* @summary Base64格式 🔖
* @param {string} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysFileDownloadFileBase64Post(body?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultString>>> {
const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileDownloadFileBase64Post(body, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
/** /**
* *
* @summary Id或Url下载 🔖 * @summary Id或Url下载 🔖
@ -969,6 +965,21 @@ export const SysFileApiFp = function(configuration?: Configuration) {
return axios.request(axiosRequestArgs); return axios.request(axiosRequestArgs);
}; };
}, },
/**
*
* @summary Base64格式 🔖
* @param {number} [id]
* @param {string} [url]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysFileFileBase64Get(id?: number, url?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultString>>> {
const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileFileBase64Get(id, url, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
/** /**
* *
* @summary Id集合获取文件 🔖 * @summary Id集合获取文件 🔖
@ -1105,11 +1116,12 @@ export const SysFileApiFp = function(configuration?: Configuration) {
* @param {boolean} [isPublic] * @param {boolean} [isPublic]
* @param {string} [allowSuffix] * @param {string} [allowSuffix]
* @param {number} [dataId] * @param {number} [dataId]
* @param {number} [userId]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultSysFile>>> { async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultSysFile>>> {
const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options); const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, userId, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs); return axios.request(axiosRequestArgs);
@ -1192,16 +1204,6 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa
async apiSysFileDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> { async apiSysFileDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysFileApiFp(configuration).apiSysFileDeletePost(body, options).then((request) => request(axios, basePath)); return SysFileApiFp(configuration).apiSysFileDeletePost(body, options).then((request) => request(axios, basePath));
}, },
/**
*
* @summary Base64格式 🔖
* @param {string} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysFileDownloadFileBase64Post(body?: string, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultString>> {
return SysFileApiFp(configuration).apiSysFileDownloadFileBase64Post(body, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @summary Id或Url下载 🔖 * @summary Id或Url下载 🔖
@ -1212,6 +1214,17 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa
async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultIActionResult>> { async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultIActionResult>> {
return SysFileApiFp(configuration).apiSysFileDownloadFilePost(body, options).then((request) => request(axios, basePath)); return SysFileApiFp(configuration).apiSysFileDownloadFilePost(body, options).then((request) => request(axios, basePath));
}, },
/**
*
* @summary Base64格式 🔖
* @param {number} [id]
* @param {string} [url]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysFileFileBase64Get(id?: number, url?: string, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultString>> {
return SysFileApiFp(configuration).apiSysFileFileBase64Get(id, url, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @summary Id集合获取文件 🔖 * @summary Id集合获取文件 🔖
@ -1312,11 +1325,12 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa
* @param {boolean} [isPublic] * @param {boolean} [isPublic]
* @param {string} [allowSuffix] * @param {string} [allowSuffix]
* @param {number} [dataId] * @param {number} [dataId]
* @param {number} [userId]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSysFile>> { async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSysFile>> {
return SysFileApiFp(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options).then((request) => request(axios, basePath)); return SysFileApiFp(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, userId, options).then((request) => request(axios, basePath));
}, },
/** /**
* *
@ -1381,17 +1395,6 @@ export class SysFileApi extends BaseAPI {
public async apiSysFileDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> { public async apiSysFileDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysFileApiFp(this.configuration).apiSysFileDeletePost(body, options).then((request) => request(this.axios, this.basePath)); return SysFileApiFp(this.configuration).apiSysFileDeletePost(body, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @summary Base64格式 🔖
* @param {string} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysFileApi
*/
public async apiSysFileDownloadFileBase64Post(body?: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultString>> {
return SysFileApiFp(this.configuration).apiSysFileDownloadFileBase64Post(body, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @summary Id或Url下载 🔖 * @summary Id或Url下载 🔖
@ -1403,6 +1406,18 @@ export class SysFileApi extends BaseAPI {
public async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultIActionResult>> { public async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultIActionResult>> {
return SysFileApiFp(this.configuration).apiSysFileDownloadFilePost(body, options).then((request) => request(this.axios, this.basePath)); return SysFileApiFp(this.configuration).apiSysFileDownloadFilePost(body, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @summary Base64格式 🔖
* @param {number} [id]
* @param {string} [url]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysFileApi
*/
public async apiSysFileFileBase64Get(id?: number, url?: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultString>> {
return SysFileApiFp(this.configuration).apiSysFileFileBase64Get(id, url, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @summary Id集合获取文件 🔖 * @summary Id集合获取文件 🔖
@ -1512,12 +1527,13 @@ export class SysFileApi extends BaseAPI {
* @param {boolean} [isPublic] * @param {boolean} [isPublic]
* @param {string} [allowSuffix] * @param {string} [allowSuffix]
* @param {number} [dataId] * @param {number} [dataId]
* @param {number} [userId]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
* @memberof SysFileApi * @memberof SysFileApi
*/ */
public async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSysFile>> { public async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSysFile>> {
return SysFileApiFp(this.configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options).then((request) => request(this.axios, this.basePath)); return SysFileApiFp(this.configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, userId, options).then((request) => request(this.axios, this.basePath));
} }
/** /**
* *

View File

@ -67,4 +67,12 @@ export interface SysFileUploadFileBody {
* @memberof SysFileUploadFileBody * @memberof SysFileUploadFileBody
*/ */
dataId?: number; dataId?: number;
/**
* Id
*
* @type {number}
* @memberof SysFileUploadFileBody
*/
userId?: number;
} }

View File

@ -75,4 +75,12 @@ export interface UploadFileFromBase64Input {
* @memberof UploadFileFromBase64Input * @memberof UploadFileFromBase64Input
*/ */
dataId?: number | null; dataId?: number | null;
/**
* Id
*
* @type {number}
* @memberof UploadFileFromBase64Input
*/
userId?: number;
} }

View File

@ -292,6 +292,8 @@ const handleChange = (row: any) => {
// //
const selectVisibleChange = (visible: boolean) => { const selectVisibleChange = (visible: boolean) => {
if (visible) { if (visible) {
state.tableData.items = [];
state.tableData.total = 0;
state.tableQuery[props.keywordProp] = undefined; state.tableQuery[props.keywordProp] = undefined;
handleQuery(); handleQuery();
} }

View File

@ -290,26 +290,30 @@ const formattedDictData = computed(() => {
* @computed * @computed
* @returns {DictItem|DictItem[]|null} - 当前选中的字典项或字典项数组 * @returns {DictItem|DictItem[]|null} - 当前选中的字典项或字典项数组
*/ */
const currentDictItems = computed(() => { const currentDictItems = computed(() => {
// 0 // 0
const isEmpty = (val: any) => const isEmpty = (val: any) => val === null || val === undefined || (Array.isArray(val) && val.length === 0) || (typeof val === 'string' && val.trim() === '');
val === null ||
val === undefined ||
(Array.isArray(val) && val.length === 0) ||
(typeof val === 'string' && val.trim() === '');
if (isEmpty(state.value)) return null; if (isEmpty(state.value)) return null;
if (Array.isArray(state.value)) { let values: any[] = [];
// /
const uniqueValues = [...new Set(state.value)];
return formattedDictData.value.filter(item =>
uniqueValues.some(val => val == item.value)
);
}
// if (props.multiple) {
return formattedDictData.value.find(item => item.value == state.value) || null; if (Array.isArray(state.value)) {
values = state.value;
} else if (typeof state.value === 'string' && props.renderAs === 'tag') {
values = state.value.split(',').filter((v) => v !== '');
} else if (state.value !== undefined && state.value !== null && state.value !== '') {
values = [state.value];
}
console.log('[g-sys-dict] 解析多选值:', state.value, values);
//
const uniqueValues = [...new Set(values)];
return formattedDictData.value.filter((item) => uniqueValues.some((val) => val == item.value));
}
//
return formattedDictData.value.find((item) => item.value == state.value) || null;
}); });
/** /**
@ -376,6 +380,22 @@ const parseMultipleValue = (value: any): any => {
return props.multiple ? [] : value; return props.multiple ? [] : value;
} }
//
if (props.multiple && typeof value === 'string') {
if (value.trim().startsWith('[') && value.trim().endsWith(']')) {
try {
return JSON.parse(value.trim());
} catch {
return [];
}
}
// +
return value
.split(',')
.map((v) => v.trim())
.filter((v) => v !== '');
}
// //
if (typeof value === 'number' && !state.conversion) { if (typeof value === 'number' && !state.conversion) {
try { try {
@ -388,29 +408,6 @@ const parseMultipleValue = (value: any): any => {
} }
} }
//
if (typeof value === 'string') {
const trimmedValue = value.trim();
// JSON
if (trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) {
try {
return JSON.parse(trimmedValue);
} catch (error) {
console.warn('[g-sys-dict] 解析多选值失败:', error);
return [];
}
}
//
if (props.multipleModel === MultipleModel.Comma && trimmedValue.includes(',')) {
return trimmedValue.split(',');
}
//
return props.multiple ? [trimmedValue] : trimmedValue;
}
// //
return value; return value;
}; };
@ -477,36 +474,34 @@ const handleMutex = (newValue: any, mutexConfigs: MutexConfig[]): any => {
* @function * @function
* @param {any} newValue - 新值 * @param {any} newValue - 新值
*/ */
const updateValue = (newValue: any) => { const updateValue = (newValue: any) => {
// //
let processedValue = Array.isArray(newValue) ? newValue : (typeof newValue === 'string' && props.multipleModel === MultipleModel.Comma) let processedValue = Array.isArray(newValue) ? newValue : typeof newValue === 'string' && props.multipleModel === MultipleModel.Comma ? newValue.split(',').filter(Boolean) : newValue;
? newValue.split(',').filter(Boolean)
: newValue;
// //
if (props.mutexConfigs && props.mutexConfigs.length > 0) { if (props.mutexConfigs && props.mutexConfigs.length > 0) {
processedValue = handleMutex(processedValue, props.mutexConfigs); processedValue = handleMutex(processedValue, props.mutexConfigs);
} }
let emitValue = processedValue; let emitValue = processedValue;
if (props.multipleModel === MultipleModel.Comma) { if (props.multipleModel === MultipleModel.Comma) {
if (Array.isArray(processedValue)) { if (Array.isArray(processedValue)) {
emitValue = processedValue.length > 0 ? processedValue.sort().join(',') : ''; emitValue = processedValue.length > 0 ? processedValue.sort().join(',') : '';
} else if (processedValue === null || processedValue === undefined) { } else if (processedValue === null || processedValue === undefined) {
emitValue = undefined; emitValue = undefined;
} }
} else { } else {
if (Array.isArray(processedValue)) { if (Array.isArray(processedValue)) {
emitValue = processedValue.length > 0 ? processedValue.sort() : []; emitValue = processedValue.length > 0 ? processedValue.sort() : [];
} else if (processedValue === null || processedValue === undefined) { } else if (processedValue === null || processedValue === undefined) {
emitValue = undefined; emitValue = undefined;
} }
} }
console.log('[g-sys-dict] 更新值:', { newValue, processedValue, emitValue }); console.log('[g-sys-dict] 更新值:', { newValue, processedValue, emitValue });
state.value = processedValue; state.value = processedValue;
emit('update:modelValue', emitValue === '' || emitValue?.length === 0 ? undefined : emitValue); emit('update:modelValue', emitValue === '' || emitValue?.length === 0 ? undefined : emitValue);
emit('change', state.value, currentDictItems, state.dictData); emit('change', state.value, currentDictItems, state.dictData);
}; };
/** /**

View File

@ -135,3 +135,10 @@
.vxe-modal--close-btn:hover { .vxe-modal--close-btn:hover {
color: red !important; color: red !important;
} }
// 解决el-dialog和vxe-tooltip弹出层z-index冲突问题
.vxe-modal--wrapper,
.vxe-tooltip--wrapper,
.vxe-table--filter-wrapper{
z-index: 2023 !important;
}

View File

@ -31,7 +31,7 @@ class ValidationBuilder {
constructor(config: ValidatorConfig) { constructor(config: ValidatorConfig) {
this.prop = config.prop; this.prop = config.prop;
this.label = config.label || config.prop; this.label = config.label || '此项';
this.customMessages = config.customMessages || {}; this.customMessages = config.customMessages || {};
this.defaultTrigger = config.trigger || 'blur'; // 设置默认触发方式 this.defaultTrigger = config.trigger || 'blur'; // 设置默认触发方式
} }
@ -75,6 +75,18 @@ class ValidationBuilder {
return this; return this;
} }
// 条件必填验证
requiredIf(required?: boolean, message?: string): ValidationBuilder {
const defaultMessage = `${this.label}不能为空`;
const rule: ValidationRule = {
type: 'required', // 添加 type 字段
message: message || this.getMessage('required', defaultMessage),
required: required,
};
this.rules.push(rule);
return this;
}
// 最小长度验证 // 最小长度验证
min(len: number, message?: string): ValidationBuilder { min(len: number, message?: string): ValidationBuilder {
const defaultMessage = `${this.label}最少输入${len}个字符`; const defaultMessage = `${this.label}最少输入${len}个字符`;