mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-04-23 22:52:51 +08:00
feat: ✨ 查询过滤器保存
页面定时刷新 WebSocket断线自动重连
This commit is contained in:
parent
6922a863ec
commit
779d8e511a
@ -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')
|
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')
|
||||||
}
|
}
|
||||||
ws.onmessage = async (res) => {
|
this.ws.onmessage = async (res) => {
|
||||||
if (res.data !== this.$TOOL.data.get('APP_VERSION')) {
|
if (res.data !== this.$TOOL.data.get('APP_VERSION')) {
|
||||||
await this.$TOOL.data.set('APP_VERSION', res.data)
|
await this.$TOOL.data.set('APP_VERSION', res.data)
|
||||||
this.showTip(res.data.slice(0, res.data.indexOf('+')))
|
this.showTip(res.data.slice(0, res.data.indexOf('+')))
|
||||||
} else {
|
} else {
|
||||||
await new Promise((x) => setTimeout(x, 10000))
|
await new Promise((x) => setTimeout(x, 10000))
|
||||||
ws.send('1')
|
this.ws.send('1')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.ws.onclose = () => {
|
||||||
|
setTimeout(this.connectWebSocket, 5000) // 5秒后重试
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
/**
|
/**
|
||||||
* 通知消息
|
* 通知消息
|
||||||
*/
|
*/
|
||||||
|
@ -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: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user