feat: 框架代码同步 (#178)

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
2024-10-14 13:55:53 +08:00
committed by GitHub
parent dfe6b03b21
commit 58e4572723
185 changed files with 4732 additions and 1086 deletions

View File

@ -9,40 +9,40 @@
"prettier": "prettier --write ."
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"ace-builds": "^1.36.0",
"aieditor": "^1.0.14",
"axios": "^1.7.5",
"clipboard": "^2.0.11",
"core-js": "^3.38.1",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.1",
"element-plus": "^2.8.1",
"json-bigint": "^1.0.0",
"json5-to-table": "^0.1.8",
"markdown-it": "^14.1.0",
"markdown-it-emoji": "^3.0.0",
"nprogress": "^0.2.0",
"pinyin-match": "^1.2.5",
"qrcodejs2": "^0.0.2",
"sortablejs": "^1.15.2",
"vkbeautify": "^0.99.3",
"vue": "^3.4.38",
"vue-i18n": "^9.14.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"
"@element-plus/icons-vue": "2.3.1",
"ace-builds": "1.36.2",
"aieditor": "1.1.7",
"axios": "1.7.7",
"clipboard": "2.0.11",
"core-js": "3.38.1",
"cropperjs": "1.6.2",
"crypto-js": "4.2.0",
"echarts": "5.5.1",
"element-plus": "2.8.5",
"json-bigint": "1.0.0",
"json5-to-table": "0.1.8",
"markdown-it": "14.1.0",
"markdown-it-emoji": "3.0.0",
"nprogress": "0.2.0",
"pinyin-match": "1.2.6",
"qrcodejs2": "0.0.2",
"sortablejs": "1.15.3",
"vkbeautify": "0.99.3",
"vue": "3.5.12",
"vue-i18n": "10.0.4",
"vue-router": "4.4.5",
"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.2",
"prettier": "^3.3.3",
"prettier-plugin-organize-attributes": "^1.0.0",
"sass": "^1.77.8",
"terser": "^5.31.6",
"vite": "^5.4.2"
"@vitejs/plugin-vue": "5.1.4",
"prettier": "3.3.3",
"prettier-plugin-organize-attributes": "1.0.0",
"sass": "1.79.5",
"terser": "5.34.1",
"vite": "5.4.8"
},
"browserslist": [
"> 1%",

View File

@ -5,6 +5,17 @@
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 退出程序
*/
exit: {
url: `${config.API_URL}/api/probe/exit`,
name: `退出程序`,
get: async function (data = {}, config = {}) {
return await http.get(this.url, data, config)
},
},
/**
* 健康检查
*/
@ -26,4 +37,26 @@ export default {
return await http.get(this.url, data, config)
},
},
/**
* 实例下线
*/
offline: {
url: `${config.API_URL}/api/probe/offline`,
name: `实例下线`,
get: async function (data = {}, config = {}) {
return await http.get(this.url, data, config)
},
},
/**
* 停止日志计数器
*/
stopLogCounter: {
url: `${config.API_URL}/api/probe/stop.log.counter`,
name: `停止日志计数器`,
get: async function (data = {}, config = {}) {
return await http.get(this.url, data, config)
},
},
}

View File

@ -180,4 +180,15 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 启用/禁用字典内容
*/
setEnabled: {
url: `${config.API_URL}/api/sys/dic/set.enabled`,
name: `启用/禁用字典内容`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -5,6 +5,17 @@
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* Aes解密
*/
aesDecode: {
url: `${config.API_URL}/api/sys/tools/aes.decode`,
name: `Aes解密`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 执行SQL语句
*/
@ -59,15 +70,4 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 系统停机
*/
shutdown: {
url: `${config.API_URL}/api/sys/tools/shutdown`,
name: `系统停机`,
get: async function (data = {}, config = {}) {
return await http.get(this.url, data, config)
},
},
}

View File

@ -2,7 +2,7 @@
<el-table-column v-bind="$attrs">
<template #default="{ row }">
<p>{{ row.id }}</p>
<p v-if="showTime" class="time">{{ row.createdTime }}</p>
<p v-if="showTime" class="color-secondary">{{ row.createdTime }}</p>
<slot :data="row"></slot>
</template>
</el-table-column>
@ -26,8 +26,4 @@ export default {
methods: {},
}
</script>
<style scoped>
.time {
color: var(--el-text-color-secondary);
}
</style>
<style scoped></style>

View File

@ -6,13 +6,21 @@
<el-popconfirm
v-if="item.confirm"
:title="this.$t(`确定 {title}`, { title: item.title })"
@confirm="item.click(row, vue)"
@confirm="click(item, row, vue)"
width="20rem">
<template #reference>
<el-button :icon="item.icon" :title="item.title" :type="item.type" @click.native.stop size="small"></el-button>
<el-button
v-loading="loading"
:icon="item.icon"
:title="item.title"
:type="item.type"
@click.native.stop
size="small"></el-button>
</template>
</el-popconfirm>
<el-button v-else :icon="item.icon" :title="item.title" @click="item.click(row, vue)" size="small">{{ item.title }} </el-button>
<el-button v-else v-loading="loading" :icon="item.icon" :title="item.title" @click="click(item, row, vue)" size="small"
>{{ item.title }}
</el-button>
</template>
</el-button-group>
</template>
@ -32,13 +40,21 @@ export default {
prop: { type: String },
},
data() {
return {}
return {
loading: false,
}
},
mounted() {},
created() {},
components: {},
computed: {},
methods: {},
methods: {
async click(item, row, vue) {
this.loading = true
await item.click(row, vue)
this.loading = false
},
},
}
</script>
<style scoped></style>

View File

@ -1,6 +1,6 @@
<template>
<template v-for="(item, i) in options" :key="i">
<div v-if="this.$TOOL.getNestedProperty(data, this.prop)?.toLowerCase() === item.value?.toLowerCase()">
<div v-if="this.$TOOL.getNestedProperty(data, this.prop)?.toString().toLowerCase() === item.value?.toString().toLowerCase()">
<sc-status-indicator
:pulse="item.pulse"
:style="item.type ? '' : `background: #${Math.abs(this.$TOOL.crypto.hashCode(item.value)).toString(16).substring(0, 6)}`"

View File

@ -25,7 +25,8 @@
@filter-change="filterChange"
@row-contextmenu="rowContextmenu"
@sort-change="sortChange"
ref="scTable">
ref="scTable"
tooltip-effect="light">
<slot></slot>
<template v-for="(item, index) in userColumn" :key="index">
<el-table-column
@ -181,12 +182,8 @@
:command="`${menu}^|^NotAny^|^${tool.getNestedProperty(current.row, menu) ?? ''}`"
:title="$t('非其一')"></sc-contextmenu-item>
</sc-contextmenu-item>
<sc-contextmenu-item
v-if="contextOpers.includes('view')"
:title="$t('查看')"
command="view"
divided
icon="el-icon-view"></sc-contextmenu-item>
<sc-contextmenu-item :title="$t('复制')" command="copy" divided icon="el-icon-copy-document"></sc-contextmenu-item>
<sc-contextmenu-item v-if="contextOpers.includes('view')" :title="$t('查看')" command="view" icon="el-icon-view"></sc-contextmenu-item>
<sc-contextmenu-item v-if="contextOpers.includes('edit')" :title="$t('编辑')" command="edit" icon="el-icon-edit"></sc-contextmenu-item>
<sc-contextmenu-item v-if="contextOpers.includes('del')" :title="$t('删除')" command="del" icon="el-icon-delete"></sc-contextmenu-item>
<sc-contextmenu-item
@ -222,7 +219,7 @@ export default {
props: {
vue: { type: Object },
contextMenus: { type: Array },
contextOpers: { type: Array, default: ['view', 'edit', 'del'] },
contextOpers: { type: Array, default: ['copy', 'view', 'edit', 'del'] },
contextAdvs: { type: Array, default: [] },
tableName: { type: String, default: '' },
beforePost: {
@ -352,12 +349,30 @@ export default {
methods: {
async contextMenuCommand(command) {
if (typeof command === 'object') {
return command.action()
return command.action(this.vue, this.current.row)
}
if (command === 'refresh') {
this.vue.reload()
return
}
if (command === 'copy') {
let data = this.current.row[this.current.column.property]
const textarea = document.createElement('textarea')
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
textarea.value = data
document.body.appendChild(textarea)
textarea.select()
textarea.setSelectionRange(0, textarea.value.length)
const result = document.execCommand('Copy')
if (result) {
this.$message.success(this.$t('复制成功'))
}
document.body.removeChild(textarea)
return
}
if (command === 'view') {
this.vue.dialog.save = { mode: 'view', row: { id: this.current.row.id } }
return

View File

@ -5,5 +5,5 @@ export default {
//标题
//APP_NAME: "NetAdmin",
//接口地址如遇跨域需使用nginx代理
//API_URL: "https://www.fastmock.site/mock/5039c4361c39a7e3252c5b55971f1bd3/api"
API_URL: import.meta.env.VITE_API_URL,
}

View File

@ -77,6 +77,9 @@ export default {
user: null,
numbers: null,
chars: null,
hasPermission: function (p) {
return this.permissions.includes('*/*/*') || this.permissions.some((a) => a === p)
},
}
app.use(JsonViewer)

View File

@ -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.toUpperCase() }}
{{ job.lastStatusCode?.toUpperCase() }}
</div>
<div class="jobMain">
<div class="title">

View File

@ -7,7 +7,7 @@
<img class="logo" src="@/assets/img/logo.png" />
<div>
<p>{{ $CONFIG.APP_NAME }}</p>
<p class="version">{{ version }}</p>
<p class="version color-secondary">{{ version }}</p>
</div>
</div>
<ul v-if="!ismobile" class="nav">
@ -334,6 +334,5 @@ export default {
.version {
font-size: var(--el-font-size-small);
font-weight: var(--el-font-weight-primary);
color: var(--el-text-color-secondary);
}
</style>

View File

@ -480,8 +480,8 @@ export default {
应用配置: 'Application configuration',
导出文件: 'Export file',
刷新: 'Refresh',
'访问分布(Today)': 'Access distribution (Today)',
'访问趋势(Today)': 'Access trend (Today)',
'流量分布(Today)': 'Traffic distribution (Today)',
'流量趋势(Today)': 'Traffic trend (Today)',
'作业分布(Today)': 'Job distribution (Today)',
'作业趋势(Today)': 'Job trend (Today)',
后退一时: 'Back an hour',

View File

@ -477,8 +477,8 @@ export default {
应用配置: '应用配置',
导出文件: '导出文件',
刷新: '刷新',
'访问分布(Today)': '访问分布(Today)',
'访问趋势(Today)': '访问趋势(Today)',
'流量分布(Today)': '流量分布(Today)',
'流量趋势(Today)': '流量趋势(Today)',
'作业分布(Today)': '作业分布(Today)',
'作业趋势(Today)': '作业趋势(Today)',
后退一时: '后退一时',

View File

@ -532,7 +532,9 @@ textarea {
.justify-content-center {
justify-content: center;
}
.justify-content-right {
justify-content: right;
}
.font-monospace {
font-family: 'Lucida Console', 'Microsoft YaHei', monospace;
}
@ -587,4 +589,14 @@ textarea {
position: absolute;
right: 0;
}
}
.color-secondary {
color: var(--el-text-color-secondary);
}
.color-primary {
color: var(--el-text-color-primary);
}
.color-regular {
color: var(--el-text-color-regular);
}

View File

@ -306,4 +306,9 @@
.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
--el-table-row-hover-bg-color: #fffaf0;
}
.el-popper {
max-width: 90%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

View File

@ -1,5 +1,5 @@
<template>
<el-card :header="$t('访问趋势(Today)')" shadow="never" style="height: 25rem">
<el-card :header="$t('流量趋势(Today)')" shadow="never" style="height: 25rem">
<chart-bar
:api="[
{
@ -29,9 +29,9 @@ export default {
return tool
},
},
title: '访问趋势(Today)',
title: '流量趋势(Today)',
icon: 'el-icon-data-line',
description: '访问趋势(Today)',
description: '流量趋势(Today)',
components: {
ChartBar,
},

View File

@ -1,5 +1,5 @@
<template>
<el-card :header="$t('访问分布(Today)')" shadow="never" style="height: 25rem">
<el-card :header="$t('流量分布(Today)')" shadow="never" style="height: 25rem">
<chart-pie
:api="[
{
@ -29,9 +29,9 @@ export default {
return tool
},
},
title: '访问分布(Today)',
title: '流量分布(Today)',
icon: 'el-icon-data-line',
description: '访问分布(Today)',
description: '流量分布(Today)',
components: {
ChartPie,
},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -19,7 +28,15 @@
</el-header>
<el-header>
<div class="left-panel">
<na-search :controls="[]" :vue="this" @reset="onReset" @search="onSearch" ref="search" />
<na-search
:controls="[]"
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
@ -42,8 +59,18 @@
</el-header>
<el-main class="nopadding">
<sc-table
:context-menus="['id', 'userRegisterConfirm', 'enabled', 'createdTime']"
:context-menus="[
'id',
'userRegisterConfirm',
'userRegisterDept.name',
'userRegisterRole.name',
'enabled',
'createdTime',
'phoneReuseTimes',
'emailReuseTimes',
]"
:export-api="$API.sys_config.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_config.pagedQuery"
:vue="this"
@ -81,8 +108,7 @@
type: 'danger',
})
"
:vue="this"
width="150" />
:vue="this" />
</sc-table>
</el-main>
</el-container>
@ -116,6 +142,7 @@ export default {
created() {},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -136,6 +163,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_config.count.post(this.query)
this.total = res.data
},
async setEnabled(enabled) {
let loading
try {
@ -193,12 +224,12 @@ export default {
this.$refs.selectFilter.selected['enabled'] = [true]
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
@ -211,9 +242,10 @@ export default {
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -222,12 +254,15 @@ export default {
type: 'root',
})
}
this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({
field: 'enabled',
value: true,
type: 'dy',
})
this.onReset()
await this.getStatistics()
},
props: ['keywords'],
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -31,6 +40,9 @@
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -57,6 +69,7 @@
:context-menus="['id', 'name', 'sort', 'enabled', 'createdTime', 'summary']"
:default-sort="{ prop: 'sort', order: 'descending' }"
:export-api="$API.sys_dept.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_dept.query"
:vue="this"
@ -76,7 +89,7 @@
<na-col-id :label="$t('部门编号')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('部门名称')" prop="name" sortable="custom" />
<el-table-column :label="$t('排序')" align="right" prop="sort" sortable="custom" />
<el-table-column :label="$t('备注')" prop="summary" />
<el-table-column label="备注" prop="summary" sortable="custom" />
<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>
@ -127,6 +140,7 @@ export default {
created() {},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -147,6 +161,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_dept.count.post(this.query)
this.total = res.data
},
async setEnabled(enabled) {
let loading
try {
@ -204,12 +222,12 @@ export default {
this.$refs.selectFilter.selected['enabled'] = [true]
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
@ -222,9 +240,10 @@ export default {
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -233,12 +252,15 @@ export default {
type: 'root',
})
}
this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({
field: 'enabled',
value: true,
type: 'dy',
})
this.onReset()
await this.getStatistics()
},
props: ['keywords'],
watch: {},

View File

@ -1,5 +1,22 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('启用状态'),
key: 'enabled',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('启用'), value: true },
{ label: $t('禁用'), value: false },
],
},
]"
:label-width="6"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
@ -12,7 +29,11 @@
},
]"
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -21,12 +42,29 @@
icon="el-icon-plus"
type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_dic.bulkDeleteContent" :vue="this" />
<el-dropdown v-show="this.selection.length > 0">
<el-button type="primary">
{{ $t('批量操作') }}
<el-icon>
<el-icon-arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="setEnabled(true)">{{ $t('启用') }}</el-dropdown-item>
<el-dropdown-item @click="setEnabled(false)">{{ $t('禁用') }}</el-dropdown-item>
<el-dropdown-item @click="this.dialog.savebatch = { mode: 'batchedit', data: { catalogId: this.catalogId } }">{{
$t('设置项值')
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
:before-post="(data) => data.dynamicFilter.filters.length > 0"
:context-menus="['key', 'value', 'createdTime']"
:context-menus="['key', 'value', 'enabled', 'createdTime']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_dic.exportContent"
:params="query"
@ -44,7 +82,13 @@
<el-table-column type="selection" width="50" />
<el-table-column :label="$t('项名')" prop="key" sortable="custom" />
<el-table-column :label="$t('项值')" prop="value" sortable="custom" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" />
<el-table-column :label="$t('描述')" prop="summary" sortable="custom" />
<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>
</template>
</el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -67,6 +111,13 @@
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
<savebatch-dialog
v-if="dialog.savebatch"
@closed="dialog.savebatch = null"
@mounted="$refs.savebatchDialog.open(dialog.savebatch)"
@success="(data, mode) => batchsuccess(data, mode)"
ref="savebatchDialog"></savebatch-dialog>
</template>
<script>
@ -75,9 +126,11 @@ import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
const savebatchDialog = defineAsyncComponent(() => import('./savebatch.vue'))
export default {
components: {
saveDialog,
savebatchDialog,
},
computed: {
naColOperation() {
@ -104,6 +157,59 @@ export default {
},
inject: ['reload'],
methods: {
async batchsuccess(data, mode) {
if (mode === 'batchedit') {
let loading
try {
await this.$confirm(`确定修改选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning',
})
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_dic.editContent.post(Object.assign(x, { value: data }))))
this.$message.success(
`操作成功 ${res.map((x) => (x.code === 'succeed' ? 1 : 0)).reduce((a, b) => a + b, 0)}/${this.selection.length}`,
)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
}
},
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
})
this.$refs.search.search()
},
//表格内开关事件
async changeSwitch(event, row) {
try {
await this.$API.sys_dic.setEnabled.post(row)
this.$message.success(`操作成功`)
} catch {
//
}
this.$refs.table.refresh()
},
async setEnabled(enabled) {
let loading
try {
await this.$confirm(`确定${enabled ? '启用' : '禁用'}选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning',
})
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_dic.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(`操作成功 ${res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0)}/${this.selection.length}`)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
onReset() {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
},
//搜索
onSearch(form) {
this.query.dynamicFilter.filters.push({
@ -138,6 +244,14 @@ export default {
})
}
if (typeof form.dy.enabled === 'boolean') {
this.query.dynamicFilter.filters.push({
field: 'enabled',
operator: 'eq',
value: form.dy.enabled,
})
}
this.$refs.table.upData()
},

View File

@ -13,6 +13,9 @@
<el-form-item :label="$t('项值')" prop="value">
<el-input v-model="form.value" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('描述')" prop="summary">
<el-input v-model="form.summary" clearable></el-input>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">

View File

@ -0,0 +1,83 @@
<template>
<sc-dialog v-model="visible" :title="$t('批量修改')" :width="800" @closed="$emit('closed')" destroy-on-close>
<div v-loading="loading">
<el-tabs tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="10rem" ref="dialogForm">
<el-form-item :label="$t('项值')" prop="value">
<el-input v-model="form.value" clearable></el-input>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
sort></json-viewer>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
<script>
export default {
components: {},
data() {
return {
//表单数据
form: {},
loading: true,
mode: 'edit',
//验证规则
rules: {
value: [{ required: true, message: '请输入项值' }],
},
titleMap: {
edit: this.$t('批量编辑字典项'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(data) {
this.visible = true
this.loading = true
this.mode = data.mode
this.form.catalogId = data.data?.catalogId
if (data.row?.id) {
const res = await this.$API.sys_dic.getContent.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
},
//表单提交方法
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
this.$emit('success', this.form.value, this.mode)
this.visible = false
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style scoped></style>

View File

@ -1,5 +1,19 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="12">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
<el-col :lg="12">
<el-card shadow="never">
<sc-statistic :value="running" group-separator title="运行"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -50,6 +64,9 @@
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -97,6 +114,7 @@
]"
:default-sort="{ prop: 'lastExecTime', order: 'descending' }"
:export-api="$API.sys_job.export"
:on-command="this.getStatistics"
:page-size="100"
:params="query"
:query-api="$API.sys_job.pagedQuery"
@ -143,7 +161,7 @@
sortable="custom"
width="150" />
<el-table-column :label="$t('上次执行')" align="center">
<el-table-column :label="$t('状态')" align="center" prop="lastExecTime" sortable="custom" width="100">
<el-table-column :label="$t('状态')" align="center" prop="lastStatusCode" sortable="custom" width="100">
<template #default="{ row }">
<na-indicator
:data="row"
@ -234,6 +252,8 @@ export default {
created() {},
data() {
return {
total: '...',
running: '...',
dialog: {},
loading: false,
query: {
@ -255,6 +275,17 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const runningFilter = JSON.parse(JSON.stringify(this.query))
runningFilter.dynamicFilter.filters.push({
field: 'status',
operator: 'eq',
value: 'running',
})
const res = await Promise.all([this.$API.sys_job.count.post(this.query), this.$API.sys_job.count.post(runningFilter)])
this.total = res[0].data
this.running = res[1].data
},
async copyJob(row) {
let loading = this.$loading()
try {
@ -350,12 +381,12 @@ export default {
this.$refs.selectFilter.selected['enabled'] = [true]
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
@ -383,9 +414,10 @@ export default {
})
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords || this.$route.query.keywords) {
this.$refs.search.form.root.keywords = this.keywords || this.$route.query.keywords
this.$refs.search.keeps.push({
@ -394,12 +426,15 @@ export default {
type: 'root',
})
}
this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({
field: 'enabled',
value: true,
type: 'dy',
})
this.onReset()
await this.getStatistics()
},
props: ['keywords'],
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header>
<div class="left-panel">
<na-search
@ -65,6 +74,7 @@
:context-menus="['id', 'duration', 'httpMethod', 'requestUrl', 'httpStatusCode', 'createdTime', 'jobId', 'responseBody']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_job.exportRecord"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_job.pagedQueryRecord"
:vue="this"
@ -183,6 +193,7 @@ export default {
},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -201,6 +212,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_job.countRecord.post(this.query)
this.total = res.data
},
jobClick(job) {
this.dialog.job = { mode: 'view', row: { id: job.id } }
},
@ -210,7 +225,7 @@ export default {
}
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
@ -242,6 +257,14 @@ export default {
})
}
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.jobId === 'string' && form.dy.jobId.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'jobId',
@ -266,9 +289,10 @@ export default {
})
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.jobId) {
this.$refs.search.selectInputKey = 'jobId'
this.$refs.search.form.dy.jobId = this.jobId
@ -295,6 +319,7 @@ export default {
value: this.$refs.search.form.dy.createdTime,
type: 'dy',
})
await this.getStatistics()
},
props: ['statusCodes', 'jobId'],
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
v-if="showFilter"
@ -45,6 +54,7 @@
:context-opers="['view']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_loginlog.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_loginlog.pagedQuery"
:vue="this"
@ -100,6 +110,7 @@ export default {
created() {},
data() {
return {
total: '...',
dialog: {
info: false,
},
@ -117,6 +128,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_loginlog.count.post(this.query)
this.total = res.data
},
async dataChange(data) {
this.apis = []
const ips = data.data.rows?.map((x) => x.createdClientIp) ?? []
@ -136,7 +151,7 @@ export default {
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
@ -160,6 +175,7 @@ export default {
)
}
this.$refs.table.upData()
await this.getStatistics()
},
async rowClick(row) {
@ -174,7 +190,7 @@ export default {
)
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -183,6 +199,7 @@ export default {
type: 'root',
})
}
await this.getStatistics()
},
props: { keywords: { type: String }, showFilter: { type: Boolean, default: true } },
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -192,6 +201,7 @@ export default {
},
data() {
return {
total: '...',
dialog: {
info: false,
},
@ -252,6 +262,7 @@ export default {
this.owners = res[0].data
this.apis = res[1].data
this.ips = res[2]
this.total = data.data.total
},
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
@ -260,7 +271,7 @@ export default {
this.$refs.search.search()
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
@ -353,7 +364,7 @@ export default {
)
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -32,6 +41,9 @@
:vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -44,6 +56,7 @@
:context-menus="['id', 'createdUserName', 'msgType', 'title', 'summary', 'createdTime']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_sitemsg.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_sitemsg.pagedQuery"
:vue="this"
@ -119,6 +132,7 @@ export default {
created() {},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -133,6 +147,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_sitemsg.count.post(this.query)
this.total = res.data
},
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
@ -148,7 +166,7 @@ export default {
}
this.$refs.table.refresh()
},
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
@ -166,9 +184,10 @@ export default {
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -177,6 +196,7 @@ export default {
type: 'root',
})
}
await this.getStatistics()
},
props: ['keywords'],
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -49,6 +58,9 @@
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -75,6 +87,7 @@
:context-menus="['id', 'name', 'sort', 'enabled', 'ignorePermissionControl', 'dataScope', 'displayDashboard', 'createdTime']"
:default-sort="{ prop: 'sort', order: 'descending' }"
:export-api="$API.sys_role.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_role.pagedQuery"
:vue="this"
@ -173,6 +186,7 @@ export default {
created() {},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -193,6 +207,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_role.count.post(this.query)
this.total = res.data
},
async copyRole(row) {
const loading = this.$loading()
await this.$API.sys_role.create.post(Object.assign({}, row, { id: null, name: row.name + '-copy' }))
@ -274,12 +292,12 @@ export default {
this.$refs.selectFilter.selected['enabled'] = [true]
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
@ -307,9 +325,10 @@ export default {
})
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -318,12 +337,15 @@ export default {
type: 'root',
})
}
this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({
field: 'enabled',
value: true,
type: 'dy',
})
this.onReset()
await this.getStatistics()
},
props: ['keywords'],
watch: {},

View File

@ -1,5 +1,14 @@
<template>
<el-container>
<el-header v-loading="total === '...'" style="height: auto; padding: 1rem 1rem 0 1rem; display: block">
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never">
<sc-statistic :value="total" group-separator title="总数"></sc-statistic>
</el-card>
</el-col>
</el-row>
</el-header>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
@ -47,6 +56,9 @@
:vue="this"
@reset="onReset"
@search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange"
dateValueFormat="YYYY-MM-DD HH:mm:ss"
ref="search" />
</div>
<div class="right-panel">
@ -73,6 +85,7 @@
:context-opers="['view', 'edit']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:export-api="$API.sys_user.export"
:on-command="this.getStatistics"
:params="query"
:query-api="$API.sys_user.pagedQuery"
:vue="this"
@ -163,6 +176,7 @@ export default {
},
data() {
return {
total: '...',
dialog: {},
loading: false,
query: {
@ -183,6 +197,10 @@ export default {
},
inject: ['reload'],
methods: {
async getStatistics() {
const res = await this.$API.sys_user.count.post(this.query)
this.total = res.data
},
async setEnabled(enabled) {
let loading
try {
@ -231,12 +249,12 @@ export default {
this.$refs.selectFilter.selected['enabled'] = [true]
},
//搜索
onSearch(form) {
async onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
})
}
@ -249,9 +267,10 @@ export default {
}
this.$refs.table.upData()
await this.getStatistics()
},
},
mounted() {
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keeps.push({
@ -260,12 +279,15 @@ export default {
type: 'root',
})
}
this.$refs.search.form.dy.enabled = true
this.$refs.search.keeps.push({
field: 'enabled',
value: true,
type: 'dy',
})
this.onReset()
await this.getStatistics()
},
props: ['keywords', 'roleId', 'deptId'],
watch: {},