mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-07-05 10:08:15 +08:00
feat: ✨ 移除RedLocker,更改为自实现 (#169)
用户表增加最后登录时间字段 列表查询多字段模糊查询改为单字段精确查询 WebSocket版本更新检查 前端自定义字段筛选 暗黑模式样式调整 [skip ci] Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
@ -83,7 +83,7 @@
|
||||
}
|
||||
|
||||
.dark .app-loading__title {
|
||||
color: #d0d0d0;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
|
@ -12,13 +12,13 @@
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"ace-builds": "^1.35.4",
|
||||
"aieditor": "^1.0.13",
|
||||
"axios": "^1.7.2",
|
||||
"axios": "^1.7.3",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.37.1",
|
||||
"core-js": "^3.38.0",
|
||||
"cropperjs": "^1.6.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.7.8",
|
||||
"element-plus": "^2.8.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"json5-to-table": "^0.1.8",
|
||||
"markdown-it": "^14.1.0",
|
||||
@ -28,21 +28,21 @@
|
||||
"qrcodejs2": "^0.0.2",
|
||||
"sortablejs": "^1.15.2",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"vue": "^3.4.34",
|
||||
"vue": "^3.4.37",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.4.0",
|
||||
"vue-router": "^4.4.3",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
"vue3-json-viewer": "^2.2.2",
|
||||
"vuedraggable": "^4.0.3",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.1",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-organize-attributes": "^1.0.0",
|
||||
"sass": "^1.77.8",
|
||||
"terser": "^5.31.3",
|
||||
"vite": "^5.3.5"
|
||||
"terser": "^5.31.5",
|
||||
"vite": "^5.4.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
@ -21,6 +21,21 @@
|
||||
:placeholder="item.placeholder"
|
||||
:style="item.style"
|
||||
clearable />
|
||||
<el-input
|
||||
v-if="item.type === 'select-input' && (!item.condition || item.condition())"
|
||||
v-model="form[item.field[0]][selectInputKey]"
|
||||
v-role="item.role || '*/*/*'"
|
||||
:class="item.class"
|
||||
:placeholder="item.placeholder"
|
||||
:style="item.style"
|
||||
@change="trimSpaces(item.field[0])"
|
||||
clearable>
|
||||
<template #prepend>
|
||||
<el-select v-model="selectInputKey" :placeholder="$t('查询字段')" :style="item.selectStyle" @change="selectInputChange(item)">
|
||||
<el-option v-for="(field, j) in item.field[1]" :label="field.label" :value="field.key" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<sc-select
|
||||
v-else-if="item.type === 'remote-select' && (!item.condition || item.condition())"
|
||||
v-model="form[item.field[0]][item.field[1]]"
|
||||
@ -93,6 +108,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectInputKey: null,
|
||||
dateShortCuts: [
|
||||
{
|
||||
text: this.$t('今日'),
|
||||
@ -342,6 +358,7 @@ export default {
|
||||
},
|
||||
mounted() {},
|
||||
async created() {
|
||||
this.selectInputKey = this.controls.find((x) => x.type === 'select-input')?.field[1][0].key
|
||||
if (this.dateType === 'datetimerange') {
|
||||
this.dateShortCuts.unshift(
|
||||
{
|
||||
@ -390,6 +407,14 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
trimSpaces(key) {
|
||||
this.form[key][this.selectInputKey] = this.form[key][this.selectInputKey].replace(/^\s*(.*?)\s*$/g, '$1')
|
||||
},
|
||||
selectInputChange(item) {
|
||||
for (const field of item.field[1]) {
|
||||
delete this.form[item.field[0]][field.key]
|
||||
}
|
||||
},
|
||||
vkbeautify() {
|
||||
return vkbeautify
|
||||
},
|
||||
|
@ -32,7 +32,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
user: {},
|
||||
form: {},
|
||||
form: {
|
||||
requiredFields: ['Id', 'UserName', 'Mobile'],
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -2,20 +2,26 @@
|
||||
|
||||
<script>
|
||||
import { h } from 'vue'
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
async created() {
|
||||
setInterval(async () => {
|
||||
// 检查版本
|
||||
const res = await this.$API.sys_tools.getVersion.post({})
|
||||
const ws = new WebSocket(`ws://${config.API_URL.replace('http://', '')}/ws/version`)
|
||||
ws.onopen = () => {
|
||||
ws.send('1')
|
||||
}
|
||||
ws.onmessage = async (res) => {
|
||||
if (res.data !== this.$TOOL.data.get('APP_VERSION')) {
|
||||
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('+')))
|
||||
} else {
|
||||
await new Promise((x) => setTimeout(x, 10000))
|
||||
ws.send('1')
|
||||
}
|
||||
}, 10000)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
|
@ -12,7 +12,7 @@
|
||||
<el-card v-for="job in jobs" :class="`user-bar-jobs-item ${job.lastStatusCode === 'oK' ? '' : 'alert'}`" :key="job.id" shadow="hover">
|
||||
<div class="user-bar-jobs-item-body">
|
||||
<div class="jobIcon">
|
||||
{{ job.lastStatusCode }}
|
||||
{{ job.lastStatusCode.toUpperCase() }}
|
||||
</div>
|
||||
<div class="jobMain">
|
||||
<div class="title">
|
||||
|
@ -495,4 +495,6 @@ export default {
|
||||
一行一个: 'One line per item',
|
||||
请输入字段名: 'Please enter field name',
|
||||
请输入操作符: 'Please enter operator',
|
||||
查询字段: 'Query field',
|
||||
最后登录: 'Last login',
|
||||
}
|
@ -492,4 +492,6 @@ export default {
|
||||
一行一个: '一行一个',
|
||||
请输入字段名: '请输入字段名',
|
||||
请输入操作符: '请输入操作符',
|
||||
查询字段: '查询字段',
|
||||
最后登录: '最后登录',
|
||||
}
|
@ -2,7 +2,10 @@
|
||||
|
||||
html.dark {
|
||||
//变量
|
||||
--el-text-color-primary: #d0d0d0;
|
||||
--el-text-color-primary: #c0c0c0;
|
||||
--el-text-color-regular: #c0c0c0;
|
||||
--el-text-color-secondary: #666666;
|
||||
--el-mask-color: rgba(29, 30, 31, 0.9);
|
||||
--el-color-primary-dark-2: var(--el-color-primary-light-2) !important;
|
||||
--el-color-primary-light-9: var(--el-color-primary-dark-8) !important;
|
||||
--el-color-primary-light-8: var(--el-color-primary-dark-7) !important;
|
||||
@ -73,7 +76,7 @@ html.dark {
|
||||
.el-header,
|
||||
.el-main.nopadding,
|
||||
.el-footer {
|
||||
background: var(--el-bg-color-overlay);
|
||||
background: var(--el-bg-color);
|
||||
border-color: var(--el-border-color-light);
|
||||
}
|
||||
|
||||
@ -93,4 +96,16 @@ html.dark {
|
||||
.el-table th.is-sortable:hover {
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.jv-container .jv-code.open {
|
||||
background: var(--el-bg-color) !important;
|
||||
}
|
||||
|
||||
.ace_editor {
|
||||
background: var(--el-bg-color-overlay);
|
||||
}
|
||||
|
||||
.ace_gutter {
|
||||
background: var(--el-bg-color-overlay);
|
||||
}
|
||||
}
|
@ -257,7 +257,9 @@ export default {
|
||||
async copyJob(row) {
|
||||
let loading = this.$loading()
|
||||
try {
|
||||
const res = await this.$API.sys_job.create.post(Object.assign({}, row, { id: 0, jobName: row.jobName + '-copy' }))
|
||||
const res = await this.$API.sys_job.create.post(
|
||||
Object.assign({}, row, { id: 0, jobName: row.jobName + '-copy', enabled: false, nextTimeId: null, status: 'idle' }),
|
||||
)
|
||||
if (res.data) {
|
||||
this.$message.success(this.$t('操作成功'))
|
||||
} else {
|
||||
|
@ -44,7 +44,7 @@
|
||||
v-model:value="form.requestHeader"
|
||||
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
|
||||
lang="json"
|
||||
style="height: 5rem; width: 100%" />
|
||||
style="height: 10rem; width: 100%" />
|
||||
<el-button @click="form.requestHeader = jsonFormat(form.requestHeader)" type="text">{{ $t('JSON格式化') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求体')" prop="requestBody">
|
||||
@ -52,7 +52,7 @@
|
||||
v-model:value="form.requestBody"
|
||||
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
|
||||
lang="json"
|
||||
style="height: 10rem; width: 100%" />
|
||||
style="height: 15rem; width: 100%" />
|
||||
<el-button @click="form.requestBody = jsonFormat(form.requestBody)" type="text">{{ $t('JSON格式化') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('请求的网络地址')" prop="requestUrl">
|
||||
@ -107,7 +107,7 @@
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-if="mode === 'view'" :label="$t('执行记录')" name="record">
|
||||
<record v-if="tabId === 'record'" :keywords="form.id" />
|
||||
<record v-if="tabId === 'record'" :job-id="form.id" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
|
||||
<json-viewer
|
||||
|
@ -28,13 +28,21 @@
|
||||
style: 'width:20rem',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: ['root', 'keywords'],
|
||||
placeholder: $t('作业编号 / 作业名称 / 执行编号'),
|
||||
style: 'width:20rem',
|
||||
type: 'select-input',
|
||||
field: [
|
||||
'dy',
|
||||
[
|
||||
{ label: '唯一编码', key: 'id' },
|
||||
{ label: '作业编号', key: 'jobId' },
|
||||
],
|
||||
],
|
||||
placeholder: '匹配内容',
|
||||
style: 'width:25rem',
|
||||
selectStyle: 'width:8rem',
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
@reset="onReset"
|
||||
@search="onSearch"
|
||||
dateFormat="YYYY-MM-DD HH:mm:ss"
|
||||
dateType="datetimerange"
|
||||
@ -73,7 +81,7 @@
|
||||
:data="row"
|
||||
:options="
|
||||
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
|
||||
return { value: x[0], text: `${x[1][1]}`, type: x[1][2] }
|
||||
return { value: x[0], text: `${x[1][1].toString().toUpperCase()}`, type: x[1][2] }
|
||||
})
|
||||
"
|
||||
prop="httpMethod" />
|
||||
@ -96,8 +104,8 @@
|
||||
align="right"
|
||||
prop="duration"
|
||||
sortable="custom"
|
||||
width="150" />
|
||||
<el-table-column :label="$t('作业信息')" prop="jobId" show-overflow-tooltip sortable="custom" width="500">
|
||||
width="100" />
|
||||
<el-table-column :label="$t('作业信息')" min-width="150" prop="jobId" show-overflow-tooltip sortable="custom">
|
||||
<template #default="{ row }">
|
||||
<p>
|
||||
<el-link @click="jobClick(row.job)">
|
||||
@ -109,7 +117,7 @@
|
||||
</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('响应体')" prop="responseBody" show-overflow-tooltip sortable="custom" />
|
||||
<el-table-column :label="$t('响应体')" min-width="300" prop="responseBody" show-overflow-tooltip sortable="custom" />
|
||||
<na-col-operation :buttons="[naColOperation.buttons[0]]" :vue="this" width="100" />
|
||||
</sc-table>
|
||||
</el-main>
|
||||
@ -165,6 +173,13 @@ export default {
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (this.jobId) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'jobId',
|
||||
operator: 'eq',
|
||||
value: this.jobId,
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -181,7 +196,6 @@ export default {
|
||||
],
|
||||
},
|
||||
filter: {},
|
||||
keywords: this.keywords,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -190,6 +204,11 @@ export default {
|
||||
jobClick(job) {
|
||||
this.dialog.job = { mode: 'view', row: { id: job.id } }
|
||||
},
|
||||
onReset() {
|
||||
if (this.jobId) {
|
||||
this.$refs.search.selectInputKey = 'jobId'
|
||||
}
|
||||
},
|
||||
//搜索
|
||||
onSearch(form) {
|
||||
if (Array.isArray(form.dy.createdTime)) {
|
||||
@ -223,6 +242,22 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.jobId === 'string' && form.dy.jobId.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'jobId',
|
||||
operator: 'eq',
|
||||
value: form.dy.jobId,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.jobId === 'number' && form.dy.jobId !== 0) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'jobId',
|
||||
operator: 'eq',
|
||||
value: form.dy.jobId,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'httpMethod',
|
||||
@ -234,12 +269,13 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.keywords) {
|
||||
this.$refs.search.form.root.keywords = this.keywords
|
||||
if (this.jobId) {
|
||||
this.$refs.search.selectInputKey = 'jobId'
|
||||
this.$refs.search.form.dy.jobId = this.jobId
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'keywords',
|
||||
value: this.keywords,
|
||||
type: 'root',
|
||||
field: 'jobId',
|
||||
value: this.jobId,
|
||||
type: 'dy',
|
||||
})
|
||||
}
|
||||
if (this.statusCodes) {
|
||||
@ -260,7 +296,7 @@ export default {
|
||||
type: 'dy',
|
||||
})
|
||||
},
|
||||
props: ['keywords', 'statusCodes'],
|
||||
props: ['statusCodes', 'jobId'],
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
@ -44,10 +44,18 @@
|
||||
style: 'width:20rem',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: ['root', 'keywords'],
|
||||
placeholder: $t('日志编号 / 用户编号 / 客户端IP'),
|
||||
type: 'select-input',
|
||||
field: [
|
||||
'dy',
|
||||
[
|
||||
{ label: '日志编号', key: 'id' },
|
||||
{ label: '用户编号', key: 'ownerId' },
|
||||
{ label: '客户端IP', key: 'createdClientIp' },
|
||||
],
|
||||
],
|
||||
placeholder: '匹配内容',
|
||||
style: 'width:25rem',
|
||||
selectStyle: 'width:8rem',
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
@ -178,6 +186,9 @@ export default {
|
||||
if (this.ownerId) {
|
||||
this.query.dynamicFilter.filters.push({ field: 'ownerId', operator: 'eq', value: this.ownerId })
|
||||
}
|
||||
if (this.excludeApiPathCrc32) {
|
||||
this.query.dynamicFilter.filters.push({ field: 'apiPathCrc32', operator: 'notEqual', value: this.excludeApiPathCrc32 })
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -194,10 +205,7 @@ export default {
|
||||
{
|
||||
field: 'createdTime',
|
||||
operator: 'dateRange',
|
||||
value: [
|
||||
this.$TOOL.dateFormat(new Date(new Date() - 3600 * 1000), 'yyyy-MM-dd hh:mm:ss'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'),
|
||||
],
|
||||
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -268,6 +276,13 @@ export default {
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (typeof form.dy['excludeApiPathCrc32'] === 'number' && form.dy['excludeApiPathCrc32'] !== 0) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'apiPathCrc32',
|
||||
operator: 'notEqual',
|
||||
value: form.dy['excludeApiPathCrc32'],
|
||||
})
|
||||
}
|
||||
if (typeof form.dy['apiPathCrc32'] === 'number' && form.dy['apiPathCrc32'] !== 0) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'apiPathCrc32',
|
||||
@ -284,6 +299,29 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.ownerId === 'string' && form.dy.ownerId.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'ownerId',
|
||||
operator: 'eq',
|
||||
value: form.dy.ownerId,
|
||||
})
|
||||
}
|
||||
if (typeof form.dy.id === 'string' && form.dy.id.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'id',
|
||||
operator: 'eq',
|
||||
value: form.dy.id,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.createdClientIp === 'string' && form.dy.createdClientIp.trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'createdClientIp',
|
||||
operator: 'eq',
|
||||
value: form.dy.createdClientIp,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy.operationResult === 'boolean') {
|
||||
this.query.dynamicFilter.filters.push(
|
||||
form.dy.operationResult
|
||||
@ -334,9 +372,18 @@ export default {
|
||||
this.$refs.search.form.dy.ownerId = this.ownerId
|
||||
}
|
||||
|
||||
if (this.excludeApiPathCrc32) {
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'excludeApiPathCrc32',
|
||||
value: this.excludeApiPathCrc32,
|
||||
type: 'dy',
|
||||
})
|
||||
this.$refs.search.form.dy.excludeApiPathCrc32 = this.excludeApiPathCrc32
|
||||
}
|
||||
|
||||
this.$refs.search.form.dy.createdTime = [
|
||||
this.$TOOL.dateFormat(new Date(new Date() - 3600 * 1000), 'yyyy-MM-dd hh:mm:ss'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd 00:00:00'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd 00:00:00'),
|
||||
]
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'createdTime',
|
||||
@ -344,7 +391,7 @@ export default {
|
||||
type: 'dy',
|
||||
})
|
||||
},
|
||||
props: ['keywords', 'ownerId'],
|
||||
props: ['keywords', 'ownerId', 'excludeApiPathCrc32'],
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
@ -102,6 +102,11 @@
|
||||
field="name"
|
||||
prop="dept"
|
||||
width="200" />
|
||||
<el-table-column :label="$t('最后登录')" align="right" prop="lastLoginTime" sortable="custom" width="120">
|
||||
<template #default="{ row }">
|
||||
<span v-time.tip="row.lastLoginTime" :title="row.lastLoginTime"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.enabled" @change="changeSwitch($event, row)"></el-switch>
|
||||
|
@ -70,6 +70,9 @@
|
||||
<el-switch v-model="form.enabled"></el-switch>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item v-if="mode === 'view'" :label="$t('最后登录')" prop="summary">
|
||||
<el-input v-model="form.lastLoginTime" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('备注')" prop="summary">
|
||||
<el-input v-model="form.summary" clearable type="textarea"></el-input>
|
||||
</el-form-item>
|
||||
|
Reference in New Issue
Block a user