mirror of
				https://github.com/nsnail/NetAdmin.git
				synced 2025-10-31 03:19:26 +08:00 
			
		
		
		
	| @@ -11,4 +11,4 @@ | |||||||
|       "path": "node_modules/cz-git" |       "path": "node_modules/cz-git" | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -25,7 +25,6 @@ public record QueryConfigRsp : Sys_Config | |||||||
|     public override bool UserRegisterConfirm { get; init; } |     public override bool UserRegisterConfirm { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_Config.UserRegisterDept" /> |     /// <inheritdoc cref="Sys_Config.UserRegisterDept" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryDeptRsp UserRegisterDept { get; init; } |     public new virtual QueryDeptRsp UserRegisterDept { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_Config.UserRegisterDeptId" /> |     /// <inheritdoc cref="Sys_Config.UserRegisterDeptId" /> | ||||||
| @@ -33,7 +32,6 @@ public record QueryConfigRsp : Sys_Config | |||||||
|     public override long UserRegisterDeptId { get; init; } |     public override long UserRegisterDeptId { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_Config.UserRegisterRole" /> |     /// <inheritdoc cref="Sys_Config.UserRegisterRole" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryRoleRsp UserRegisterRole { get; init; } |     public new virtual QueryRoleRsp UserRegisterRole { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_Config.UserRegisterRoleId" /> |     /// <inheritdoc cref="Sys_Config.UserRegisterRoleId" /> | ||||||
|   | |||||||
| @@ -48,7 +48,6 @@ public record QueryLoginLogRsp : Sys_LoginLog | |||||||
|     public override string LoginUserName { get; protected init; } |     public override string LoginUserName { get; protected init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_LoginLog.Owner" /> |     /// <inheritdoc cref="Sys_LoginLog.Owner" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryUserRsp Owner { get; init; } |     public new virtual QueryUserRsp Owner { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_LoginLog.RequestBody" /> |     /// <inheritdoc cref="Sys_LoginLog.RequestBody" /> | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ public record QueryRequestLogRsp : Sys_RequestLog | |||||||
|     public new virtual string CreatedClientIp => base.CreatedClientIp?.ToIpV4(); |     public new virtual string CreatedClientIp => base.CreatedClientIp?.ToIpV4(); | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_RequestLog.Api" /> |     /// <inheritdoc cref="Sys_RequestLog.Api" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryApiRsp Api { get; init; } |     public new virtual QueryApiRsp Api { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_RequestLog.ApiPathCrc32" /> |     /// <inheritdoc cref="Sys_RequestLog.ApiPathCrc32" /> | ||||||
| @@ -29,7 +28,6 @@ public record QueryRequestLogRsp : Sys_RequestLog | |||||||
|     public override DateTime CreatedTime { get; init; } |     public override DateTime CreatedTime { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_RequestLog.Detail" /> |     /// <inheritdoc cref="Sys_RequestLog.Detail" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryRequestLogDetailRsp Detail { get; init; } |     public new virtual QueryRequestLogDetailRsp Detail { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_RequestLog.Duration" /> |     /// <inheritdoc cref="Sys_RequestLog.Duration" /> | ||||||
| @@ -45,7 +43,6 @@ public record QueryRequestLogRsp : Sys_RequestLog | |||||||
|     public override int HttpStatusCode { get; init; } |     public override int HttpStatusCode { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_RequestLog.Owner" /> |     /// <inheritdoc cref="Sys_RequestLog.Owner" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual QueryUserRsp Owner { get; init; } |     public new virtual QueryUserRsp Owner { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="IFieldOwner.OwnerId" /> |     /// <inheritdoc cref="IFieldOwner.OwnerId" /> | ||||||
|   | |||||||
| @@ -24,7 +24,6 @@ public record QuerySiteMsgRsp : Sys_SiteMsg | |||||||
|     public override string CreatedUserName { get; init; } |     public override string CreatedUserName { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_SiteMsg.Depts" /> |     /// <inheritdoc cref="Sys_SiteMsg.Depts" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual IEnumerable<QueryDeptRsp> Depts { get; init; } |     public new virtual IEnumerable<QueryDeptRsp> Depts { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="EntityBase{T}.Id" /> |     /// <inheritdoc cref="EntityBase{T}.Id" /> | ||||||
| @@ -45,7 +44,6 @@ public record QuerySiteMsgRsp : Sys_SiteMsg | |||||||
|     public QuerySiteMsgFlagRsp MyFlags { get; init; } |     public QuerySiteMsgFlagRsp MyFlags { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_SiteMsg.Roles" /> |     /// <inheritdoc cref="Sys_SiteMsg.Roles" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual IEnumerable<QueryRoleRsp> Roles { get; init; } |     public new virtual IEnumerable<QueryRoleRsp> Roles { get; init; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -62,7 +60,6 @@ public record QuerySiteMsgRsp : Sys_SiteMsg | |||||||
|     public override string Title { get; init; } |     public override string Title { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="Sys_SiteMsg.Users" /> |     /// <inheritdoc cref="Sys_SiteMsg.Users" /> | ||||||
|     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] |  | ||||||
|     public new virtual IEnumerable<QueryUserRsp> Users { get; init; } |     public new virtual IEnumerable<QueryUserRsp> Users { get; init; } | ||||||
|  |  | ||||||
|     /// <inheritdoc cref="IFieldVersion.Version" /> |     /// <inheritdoc cref="IFieldVersion.Version" /> | ||||||
|   | |||||||
| @@ -207,7 +207,9 @@ public sealed class UserService( | |||||||
|                            .ConfigureAwait(false); |                            .ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return dbUser == null ? throw new NetAdminInvalidOperationException(Ln.用户名或密码错误) : LoginInternal(dbUser); |         return dbUser == null | ||||||
|  |             ? throw new NetAdminInvalidOperationException(Ln.用户名或密码错误) | ||||||
|  |             : await LoginInternalAsync(dbUser).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
| @@ -221,7 +223,9 @@ public sealed class UserService( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         var dbUser = await Rpo.Where(a => a.Mobile == req.DestDevice).ToOneAsync().ConfigureAwait(false); |         var dbUser = await Rpo.Where(a => a.Mobile == req.DestDevice).ToOneAsync().ConfigureAwait(false); | ||||||
|         return dbUser == null ? throw new NetAdminInvalidOperationException(Ln.用户不存在) : LoginInternal(dbUser); |         return dbUser == null | ||||||
|  |             ? throw new NetAdminInvalidOperationException(Ln.用户不存在) | ||||||
|  |             : await LoginInternalAsync(dbUser).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
| @@ -229,7 +233,7 @@ public sealed class UserService( | |||||||
|     { |     { | ||||||
|         var dbUser = await Rpo.Where(a => a.Id == userId).ToOneAsync().ConfigureAwait(false); |         var dbUser = await Rpo.Where(a => a.Id == userId).ToOneAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|         return LoginInternal(dbUser); |         return await LoginInternalAsync(dbUser).ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
| @@ -464,14 +468,15 @@ public sealed class UserService( | |||||||
|         return dept.Count != 1 ? throw new NetAdminInvalidOperationException(Ln.部门不存在) : roles; |         return dept.Count != 1 ? throw new NetAdminInvalidOperationException(Ln.部门不存在) : roles; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private LoginRsp LoginInternal(Sys_User dbUser) |     private async Task<LoginRsp> LoginInternalAsync(Sys_User dbUser) | ||||||
|     { |     { | ||||||
|         if (!dbUser.Enabled) { |         if (!dbUser.Enabled) { | ||||||
|             throw new NetAdminInvalidOperationException(Ln.请联系管理员激活账号); |             throw new NetAdminInvalidOperationException(Ln.请联系管理员激活账号); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         _ = UpdateAsync(dbUser with { LastLoginTime = DateTime.Now }, [nameof(Sys_User.LastLoginTime)] |         _ = await UpdateAsync(dbUser with { LastLoginTime = DateTime.Now }, [nameof(Sys_User.LastLoginTime)] | ||||||
| ,                                                                     ignoreVersion: true); | ,                                                                           ignoreVersion: true) | ||||||
|  |             .ConfigureAwait(false); | ||||||
|  |  | ||||||
|         var tokenPayload |         var tokenPayload | ||||||
|             = new Dictionary<string, object> { { nameof(ContextUserToken), dbUser.Adapt<ContextUserToken>() } }; |             = new Dictionary<string, object> { { nameof(ContextUserToken), dbUser.Adapt<ContextUserToken>() } }; | ||||||
|   | |||||||
| @@ -76,15 +76,59 @@ | |||||||
|         <el-badge :hidden="vue.query.dynamicFilter.filters.length === 0" :value="vue.query.dynamicFilter.filters.length"> |         <el-badge :hidden="vue.query.dynamicFilter.filters.length === 0" :value="vue.query.dynamicFilter.filters.length"> | ||||||
|             <el-button-group> |             <el-button-group> | ||||||
|                 <el-button @click="search" icon="el-icon-search" type="primary">{{ $t('查询') }}</el-button> |                 <el-button @click="search" icon="el-icon-search" type="primary">{{ $t('查询') }}</el-button> | ||||||
|                 <el-popover :title="$t('已应用的查询条件')" placement="bottom-end" trigger="hover" width="40rem"> |                 <el-popover :title="$t('已应用的查询条件')" :width="popWidth" placement="bottom-end" trigger="hover"> | ||||||
|                     <template #reference> |                     <template #reference> | ||||||
|                         <el-button @click="reset" icon="el-icon-refresh-left">{{ $t('重置') }}</el-button> |                         <el-button @click="reset" icon="el-icon-refresh-left">{{ $t('重置') }}</el-button> | ||||||
|                     </template> |                     </template> | ||||||
|                     <v-ace-editor |                     <v-ace-editor | ||||||
|                         :theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'" |                         v-model:value="aceEditorValue" | ||||||
|                         :value="vkbeautify().json(vue.query, 2)" |                         :theme="$TOOL.data.get('APP_SET_DARK') || $CONFIG.APP_SET_DARK ? 'github_dark' : 'github'" | ||||||
|                         lang="json" |                         lang="json" | ||||||
|                         style="height: 20rem; width: 100%" /> |                         style="height: 20rem; width: 100%" /> | ||||||
|  |                     <p class="mt-4 flex gap05 items-center" style="justify-content: right"> | ||||||
|  |                         <span class="text-right" style="width: 5rem">{{ $t('全局') }}</span> | ||||||
|  |                         <el-select v-model="this.aceEditorValue" :key="selectFilterKey" :teleported="false" style="flex-grow: 1"> | ||||||
|  |                             <el-option | ||||||
|  |                                 v-for="(item, i) in $TOOL.data.get('APP_SET_QUERY_FILTERS') || []" | ||||||
|  |                                 :key="i" | ||||||
|  |                                 :label="item.name" | ||||||
|  |                                 :value="item.value" /> | ||||||
|  |                         </el-select> | ||||||
|  |                         <el-button-group> | ||||||
|  |                             <el-button @click="saveFilter(true)" plain type="primary">{{ $t('保存') }}</el-button> | ||||||
|  |                             <el-button @click="delFilter(true)" plain>{{ $t('删除') }}</el-button> | ||||||
|  |                         </el-button-group> | ||||||
|  |                     </p> | ||||||
|  |                     <p class="mt-4 flex gap05 items-center" style="justify-content: right"> | ||||||
|  |                         <span class="text-right" style="width: 5rem">{{ $t('本页') }}</span> | ||||||
|  |                         <el-select v-model="this.aceEditorValue" :key="selectFilterKey" :teleported="false" style="flex-grow: 1"> | ||||||
|  |                             <el-option | ||||||
|  |                                 v-for="(item, i) in $TOOL.data.get('APP_SET_QUERY_FILTERS_' + this.queryApi) || []" | ||||||
|  |                                 :key="i" | ||||||
|  |                                 :label="item.name" | ||||||
|  |                                 :value="item.value" /> | ||||||
|  |                         </el-select> | ||||||
|  |                         <el-button-group> | ||||||
|  |                             <el-button @click="saveFilter(false)" plain type="primary">{{ $t('保存') }}</el-button> | ||||||
|  |                             <el-button @click="delFilter(false)" plain>{{ $t('删除') }}</el-button> | ||||||
|  |                         </el-button-group> | ||||||
|  |                     </p> | ||||||
|  |                     <p class="mt-4 text-right"> | ||||||
|  |                         <el-button @click="jsonFormat">{{ $t('JSON 格式化') }}</el-button> | ||||||
|  |                         <el-dropdown :teleported="false"> | ||||||
|  |                             <el-button @click="reSearch" type="primary">{{ $t('重新查询') }}</el-button> | ||||||
|  |                             <template #dropdown> | ||||||
|  |                                 <el-dropdown-menu> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(5)">5s</el-dropdown-item> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(10)">10s</el-dropdown-item> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(15)">15s</el-dropdown-item> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(30)">30s</el-dropdown-item> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(60)">60s</el-dropdown-item> | ||||||
|  |                                     <el-dropdown-item @click="reSearch(120)">120s</el-dropdown-item> | ||||||
|  |                                 </el-dropdown-menu> | ||||||
|  |                             </template> | ||||||
|  |                         </el-dropdown> | ||||||
|  |                     </p> | ||||||
|                 </el-popover> |                 </el-popover> | ||||||
|             </el-button-group> |             </el-button-group> | ||||||
|         </el-badge> |         </el-badge> | ||||||
| @@ -96,7 +140,7 @@ import tool from '@/utils/tool' | |||||||
| import vkbeautify from 'vkbeautify/index' | import vkbeautify from 'vkbeautify/index' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     emits: ['search', 'reset'], |     emits: ['search', 'reset', 'reSearch'], | ||||||
|     props: { |     props: { | ||||||
|         dateField: { type: String, default: 'createdTime' }, |         dateField: { type: String, default: 'createdTime' }, | ||||||
|         hasDate: { type: Boolean, default: true }, |         hasDate: { type: Boolean, default: true }, | ||||||
| @@ -108,6 +152,11 @@ export default { | |||||||
|     }, |     }, | ||||||
|     data() { |     data() { | ||||||
|         return { |         return { | ||||||
|  |             autoResearchTimer: null, | ||||||
|  |             queryApi: null, | ||||||
|  |             popWidth: '40rem', | ||||||
|  |             selectFilterKey: 0, | ||||||
|  |             aceEditorValue: null, | ||||||
|             selectInputKey: null, |             selectInputKey: null, | ||||||
|             dateShortCuts: [ |             dateShortCuts: [ | ||||||
|                 { |                 { | ||||||
| @@ -356,8 +405,23 @@ export default { | |||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     mounted() {}, |     mounted() { | ||||||
|  |         this.queryApi = this.vue.$refs.table.queryApi.url.toUpperCase() | ||||||
|  |     }, | ||||||
|  |     watch: { | ||||||
|  |         'vue.query': { | ||||||
|  |             immediate: true, | ||||||
|  |             deep: true, | ||||||
|  |             handler: function (o, n) { | ||||||
|  |                 this.aceEditorValue = this.vkbeautify.json(n, 2) | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     }, | ||||||
|     async created() { |     async created() { | ||||||
|  |         if (document.body.clientWidth < 1000) { | ||||||
|  |             this.popWidth = '100%' | ||||||
|  |         } | ||||||
|  |         this.aceEditorValue = this.vkbeautify.json(this.vue.query, 2) | ||||||
|         this.selectInputKey = this.controls.find((x) => x.type === 'select-input')?.field[1][0].key |         this.selectInputKey = this.controls.find((x) => x.type === 'select-input')?.field[1][0].key | ||||||
|         if (this.dateType === 'datetimerange') { |         if (this.dateType === 'datetimerange') { | ||||||
|             this.dateShortCuts.unshift( |             this.dateShortCuts.unshift( | ||||||
| @@ -405,8 +469,70 @@ export default { | |||||||
|         tool() { |         tool() { | ||||||
|             return tool |             return tool | ||||||
|         }, |         }, | ||||||
|  |         vkbeautify() { | ||||||
|  |             return vkbeautify | ||||||
|  |         }, | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|  |         jsonFormat() { | ||||||
|  |             try { | ||||||
|  |                 this.aceEditorValue = vkbeautify.json(this.aceEditorValue, 2) | ||||||
|  |             } catch { | ||||||
|  |                 this.$message.error(this.$t('格式错误')) | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         async reSearch(sec) { | ||||||
|  |             const newParam = JSON.parse(this.aceEditorValue) | ||||||
|  |             this.vue.$refs.table.tableParams = newParam | ||||||
|  |             this.vue.$refs.table.upData() | ||||||
|  |             await this.$nextTick() | ||||||
|  |             this.vue.$refs.table.tableParams = this.vue.query | ||||||
|  |             this.$emit('reSearch', newParam) | ||||||
|  |  | ||||||
|  |             if (typeof sec !== 'number') return | ||||||
|  |             const timerEl = document.getElementsByClassName('autoResearchTimer')[0] | ||||||
|  |             if (!timerEl) { | ||||||
|  |                 this.$message({ | ||||||
|  |                     showClose: true, | ||||||
|  |                     onClose: () => clearInterval(this.autoResearchTimer), | ||||||
|  |                     type: 'warning', | ||||||
|  |                     customClass: 'autoResearchTimer', | ||||||
|  |                     message: this.$t('{s} 秒后刷新...', { s: sec }), | ||||||
|  |                     duration: 0, | ||||||
|  |                 }) | ||||||
|  |                 this.autoResearchTimer = setInterval(() => { | ||||||
|  |                     const el = document.getElementsByClassName('autoResearchTimer')[0].getElementsByClassName('el-message__content')[0] | ||||||
|  |                     let num = parseInt(/(\d+)/.exec(el.innerHTML)[0]) | ||||||
|  |                     if (num === 1) { | ||||||
|  |                         this.reSearch() | ||||||
|  |                         num = sec + 1 | ||||||
|  |                     } | ||||||
|  |                     el.innerHTML = el.innerHTML.replace(/\d+/, (num - 1).toString()) | ||||||
|  |                 }, 1000) | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         async delFilter(isGlobal) { | ||||||
|  |             const key = isGlobal ? 'APP_SET_QUERY_FILTERS' : 'APP_SET_QUERY_FILTERS_' + this.queryApi | ||||||
|  |             let filters = this.$TOOL.data.get(key) || [] | ||||||
|  |             filters = filters.filter((x) => x.value !== this.aceEditorValue) | ||||||
|  |             await this.$TOOL.data.set(key, filters) | ||||||
|  |             this.$message.success(this.$t('删除成功')) | ||||||
|  |             this.selectFilterKey = Math.random() | ||||||
|  |         }, | ||||||
|  |         async saveFilter(isGlobal) { | ||||||
|  |             const key = isGlobal ? 'APP_SET_QUERY_FILTERS' : 'APP_SET_QUERY_FILTERS_' + this.queryApi | ||||||
|  |             try { | ||||||
|  |                 const filterName = await this.$prompt('设置一个过滤器名称', '保存查询条件', { | ||||||
|  |                     inputPattern: /\S/, | ||||||
|  |                     inputErrorMessage: '名称不能为空', | ||||||
|  |                 }) | ||||||
|  |                 let filters = this.$TOOL.data.get(key) || [] | ||||||
|  |                 filters = filters.filter((x) => x.name !== filterName.value) | ||||||
|  |                 filters.push({ name: filterName.value, value: this.aceEditorValue }) | ||||||
|  |                 await this.$TOOL.data.set(key, filters) | ||||||
|  |                 this.$message.success(this.$t('保存成功')) | ||||||
|  |             } catch {} | ||||||
|  |         }, | ||||||
|         trimSpaces(key) { |         trimSpaces(key) { | ||||||
|             this.form[key][this.selectInputKey] = this.form[key][this.selectInputKey].replace(/^\s*(.*?)\s*$/g, '$1') |             this.form[key][this.selectInputKey] = this.form[key][this.selectInputKey].replace(/^\s*(.*?)\s*$/g, '$1') | ||||||
|         }, |         }, | ||||||
| @@ -415,9 +541,6 @@ export default { | |||||||
|                 delete this.form[item.field[0]][field.key] |                 delete this.form[item.field[0]][field.key] | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         vkbeautify() { |  | ||||||
|             return vkbeautify |  | ||||||
|         }, |  | ||||||
|         search() { |         search() { | ||||||
|             const parentQuery = this.clearParentQuery() |             const parentQuery = this.clearParentQuery() | ||||||
|             Object.assign(parentQuery, this.form.root || {}) |             Object.assign(parentQuery, this.form.root || {}) | ||||||
|   | |||||||
| @@ -6,24 +6,36 @@ import config from '@/config' | |||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     data() { |     data() { | ||||||
|         return {} |         return { | ||||||
|  |             ws: null, | ||||||
|  |         } | ||||||
|     }, |     }, | ||||||
|     async created() { |     async created() { | ||||||
|         const ws = new WebSocket(`ws://${config.API_URL.replace('http://', '')}/ws/version`) |         this.connectWebSocket() | ||||||
|         ws.onopen = () => { |  | ||||||
|             ws.send('1') |  | ||||||
|         } |  | ||||||
|         ws.onmessage = async (res) => { |  | ||||||
|             if (res.data !== this.$TOOL.data.get('APP_VERSION')) { |  | ||||||
|                 await this.$TOOL.data.set('APP_VERSION', res.data) |  | ||||||
|                 this.showTip(res.data.slice(0, res.data.indexOf('+'))) |  | ||||||
|             } else { |  | ||||||
|                 await new Promise((x) => setTimeout(x, 10000)) |  | ||||||
|                 ws.send('1') |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|  |         connectWebSocket() { | ||||||
|  |             this.ws = new WebSocket( | ||||||
|  |                 import.meta.env.MODE === 'production' | ||||||
|  |                     ? `wss://${config.API_URL.replace('https://', '')}/ws/version` | ||||||
|  |                     : `ws://${window.location.host}/ws/version`, | ||||||
|  |             ) | ||||||
|  |             this.ws.onopen = () => { | ||||||
|  |                 this.ws.send('1') | ||||||
|  |             } | ||||||
|  |             this.ws.onmessage = async (res) => { | ||||||
|  |                 if (res.data !== this.$TOOL.data.get('APP_VERSION')) { | ||||||
|  |                     await this.$TOOL.data.set('APP_VERSION', res.data) | ||||||
|  |                     this.showTip(res.data.slice(0, res.data.indexOf('+'))) | ||||||
|  |                 } else { | ||||||
|  |                     await new Promise((x) => setTimeout(x, 10000)) | ||||||
|  |                     this.ws.send('1') | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             this.ws.onclose = () => { | ||||||
|  |                 setTimeout(this.connectWebSocket, 5000) // 5秒后重试 | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         /** |         /** | ||||||
|          * 通知消息 |          * 通知消息 | ||||||
|          */ |          */ | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
|  |  | ||||||
| <template> | <template> | ||||||
|     <div class="sc-dialog" ref="scDialog"> |     <div class="sc-dialog" ref="scDialog"> | ||||||
|         <el-dialog v-bind="$attrs" v-model="dialogVisible" :fullscreen="isFullscreen" :show-close="false" ref="dialog"> |         <el-dialog v-bind="$attrs" v-model="dialogVisible" :fullscreen="isFullscreen" :show-close="false" draggable ref="dialog"> | ||||||
|             <template #header> |             <template #header> | ||||||
|                 <slot name="header"> |                 <slot name="header"> | ||||||
|                     <span class="el-dialog__title">{{ title }}</span> |                     <span class="el-dialog__title">{{ title }}</span> | ||||||
|   | |||||||
| @@ -24,6 +24,12 @@ | |||||||
|                 </el-icon> |                 </el-icon> | ||||||
|                 刷新 |                 刷新 | ||||||
|             </li> |             </li> | ||||||
|  |             <li @click="scheduledRefresh()"> | ||||||
|  |                 <el-icon> | ||||||
|  |                     <el-icon-alarm-clock /> | ||||||
|  |                 </el-icon> | ||||||
|  |                 定时刷新 | ||||||
|  |             </li> | ||||||
|             <hr /> |             <hr /> | ||||||
|             <li :class="contextMenuItem.meta.affix ? 'disabled' : ''" @click="closeTabs()"> |             <li :class="contextMenuItem.meta.affix ? 'disabled' : ''" @click="closeTabs()"> | ||||||
|                 <el-icon> |                 <el-icon> | ||||||
| @@ -61,6 +67,7 @@ export default { | |||||||
|     name: 'tags', |     name: 'tags', | ||||||
|     data() { |     data() { | ||||||
|         return { |         return { | ||||||
|  |             refreshTimer: null, | ||||||
|             contextMenuVisible: false, |             contextMenuVisible: false, | ||||||
|             contextMenuItem: null, |             contextMenuItem: null, | ||||||
|             left: 0, |             left: 0, | ||||||
| @@ -69,7 +76,9 @@ export default { | |||||||
|             tipDisplayed: false, |             tipDisplayed: false, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     props: {}, |     props: { | ||||||
|  |         vue: { type: Object }, | ||||||
|  |     }, | ||||||
|     watch: { |     watch: { | ||||||
|         $route(e) { |         $route(e) { | ||||||
|             this.addViewTags(e) |             this.addViewTags(e) | ||||||
| @@ -188,27 +197,38 @@ export default { | |||||||
|             this.contextMenuItem = null |             this.contextMenuItem = null | ||||||
|             this.contextMenuVisible = false |             this.contextMenuVisible = false | ||||||
|         }, |         }, | ||||||
|  |         async scheduledRefresh() { | ||||||
|  |             this.closeMenu() | ||||||
|  |             try { | ||||||
|  |                 const sleep = await this.$prompt('刷新时间间隔(秒)', '定时刷新', { | ||||||
|  |                     inputPattern: /^[1-9]\d*$/, | ||||||
|  |                     inputErrorMessage: '时间必须为数字', | ||||||
|  |                     inputValue: '10', | ||||||
|  |                 }) | ||||||
|  |                 const sleepSecs = parseInt(sleep.value) | ||||||
|  |                 this.$message({ | ||||||
|  |                     showClose: true, | ||||||
|  |                     onClose: () => clearInterval(this.refreshTimer), | ||||||
|  |                     type: 'warning', | ||||||
|  |                     customClass: 'refreshTimer', | ||||||
|  |                     message: this.$t('{s} 秒后刷新...', { s: sleepSecs }), | ||||||
|  |                     duration: 0, | ||||||
|  |                 }) | ||||||
|  |                 this.refreshTimer = setInterval(() => { | ||||||
|  |                     const el = document.getElementsByClassName('refreshTimer')[0].getElementsByClassName('el-message__content')[0] | ||||||
|  |                     let num = parseInt(/(\d+)/.exec(el.innerHTML)[0]) | ||||||
|  |                     if (num === 1) { | ||||||
|  |                         this.vue.routerViewKey = Math.random() | ||||||
|  |                         num = sleepSecs + 1 | ||||||
|  |                     } | ||||||
|  |                     el.innerHTML = el.innerHTML.replace(/\d+/, (num - 1).toString()) | ||||||
|  |                 }, 1000) | ||||||
|  |             } catch {} | ||||||
|  |         }, | ||||||
|         //TAB 刷新 |         //TAB 刷新 | ||||||
|         refreshTab() { |         refreshTab() { | ||||||
|             this.contextMenuVisible = false |             this.closeMenu() | ||||||
|             const nowTag = this.contextMenuItem |             this.vue.routerViewKey = Math.random() | ||||||
|             //判断是否当前路由,否的话跳转 |  | ||||||
|             if (this.$route.fullPath !== nowTag.fullPath) { |  | ||||||
|                 this.$router.push({ |  | ||||||
|                     path: nowTag.fullPath, |  | ||||||
|                     query: nowTag.query, |  | ||||||
|                 }) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             this.$store.commit('refreshIframe', nowTag) |  | ||||||
|             setTimeout(() => { |  | ||||||
|                 this.$store.commit('removeKeepLive', nowTag.name) |  | ||||||
|                 this.$store.commit('setRouteShow', false) |  | ||||||
|                 this.$nextTick(() => { |  | ||||||
|                     this.$store.commit('pushKeepLive', nowTag.name) |  | ||||||
|                     this.$store.commit('setRouteShow', true) |  | ||||||
|                 }) |  | ||||||
|             }, 0) |  | ||||||
|         }, |         }, | ||||||
|         //TAB 关闭 |         //TAB 关闭 | ||||||
|         closeTabs() { |         closeTabs() { | ||||||
|   | |||||||
| @@ -46,9 +46,9 @@ | |||||||
|             </div> |             </div> | ||||||
|             <Side-m v-if="ismobile"></Side-m> |             <Side-m v-if="ismobile"></Side-m> | ||||||
|             <div class="aminui-body el-container"> |             <div class="aminui-body el-container"> | ||||||
|                 <Tags v-if="!ismobile && layoutTags"></Tags> |                 <Tags v-if="!ismobile && layoutTags" :vue="this"></Tags> | ||||||
|                 <div class="adminui-main" id="adminui-main"> |                 <div class="adminui-main" id="adminui-main"> | ||||||
|                     <router-view v-slot="{ Component }"> |                     <router-view v-slot="{ Component }" :key="routerViewKey"> | ||||||
|                         <keep-alive> |                         <keep-alive> | ||||||
|                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> |                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> | ||||||
|                         </keep-alive> |                         </keep-alive> | ||||||
| @@ -93,9 +93,9 @@ | |||||||
|             </div> |             </div> | ||||||
|             <Side-m v-if="ismobile"></Side-m> |             <Side-m v-if="ismobile"></Side-m> | ||||||
|             <div class="aminui-body el-container"> |             <div class="aminui-body el-container"> | ||||||
|                 <Tags v-if="!ismobile && layoutTags"></Tags> |                 <Tags v-if="!ismobile && layoutTags" :vue="this"></Tags> | ||||||
|                 <div class="adminui-main" id="adminui-main"> |                 <div class="adminui-main" id="adminui-main"> | ||||||
|                     <router-view v-slot="{ Component }"> |                     <router-view v-slot="{ Component }" :key="routerViewKey"> | ||||||
|                         <keep-alive> |                         <keep-alive> | ||||||
|                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> |                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> | ||||||
|                         </keep-alive> |                         </keep-alive> | ||||||
| @@ -135,9 +135,9 @@ | |||||||
|         </header> |         </header> | ||||||
|         <section class="aminui-wrapper"> |         <section class="aminui-wrapper"> | ||||||
|             <div class="aminui-body el-container"> |             <div class="aminui-body el-container"> | ||||||
|                 <Tags v-if="!ismobile && layoutTags"></Tags> |                 <Tags v-if="!ismobile && layoutTags" :vue="this"></Tags> | ||||||
|                 <div class="adminui-main" id="adminui-main"> |                 <div class="adminui-main" id="adminui-main"> | ||||||
|                     <router-view v-slot="{ Component }"> |                     <router-view v-slot="{ Component }" :key="routerViewKey"> | ||||||
|                         <keep-alive> |                         <keep-alive> | ||||||
|                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> |                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> | ||||||
|                         </keep-alive> |                         </keep-alive> | ||||||
| @@ -195,9 +195,9 @@ | |||||||
|                 <Topbar> |                 <Topbar> | ||||||
|                     <userbar></userbar> |                     <userbar></userbar> | ||||||
|                 </Topbar> |                 </Topbar> | ||||||
|                 <Tags v-if="!ismobile && layoutTags"></Tags> |                 <Tags v-if="!ismobile && layoutTags" :vue="this"></Tags> | ||||||
|                 <div class="adminui-main" id="adminui-main"> |                 <div class="adminui-main" id="adminui-main"> | ||||||
|                     <router-view v-slot="{ Component }"> |                     <router-view v-slot="{ Component }" :key="routerViewKey"> | ||||||
|                         <keep-alive> |                         <keep-alive> | ||||||
|                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> |                             <component v-if="$store.state.keepAlive.routeShow" :is="Component" :key="$route.fullPath" /> | ||||||
|                         </keep-alive> |                         </keep-alive> | ||||||
| @@ -239,6 +239,7 @@ export default { | |||||||
|     }, |     }, | ||||||
|     data() { |     data() { | ||||||
|         return { |         return { | ||||||
|  |             routerViewKey: 0, | ||||||
|             menu: [], |             menu: [], | ||||||
|             nextMenu: [], |             nextMenu: [], | ||||||
|             pmenu: {}, |             pmenu: {}, | ||||||
|   | |||||||
| @@ -436,7 +436,6 @@ export default { | |||||||
|     周四: 'Thursday', |     周四: 'Thursday', | ||||||
|     周五: 'Friday', |     周五: 'Friday', | ||||||
|     周六: 'Saturday', |     周六: 'Saturday', | ||||||
|     JSON格式化: 'JSON formatting', |  | ||||||
|     确定: 'Confirm', |     确定: 'Confirm', | ||||||
|     无权限或找不到页面: 'No permission or page not found', |     无权限或找不到页面: 'No permission or page not found', | ||||||
|     '当前页面无权限访问或者打开了一个不存在的链接,请检查当前账户权限和链接的可访问性。': |     '当前页面无权限访问或者打开了一个不存在的链接,请检查当前账户权限和链接的可访问性。': | ||||||
| @@ -467,6 +466,7 @@ export default { | |||||||
|     用户列表: 'User list', |     用户列表: 'User list', | ||||||
|     是: 'Yes', |     是: 'Yes', | ||||||
|     否: 'No', |     否: 'No', | ||||||
|  |     资源复用: 'Resource reuse', | ||||||
|     手机: 'Mobile', |     手机: 'Mobile', | ||||||
|     '您已退出登录或无权限访问当前资源,请重新登录后再操作': |     '您已退出登录或无权限访问当前资源,请重新登录后再操作': | ||||||
|         'You have logged out or do not have permission to access the current resource, please log in again before operating', |         'You have logged out or do not have permission to access the current resource, please log in again before operating', | ||||||
| @@ -497,4 +497,10 @@ export default { | |||||||
|     请输入操作符: 'Please enter operator', |     请输入操作符: 'Please enter operator', | ||||||
|     查询字段: 'Query field', |     查询字段: 'Query field', | ||||||
|     最后登录: 'Last login', |     最后登录: 'Last login', | ||||||
|  |     '{s} 秒后刷新...': '{s} seconds to refresh...', | ||||||
|  |     重新查询: 'Re-query', | ||||||
|  |     全局: 'Global', | ||||||
|  |     本页: 'Page', | ||||||
|  |     'JSON 格式化': 'JSON formatting', | ||||||
|  |     格式错误: 'Format error', | ||||||
| } | } | ||||||
| @@ -435,7 +435,6 @@ export default { | |||||||
|     周四: '周四', |     周四: '周四', | ||||||
|     周五: '周五', |     周五: '周五', | ||||||
|     周六: '周六', |     周六: '周六', | ||||||
|     JSON格式化: 'JSON格式化', |  | ||||||
|     确定: '确定', |     确定: '确定', | ||||||
|     无权限或找不到页面: '无权限或找不到页面', |     无权限或找不到页面: '无权限或找不到页面', | ||||||
|     '当前页面无权限访问或者打开了一个不存在的链接,请检查当前账户权限和链接的可访问性。': |     '当前页面无权限访问或者打开了一个不存在的链接,请检查当前账户权限和链接的可访问性。': | ||||||
| @@ -465,6 +464,7 @@ export default { | |||||||
|     用户列表: '用户列表', |     用户列表: '用户列表', | ||||||
|     是: '是', |     是: '是', | ||||||
|     否: '否', |     否: '否', | ||||||
|  |     资源复用: '资源复用', | ||||||
|     手机: '手机', |     手机: '手机', | ||||||
|     '您已退出登录或无权限访问当前资源,请重新登录后再操作': '您已退出登录或无权限访问当前资源,请重新登录后再操作', |     '您已退出登录或无权限访问当前资源,请重新登录后再操作': '您已退出登录或无权限访问当前资源,请重新登录后再操作', | ||||||
|     访问受限: '访问受限', |     访问受限: '访问受限', | ||||||
| @@ -494,4 +494,10 @@ export default { | |||||||
|     请输入操作符: '请输入操作符', |     请输入操作符: '请输入操作符', | ||||||
|     查询字段: '查询字段', |     查询字段: '查询字段', | ||||||
|     最后登录: '最后登录', |     最后登录: '最后登录', | ||||||
|  |     '{s} 秒后刷新...': '{s} 秒后刷新...', | ||||||
|  |     重新查询: '重新查询', | ||||||
|  |     全局: '全局', | ||||||
|  |     本页: '本页', | ||||||
|  |     'JSON 格式化': 'JSON 格式化', | ||||||
|  |     格式错误: '格式错误', | ||||||
| } | } | ||||||
| @@ -513,6 +513,10 @@ textarea { | |||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .text-right { | ||||||
|  |     text-align: right; | ||||||
|  | } | ||||||
|  |  | ||||||
| .flex { | .flex { | ||||||
|     display: flex; |     display: flex; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ | |||||||
|                     'nextExecTime', |                     'nextExecTime', | ||||||
|                     'enabled', |                     'enabled', | ||||||
|                     'createdTime', |                     'createdTime', | ||||||
|  |                     'lastDuration', | ||||||
|                 ]" |                 ]" | ||||||
|                 :default-sort="{ prop: 'lastExecTime', order: 'descending' }" |                 :default-sort="{ prop: 'lastExecTime', order: 'descending' }" | ||||||
|                 :export-api="$API.sys_job.export" |                 :export-api="$API.sys_job.export" | ||||||
|   | |||||||
| @@ -80,7 +80,9 @@ | |||||||
|                                 :theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'" |                                 :theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'" | ||||||
|                                 lang="json" |                                 lang="json" | ||||||
|                                 style="height: 30rem; width: 100%" /> |                                 style="height: 30rem; width: 100%" /> | ||||||
|                             <el-button @click="form.dashboardLayout = jsonFormat(form.dashboardLayout)" type="text">{{ $t('JSON格式化') }}</el-button> |                             <el-button @click="form.dashboardLayout = jsonFormat(form.dashboardLayout)" type="text">{{ | ||||||
|  |                                 $t('JSON 格式化') | ||||||
|  |                             }}</el-button> | ||||||
|                         </el-form-item> |                         </el-form-item> | ||||||
|                     </el-form> |                     </el-form> | ||||||
|                 </el-tab-pane> |                 </el-tab-pane> | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ | |||||||
|         </el-header> |         </el-header> | ||||||
|         <el-main class="nopadding"> |         <el-main class="nopadding"> | ||||||
|             <sc-table |             <sc-table | ||||||
|                 :context-menus="['id', 'userName', 'mobile', 'email', 'enabled', 'createdTime']" |                 :context-menus="['id', 'userName', 'mobile', 'email', 'enabled', 'createdTime', 'lastLoginTime']" | ||||||
|                 :context-opers="['view', 'edit']" |                 :context-opers="['view', 'edit']" | ||||||
|                 :default-sort="{ prop: 'createdTime', order: 'descending' }" |                 :default-sort="{ prop: 'createdTime', order: 'descending' }" | ||||||
|                 :export-api="$API.sys_user.export" |                 :export-api="$API.sys_user.export" | ||||||
|   | |||||||
| @@ -26,6 +26,11 @@ export default defineConfig({ | |||||||
|                 changeOrigin: true, |                 changeOrigin: true, | ||||||
|                 rewrite: (p) => p.replace(/^\/api/, ''), |                 rewrite: (p) => p.replace(/^\/api/, ''), | ||||||
|             }, |             }, | ||||||
|  |             '/ws': { | ||||||
|  |                 target: 'ws://localhost:5010/ws', | ||||||
|  |                 ws: true, | ||||||
|  |                 rewrite: (p) => p.replace(/^\/ws/, ''), | ||||||
|  |             }, | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     css: { |     css: { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 GitHub
						GitHub