diff --git a/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs index 23d1eaca..f86d5cda 100644 --- a/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs +++ b/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs @@ -52,6 +52,10 @@ public sealed class RequiredIFAttribute( /// private Operator Comparison { get; set; } = comparison; + public RequiredIFAttribute(string propertyName, object[] targetValues, Operator comparison = Operator.Equal) : this(propertyName, targetValues as object, comparison) + { + } + /// /// 验证属性值是否符合要求 /// diff --git a/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs index dd75b0be..8b48003d 100644 --- a/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/File/Dto/FileInput.cs @@ -71,6 +71,11 @@ public class UploadFileInput /// 业务数据Id /// public long DataId { get; set; } + + /// + /// 上传用户Id(解决跨租户上传时用户所属不一致问题) + /// + public long UserId { get; set; } } /// @@ -115,4 +120,9 @@ public class UploadFileFromBase64Input /// 业务Id /// public long? DataId { get; set; } + + /// + /// 上传用户Id(解决跨租户上传时用户所属不一致问题) + /// + public long UserId { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs index 5f2aae9e..b0a965f6 100644 --- a/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs +++ b/Admin.NET/Admin.NET.Core/Service/File/SysFileService.cs @@ -174,12 +174,15 @@ public class SysFileService : IDynamicApiController, ITransient /// /// 下载指定文件Base64格式 🔖 /// + /// /// /// [DisplayName("下载指定文件Base64格式")] - public async Task DownloadFileBase64([FromBody] string url) + public async Task GetFileBase64([FromQuery] long id, [FromQuery] string url) { - var sysFile = await _sysFileRep.AsQueryable().ClearFilter().FirstAsync(u => u.Url == url) ?? throw Oops.Oh($"文件不存在"); + var sysFile = await _sysFileRep.AsQueryable().ClearFilter() + .WhereIF(id > 0, u => u.Id == id) + .WhereIF(!string.IsNullOrWhiteSpace(url), u => u.Url == url).FirstAsync() ?? throw Oops.Oh($"文件不存在"); return await _customFileProvider.DownloadFileBase64Async(sysFile); } @@ -322,8 +325,21 @@ public class SysFileService : IDynamicApiController, ITransient newFile.FilePath = path; newFile.FileMd5 = fileMd5; - var finalName = newFile.Id + suffix; // 文件最终名称 + // 解决跨租户上传时用户所属不一致问题 + if (input.UserId > 0) + { + var user = await _sysFileRep.Context.Queryable().ClearFilter().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); await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync(); return newFile; diff --git a/Web/src/api-services/system/apis/sys-file-api.ts b/Web/src/api-services/system/apis/sys-file-api.ts index d4270adb..bf5880c6 100644 --- a/Web/src/api-services/system/apis/sys-file-api.ts +++ b/Web/src/api-services/system/apis/sys-file-api.ts @@ -83,13 +83,13 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati }, /** * - * @summary 下载指定文件Base64格式 🔖 - * @param {string} [body] + * @summary 根据文件Id或Url下载 🔖 + * @param {SysFile} [body] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysFileDownloadFileBase64Post: async (body?: string, options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysFile/downloadFileBase64`; + apiSysFileDownloadFilePost: async (body?: SysFile, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysFile/downloadFile`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); let baseOptions; @@ -131,20 +131,21 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati }, /** * - * @summary 根据文件Id或Url下载 🔖 - * @param {SysFile} [body] + * @summary 下载指定文件Base64格式 🔖 + * @param {number} [id] + * @param {string} [url] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysFileDownloadFilePost: async (body?: SysFile, options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/api/sysFile/downloadFile`; + apiSysFileFileBase64Get: async (id?: number, url?: string, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/sysFile/fileBase64`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); let baseOptions; if (configuration) { baseOptions = configuration.baseOptions; } - const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options}; + const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -157,7 +158,13 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati 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); for (const key in localVarQueryParameter) { @@ -169,8 +176,6 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati localVarUrlObj.search = (new URLSearchParams(query)).toString(); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.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 { url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash, @@ -634,10 +639,11 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati * @param {boolean} [isPublic] * @param {string} [allowSuffix] * @param {number} [dataId] + * @param {number} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - apiSysFileUploadFilePostForm: async (file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options: AxiosRequestConfig = {}): Promise => { + apiSysFileUploadFilePostForm: async (file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/api/sysFile/uploadFile`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, 'https://example.com'); @@ -684,6 +690,10 @@ export const SysFileApiAxiosParamCreator = function (configuration?: Configurati localVarFormParams.append('DataId', dataId as any); } + if (userId !== undefined) { + localVarFormParams.append('UserId', userId as any); + } + localVarHeaderParameter['Content-Type'] = 'multipart/form-data'; const query = new URLSearchParams(localVarUrlObj.search); for (const key in localVarQueryParameter) { @@ -941,20 +951,6 @@ export const SysFileApiFp = function(configuration?: Configuration) { 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>> { - 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下载 🔖 @@ -969,6 +965,21 @@ export const SysFileApiFp = function(configuration?: Configuration) { 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>> { + 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集合获取文件 🔖 @@ -1105,11 +1116,12 @@ export const SysFileApiFp = function(configuration?: Configuration) { * @param {boolean} [isPublic] * @param {string} [allowSuffix] * @param {number} [dataId] + * @param {number} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { - const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options); + async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise>> { + const localVarAxiosArgs = await SysFileApiAxiosParamCreator(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, userId, options); return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; return axios.request(axiosRequestArgs); @@ -1192,16 +1204,6 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa async apiSysFileDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig): Promise> { 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> { - return SysFileApiFp(configuration).apiSysFileDownloadFileBase64Post(body, options).then((request) => request(axios, basePath)); - }, /** * * @summary 根据文件Id或Url下载 🔖 @@ -1212,6 +1214,17 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig): Promise> { 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> { + return SysFileApiFp(configuration).apiSysFileFileBase64Get(id, url, options).then((request) => request(axios, basePath)); + }, /** * * @summary 根据文件Id集合获取文件 🔖 @@ -1312,11 +1325,12 @@ export const SysFileApiFactory = function (configuration?: Configuration, basePa * @param {boolean} [isPublic] * @param {string} [allowSuffix] * @param {number} [dataId] + * @param {number} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig): Promise> { - return SysFileApiFp(configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options).then((request) => request(axios, basePath)); + async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig): Promise> { + 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> { 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> { - return SysFileApiFp(this.configuration).apiSysFileDownloadFileBase64Post(body, options).then((request) => request(this.axios, this.basePath)); - } /** * * @summary 根据文件Id或Url下载 🔖 @@ -1403,6 +1406,18 @@ export class SysFileApi extends BaseAPI { public async apiSysFileDownloadFilePost(body?: SysFile, options?: AxiosRequestConfig) : Promise> { 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> { + return SysFileApiFp(this.configuration).apiSysFileFileBase64Get(id, url, options).then((request) => request(this.axios, this.basePath)); + } /** * * @summary 根据文件Id集合获取文件 🔖 @@ -1512,12 +1527,13 @@ export class SysFileApi extends BaseAPI { * @param {boolean} [isPublic] * @param {string} [allowSuffix] * @param {number} [dataId] + * @param {number} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SysFileApi */ - public async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, options?: AxiosRequestConfig) : Promise> { - return SysFileApiFp(this.configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, options).then((request) => request(this.axios, this.basePath)); + public async apiSysFileUploadFilePostForm(file?: Blob, fileType?: string, fileAlias?: string, isPublic?: boolean, allowSuffix?: string, dataId?: number, userId?: number, options?: AxiosRequestConfig) : Promise> { + return SysFileApiFp(this.configuration).apiSysFileUploadFilePostForm(file, fileType, fileAlias, isPublic, allowSuffix, dataId, userId, options).then((request) => request(this.axios, this.basePath)); } /** * diff --git a/Web/src/api-services/system/models/sys-file-upload-file-body.ts b/Web/src/api-services/system/models/sys-file-upload-file-body.ts index 81c34be5..f018f968 100644 --- a/Web/src/api-services/system/models/sys-file-upload-file-body.ts +++ b/Web/src/api-services/system/models/sys-file-upload-file-body.ts @@ -67,4 +67,12 @@ export interface SysFileUploadFileBody { * @memberof SysFileUploadFileBody */ dataId?: number; + + /** + * 上传用户Id(解决跨租户上传时用户所属不一致问题) + * + * @type {number} + * @memberof SysFileUploadFileBody + */ + userId?: number; } diff --git a/Web/src/api-services/system/models/upload-file-from-base64-input.ts b/Web/src/api-services/system/models/upload-file-from-base64-input.ts index 182744b8..494e982a 100644 --- a/Web/src/api-services/system/models/upload-file-from-base64-input.ts +++ b/Web/src/api-services/system/models/upload-file-from-base64-input.ts @@ -75,4 +75,12 @@ export interface UploadFileFromBase64Input { * @memberof UploadFileFromBase64Input */ dataId?: number | null; + + /** + * 上传用户Id(解决跨租户上传时用户所属不一致问题) + * + * @type {number} + * @memberof UploadFileFromBase64Input + */ + userId?: number; } diff --git a/Web/src/components/selector/pulldownSelecter.vue b/Web/src/components/selector/pulldownSelecter.vue index 50caff5c..4c88c1f8 100644 --- a/Web/src/components/selector/pulldownSelecter.vue +++ b/Web/src/components/selector/pulldownSelecter.vue @@ -292,6 +292,8 @@ const handleChange = (row: any) => { // 选择器下拉框显示隐藏事件 const selectVisibleChange = (visible: boolean) => { if (visible) { + state.tableData.items = []; + state.tableData.total = 0; state.tableQuery[props.keywordProp] = undefined; handleQuery(); } diff --git a/Web/src/components/sysDict/sysDict.vue b/Web/src/components/sysDict/sysDict.vue index 2301dbfb..5eaedbae 100644 --- a/Web/src/components/sysDict/sysDict.vue +++ b/Web/src/components/sysDict/sysDict.vue @@ -290,26 +290,30 @@ const formattedDictData = computed(() => { * @computed * @returns {DictItem|DictItem[]|null} - 当前选中的字典项或字典项数组 */ - const currentDictItems = computed(() => { - // 更严谨的空值判断,0 不算空 - const isEmpty = (val: any) => - val === null || - val === undefined || - (Array.isArray(val) && val.length === 0) || - (typeof val === 'string' && val.trim() === ''); +const currentDictItems = computed(() => { + // 更严谨的空值判断,0 不算空 + const isEmpty = (val: any) => 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)) { - // 去重并类型兼容(数字/字符串混用时也能正确匹配) - const uniqueValues = [...new Set(state.value)]; - return formattedDictData.value.filter(item => - uniqueValues.some(val => val == item.value) - ); - } + let values: any[] = []; - // 单选时类型兼容 - return formattedDictData.value.find(item => item.value == state.value) || null; + if (props.multiple) { + 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; } + // 多选且字符串,自动按逗号分割 + 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) { 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; }; @@ -477,36 +474,34 @@ const handleMutex = (newValue: any, mutexConfigs: MutexConfig[]): any => { * @function * @param {any} newValue - 新值 */ - const updateValue = (newValue: any) => { - // 先解析为数组(逗号模式下传入可能是字符串) - let processedValue = Array.isArray(newValue) ? newValue : (typeof newValue === 'string' && props.multipleModel === MultipleModel.Comma) - ? newValue.split(',').filter(Boolean) - : newValue; +const updateValue = (newValue: any) => { + // 先解析为数组(逗号模式下传入可能是字符串) + let processedValue = Array.isArray(newValue) ? newValue : typeof newValue === 'string' && props.multipleModel === MultipleModel.Comma ? newValue.split(',').filter(Boolean) : newValue; - // 处理互斥逻辑 - if (props.mutexConfigs && props.mutexConfigs.length > 0) { - processedValue = handleMutex(processedValue, props.mutexConfigs); - } + // 处理互斥逻辑 + if (props.mutexConfigs && props.mutexConfigs.length > 0) { + processedValue = handleMutex(processedValue, props.mutexConfigs); + } - let emitValue = processedValue; - if (props.multipleModel === MultipleModel.Comma) { - if (Array.isArray(processedValue)) { - emitValue = processedValue.length > 0 ? processedValue.sort().join(',') : ''; - } else if (processedValue === null || processedValue === undefined) { - emitValue = undefined; - } - } else { - if (Array.isArray(processedValue)) { - emitValue = processedValue.length > 0 ? processedValue.sort() : []; - } else if (processedValue === null || processedValue === undefined) { - emitValue = undefined; - } - } - console.log('[g-sys-dict] 更新值:', { newValue, processedValue, emitValue }); + let emitValue = processedValue; + if (props.multipleModel === MultipleModel.Comma) { + if (Array.isArray(processedValue)) { + emitValue = processedValue.length > 0 ? processedValue.sort().join(',') : ''; + } else if (processedValue === null || processedValue === undefined) { + emitValue = undefined; + } + } else { + if (Array.isArray(processedValue)) { + emitValue = processedValue.length > 0 ? processedValue.sort() : []; + } else if (processedValue === null || processedValue === undefined) { + emitValue = undefined; + } + } + console.log('[g-sys-dict] 更新值:', { newValue, processedValue, emitValue }); - state.value = processedValue; - emit('update:modelValue', emitValue === '' || emitValue?.length === 0 ? undefined : emitValue); - emit('change', state.value, currentDictItems, state.dictData); + state.value = processedValue; + emit('update:modelValue', emitValue === '' || emitValue?.length === 0 ? undefined : emitValue); + emit('change', state.value, currentDictItems, state.dictData); }; /** diff --git a/Web/src/theme/vxeTable.scss b/Web/src/theme/vxeTable.scss index 1ca7372b..2031792d 100644 --- a/Web/src/theme/vxeTable.scss +++ b/Web/src/theme/vxeTable.scss @@ -135,3 +135,10 @@ .vxe-modal--close-btn:hover { color: red !important; } + +// 解决el-dialog和vxe-tooltip弹出层z-index冲突问题 +.vxe-modal--wrapper, +.vxe-tooltip--wrapper, +.vxe-table--filter-wrapper{ + z-index: 2023 !important; +} diff --git a/Web/src/utils/formValidator.ts b/Web/src/utils/formValidator.ts index fe762266..02222829 100644 --- a/Web/src/utils/formValidator.ts +++ b/Web/src/utils/formValidator.ts @@ -31,7 +31,7 @@ class ValidationBuilder { constructor(config: ValidatorConfig) { this.prop = config.prop; - this.label = config.label || config.prop; + this.label = config.label || '此项'; this.customMessages = config.customMessages || {}; this.defaultTrigger = config.trigger || 'blur'; // 设置默认触发方式 } @@ -75,6 +75,18 @@ class ValidationBuilder { 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 { const defaultMessage = `${this.label}最少输入${len}个字符`;