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

[skip ci]

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
2024-06-24 16:04:28 +08:00
committed by GitHub
parent d00f0d2d9c
commit 8bc8aa960c
121 changed files with 2369 additions and 1497 deletions

View File

@ -68,7 +68,7 @@
}
.app-loading__title {
font-family: 'Arial', 'Microsoft YaHei', 'monospace';
font-family: 'Microsoft YaHei', sans-serif;
font-size: 24px;
color: #333;
margin-top: 30px;
@ -125,7 +125,7 @@
<script type="text/javascript">
// 黑夜模式
if (window.localStorage.getItem('APP_DARK')) {
if (window.localStorage.getItem('APP_SET_DARK')) {
document.documentElement.classList.add('dark')
}

View File

@ -43,8 +43,17 @@ export default {
},
},
async created() {
await this.$TOOL.data.downloadConfig()
//设置深色模式
if (this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
//设置主题颜色
const app_color = this.$TOOL.data.get('APP_COLOR') ?? this.$CONFIG.COLOR
const app_color = this.$TOOL.data.get('APP_SET_COLOR') || this.$CONFIG.APP_SET_COLOR
if (app_color) {
document.documentElement.style.setProperty('--el-color-primary', app_color)
for (let i = 1; i <= 9; i++) {
@ -56,28 +65,31 @@ export default {
}
//设置布局
const layout = this.$TOOL.data.get('LAYOUT') ?? this.$CONFIG.LAYOUT
const layout = this.$TOOL.data.get('APP_SET_LAYOUT') || this.$CONFIG.APP_SET_LAYOUT
if (layout) {
this.$store.commit('SET_layout', layout)
}
//菜单是否折叠
const menuIsCollapse = this.$TOOL.data.get('MENU_IS_COLLAPSE') ?? this.$CONFIG.MENU_IS_COLLAPSE
const menuIsCollapse = this.$TOOL.data.get('APP_SET_MENU_IS_COLLAPSE') || this.$CONFIG.APP_SET_MENU_IS_COLLAPSE
if (menuIsCollapse !== this.$store.state.global.menuIsCollapse) {
this.$store.commit('TOGGLE_menuIsCollapse')
}
//是否开启多标签
const layoutTags = this.$TOOL.data.get('LAYOUT_TAGS') ?? this.$CONFIG.LAYOUT_TAGS
const layoutTags = this.$TOOL.data.get('APP_SET_MULTI_TAGS') || this.$CONFIG.APP_SET_MULTI_TAGS
if (layoutTags !== this.$store.state.global.layoutTags) {
this.$store.commit('TOGGLE_layoutTags')
}
//是否开启手风琴菜单
const menuUniqueOpened = this.$TOOL.data.get('MENU_UNIQUE_OPENED') ?? this.$CONFIG.MENU_UNIQUE_OPENED
if (menuUniqueOpened !== this.$CONFIG.MENU_UNIQUE_OPENED) {
this.$CONFIG.MENU_UNIQUE_OPENED = menuUniqueOpened
const menuUniqueOpened = this.$TOOL.data.get('APP_SET_MENU_UNIQUE_OPENED') || this.$CONFIG.APP_SET_MENU_UNIQUE_OPENED
if (menuUniqueOpened !== this.$CONFIG.APP_SET_MENU_UNIQUE_OPENED) {
this.$CONFIG.APP_SET_MENU_UNIQUE_OPENED = menuUniqueOpened
}
// 设置语言
this.$i18n.locale = this.$TOOL.data.get('APP_SET_LANG') || this.$CONFIG.APP_SET_LANG
},
}
</script>

View File

@ -103,4 +103,15 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 设置配置启用状态
*/
setEnabled: {
url: `${config.API_URL}/api/sys/config/set.enabled`,
name: `设置配置启用状态`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -93,6 +93,17 @@ export default {
},
},
/**
* 设置是否显示仪表板
*/
setDisplayDashboard: {
url: `${config.API_URL}/api/sys/role/set.display.dashboard`,
name: `设置是否显示仪表板`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 启用/禁用角色
*/
@ -103,4 +114,15 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 设置是否忽略权限控制
*/
setIgnorePermissionControl: {
url: `${config.API_URL}/api/sys/role/set.ignore.permission.control`,
name: `设置是否忽略权限控制`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -82,6 +82,17 @@ export default {
},
},
/**
* 获取当前用户应用配置
*/
getSessionUserAppConfig: {
url: `${config.API_URL}/api/sys/user/get.session.user.app.config`,
name: `获取当前用户应用配置`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 密码登录
*/
@ -214,6 +225,17 @@ export default {
},
},
/**
* 设置当前用户应用配置
*/
setSessionUserAppConfig: {
url: `${config.API_URL}/api/sys/user/set.session.user.app.config`,
name: `设置当前用户应用配置`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 当前用户信息
*/

View File

@ -1,25 +0,0 @@
<template>
<el-button @click="add" icon="el-icon-plus" type="primary"></el-button>
</template>
<style scoped></style>
<script>
export default {
emits: [],
props: { vue: { type: Object }, data: { type: Object } },
data() {
return {}
},
mounted() {},
created() {},
components: {},
computed: {},
methods: {
//添加
async add() {
this.vue.dialog.save = true
await this.vue.$nextTick()
this.vue.$refs.saveDialog.open('add', this.data)
},
},
}
</script>

View File

@ -1,9 +1,9 @@
<template>
<el-table-column :label="label" :prop="prop" sortable="custom">
<template #default="scope">
<template #default="{ row }">
<div class="avatar">
<el-avatar :src="getAvatar(scope, prop)" size="small"></el-avatar>
<span>{{ tool.getNestedProperty(scope.row, prop) }}</span>
<el-avatar :src="getAvatar(row, prop)" size="small"></el-avatar>
<span>{{ tool.getNestedProperty(row, prop) }}</span>
</div>
</template>
</el-table-column>
@ -36,8 +36,8 @@ export default {
},
methods: {
//获取头像
getAvatar(scope, prop) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR(tool.getNestedProperty(scope.row, prop))
getAvatar(row, prop) {
return row.avatar ? row.avatar : this.$CONFIG.DEFAULT_AVATAR(tool.getNestedProperty(row, prop))
},
},
}

View File

@ -1,33 +1,33 @@
<template>
<el-table-column v-bind="$attrs">
<template #default="scope">
<el-text @click="click(scope.row)" style="cursor: pointer" tag="ins">
{{ tool.getNestedProperty(scope.row, $attrs.prop) }}
</el-text>
<template #default="{ row }">
<p>{{ row.id }}</p>
<p v-if="showTime" class="time">{{ row.createdTime }}</p>
<slot :data="row"></slot>
</template>
</el-table-column>
</template>
<script>
import tool from '@/utils/tool'
export default {
emits: ['click'],
props: {},
emits: [],
props: {
showTime: {
type: Boolean,
default: true,
},
},
data() {
return {}
},
mounted() {},
created() {},
components: {},
computed: {
tool() {
return tool
},
},
methods: {
async click(row) {
this.$emit('click', row)
},
},
computed: {},
methods: {},
}
</script>
<style scoped></style>
<style scoped>
.time {
color: var(--el-text-color-secondary);
}
</style>

View File

@ -2,6 +2,7 @@
<el-table-column v-bind:="$attrs">
<template #default="{ row }">
<na-indicator :data="row" :options="options" :prop="$attrs.prop" />
<slot :data="row" name="info"></slot>
</template>
</el-table-column>
</template>

View File

@ -1,31 +1,23 @@
<template>
<el-table-column align="right">
<template #default="scope">
<template #default="{ row }">
<el-button-group>
<template v-for="(item, i) in buttons?.filter((x) => !x.condition || x.condition(scope))" :key="i">
<template v-for="(item, i) in buttons?.filter((x) => !x.condition || x.condition(row))" :key="i">
<el-popconfirm
v-if="item.confirm"
:title="this.$t(`确定 {title}`, { title: item.title })"
@confirm="item.click(scope.row, vue)"
@confirm="item.click(row, vue)"
width="20rem">
<template #reference>
<el-button :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(scope.row, vue)" size="small"
>{{ item.title }}
</el-button>
<el-button v-else :icon="item.icon" :title="item.title" @click="item.click(row, vue)" size="small">{{ item.title }} </el-button>
</template>
</el-button-group>
</template>
</el-table-column>
</template>
<style scoped>
.avatar {
display: flex;
gap: 0.5rem;
}
</style>
<script>
import naColOperation from '@/config/naColOperation'
@ -48,4 +40,5 @@ export default {
computed: {},
methods: {},
}
</script>
</script>
<style scoped></style>

View File

@ -1,15 +1,16 @@
<template>
<el-table-column :label="label" :prop="`${prop}.${field}`">
<template #default="scope">
<template v-for="(item, i) in Array.isArray(scope.row[prop]) ? scope.row[prop] : [scope.row[prop]]" :key="i">
<el-tag v-if="item" @click="$emit('click', item)">
{{ item ? item[field] : '' }}
</el-tag>
</template>
<template #default="{ row }">
<div class="flex">
<template v-for="(item, i) in Array.isArray(row[prop]) ? row[prop] : [row[prop]]" :key="i">
<el-tag v-if="item" @click="$emit('click', item)">
{{ item ? item[field] : '' }}
</el-tag>
</template>
</div>
</template>
</el-table-column>
</template>
<style scoped></style>
<script>
export default {
emits: ['click'],
@ -27,4 +28,9 @@ export default {
computed: {},
methods: {},
}
</script>
</script>
<style scoped>
.el-tag {
cursor: pointer;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<el-table-column :label="label" :prop="prop">
<template #default="scope">
{{ tool.dateFormat(scope.row[prop]) }}
<template #default="{ row }">
{{ tool.dateFormat(row[prop]) }}
</template>
</el-table-column>
</template>

View File

@ -1,24 +1,50 @@
<template>
<el-table-column v-bind="$attrs">
<template #default="scope">
<div @click="click(tool.getNestedProperty(scope.row, $attrs.prop))" class="avatar">
<el-avatar
v-if="tool.getNestedProperty(scope.row, $attrs.nestProp)"
:src="getAvatar(scope, $attrs.nestProp)"
size="small"></el-avatar>
<template #default="{ row }">
<div @click="click($TOOL.getNestedProperty(row, $attrs.prop))" class="avatar">
<el-avatar v-if="$TOOL.getNestedProperty(row, $attrs.nestProp)" :src="getAvatar(row, $attrs.nestProp)" size="small"></el-avatar>
<div>
<p>{{ tool.getNestedProperty(scope.row, $attrs.nestProp) }}</p>
<p v-if="$attrs.nestProp2">{{ tool.getNestedProperty(scope.row, $attrs.nestProp2) }}</p>
<p>{{ $TOOL.getNestedProperty(row, $attrs.nestProp) }}</p>
<p v-if="$attrs.nestProp2">{{ $TOOL.getNestedProperty(row, $attrs.nestProp2) }}</p>
</div>
</div>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" ref="saveDialog"></save-dialog>
</template>
</el-table-column>
</template>
<script>
import saveDialog from '@/views/sys/user/save.vue'
export default {
components: { saveDialog },
computed: {},
created() {},
data() {
return {
dialog: { save: false },
}
},
emits: ['click'],
methods: {
async click(id) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open({ mode: 'view', row: { id: id } })
},
//获取头像
getAvatar(row, prop) {
return row.avatar ? row.avatar : this.$CONFIG.DEFAULT_AVATAR(this.$TOOL.getNestedProperty(row, prop))
},
},
mounted() {},
props: {},
watch: {},
}
</script>
<style lang="scss" scoped>
.avatar {
div:last-child {
line-height: 1rem;
line-height: 1.2rem;
p:last-child {
color: var(--el-color-info-light-3);
}
@ -29,37 +55,4 @@
display: flex;
gap: 0.5rem;
}
</style>
<script>
import saveDialog from '@/views/sys/user/save.vue'
import tool from '@/utils/tool'
export default {
emits: ['click'],
props: {},
data() {
return {
dialog: { save: false },
}
},
mounted() {},
created() {},
components: { saveDialog },
computed: {
tool() {
return tool
},
},
methods: {
async click(id) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', { id: id })
},
//获取头像
getAvatar(scope, prop) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR(tool.getNestedProperty(scope.row, prop))
},
},
}
</script>
</style>

View File

@ -36,7 +36,8 @@ export default {
},
mounted() {},
async created() {
this.options = (await this.$API.sys_dept.query.post()).data
const res = await this.$API.sys_dept.query.post()
this.options = res.data
},
components: {},
computed: {},

View File

@ -41,7 +41,8 @@ export default {
},
mounted() {},
async created() {
this.options = (await this.$API.sys_dic.queryCatalog.post()).data
const res = await this.$API.sys_dic.queryCatalog.post()
this.options = res.data
},
components: {},
computed: {},

View File

@ -82,13 +82,16 @@ export default {
async captchaSuccess(obj) {
this.sendDisabled = true
try {
await this.$API.sys_verifycode.sendVerifyCode.post({
const res = await this.$API.sys_verifycode.sendVerifyCode.post({
destDevice: this.form[Array.isArray(this.phoneField) ? this.phoneField[1] : this.phoneField],
type: 'login',
deviceType: 'mobile',
verifyCaptchaReq: obj,
})
this.$message.success(this.$t('发送成功'))
if (res.data?.code) {
this.$message.success(res.data.code.toString())
}
this.waitSecs = 60
const t = setInterval(() => {
this.waitSecs -= 1

View File

@ -1,6 +1,6 @@
<template>
<template v-for="(item, i) in options" :key="i">
<div v-if="tool.getNestedProperty(data, this.prop) === item.value">
<div v-if="this.$TOOL.getNestedProperty(data, this.prop)?.toLowerCase() === item.value?.toLowerCase()">
<sc-status-indicator
:pulse="item.pulse"
:style="item.type ? '' : `background: #${Math.abs(this.$TOOL.crypto.hashCode(item.value)).toString(16).substring(0, 6)}`"
@ -9,11 +9,8 @@
<slot :data="data" :text="item.text"></slot>
</div>
</template>
<slot :data="data" name="info"></slot>
</template>
<script>
import tool from '@/utils/tool'
export default {
emits: [],
props: {
@ -27,11 +24,7 @@ export default {
mounted() {},
created() {},
components: {},
computed: {
tool() {
return tool
},
},
computed: {},
methods: {},
}
</script>

View File

@ -66,7 +66,7 @@
<el-button @click="reset" icon="el-icon-refresh-left">{{ $t('重置') }}</el-button>
</template>
<v-ace-editor
:theme="this.$TOOL.data.get('APP_DARK') ? 'github_dark' : 'github'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
:value="vkbeautify().json(vue.query, 2)"
lang="json"
style="height: 20rem; width: 100%" />
@ -323,6 +323,7 @@ export default {
casLoaded: false,
keepKeywords: null,
keepCreatedTime: null,
keepHttpStatusCode: null,
form: {
root: {},
filter: {},
@ -404,6 +405,9 @@ export default {
if (this.keepCreatedTime) {
this.form.dy.createdTime = this.keepCreatedTime
}
if (this.keepHttpStatusCode) {
this.form.dy.httpStatusCode = this.keepHttpStatusCode
}
this.$emit('reset')
this.search()
},

View File

@ -25,7 +25,7 @@ export default {
const contents = []
const msg = h('p', { style: 'width:230px;display:flex;justify-content:space-between' }, [
h('span', {}, this.$t('即将开始更新……')),
h('a', { style: 'color:#409eff', href: 'javascript:window.location.reload()' }, this.$t('立即更新')),
h('a', { style: 'color:#21A675', href: 'javascript:window.location.reload()' }, this.$t('立即更新')),
])
const task = h('p', { style: 'font-weight:bold' }, version)
const progress = h(
@ -37,7 +37,7 @@ export default {
style: {
width: '230px',
height: '6px',
'background-color': '#409eff',
'background-color': '#21A675',
'margin-top': '6px',
'border-radius': '6px',
},

View File

@ -1,5 +1,5 @@
const T = {
color: ['#409EFF', '#36CE9E', '#f56e6a', '#626c91', '#edb00d', '#909399'],
color: ['#21A675', '#36CE9E', '#f56e6a', '#626c91', '#edb00d', '#909399'],
grid: {
left: '3%',
right: '3%',

View File

@ -8,15 +8,13 @@
</el-tag>
</el-table-column>
<el-table-column :label="$t('列名')" prop="label">
<template #default="scope">
<el-tag :effect="scope.row.hide ? 'light' : 'dark'" :type="scope.row.hide ? 'info' : ''" disable-transitions round
>{{ scope.row.label }}
</el-tag>
<template #default="{ row }">
<el-tag :effect="row.hide ? 'light' : 'dark'" :type="row.hide ? 'info' : ''" disable-transitions round>{{ row.label }} </el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('显示')" prop="hide" width="60">
<template #default="scope">
<el-switch v-model="scope.row.hide" :active-value="false" :inactive-value="true" size="small" />
<template #default="{ row }">
<el-switch v-model="row.hide" :active-value="false" :inactive-value="true" size="small" />
</template>
</el-table-column>
</el-table>

View File

@ -440,7 +440,7 @@ export default {
}
.sc-file-select__item__file .item-file.item-file-doc {
color: #409eff;
color: #21a675;
}
.sc-file-select__item__upload {

View File

@ -125,7 +125,7 @@ export default {
.sc-filter-my-list li:hover {
background: #ecf5ff;
color: #409eff;
color: #21a675;
}
.sc-filter-my-list li label {

View File

@ -53,7 +53,7 @@ export default {
.sc-page-header__icon span {
width: 2.5rem;
height: 2.5rem;
background: #409eff;
background: #21a675;
border-radius: 40%;
display: flex;
align-items: center;

View File

@ -76,6 +76,7 @@ export default {
}
.sc-statistic-content-value {
font-family: 'Lucida Console', 'Microsoft YaHei', monospace;
font-weight: bold;
}

View File

@ -43,11 +43,11 @@ export default {
files: {
doc: {
icon: 'sc-icon-file-word-2-fill',
color: '#409eff',
color: '#21A675',
},
docx: {
icon: 'sc-icon-file-word-2-fill',
color: '#409eff',
color: '#21A675',
},
xls: {
icon: 'sc-icon-file-excel-2-fill',

View File

@ -10,57 +10,51 @@ const DEFAULT_CONFIG = {
//首页地址
DASHBOARD_URL: '/home',
//版本号
APP_VER: '1.0.0',
//内核版本号
CORE_VER: '1.6.9',
//接口地址
API_URL: '',
//请求超时
TIMEOUT: 10000,
//TokenName
TOKEN_NAME: 'Authorization',
//Token前缀注意最后有个空格如不需要需设置空字符串
TOKEN_PREFIX: 'Bearer ',
//追加其他头
HEADERS: {},
//请求是否开启缓存
REQUEST_CACHE: false,
//布局 默认default | 通栏header | 经典menu | 功能坞dock
//dock将关闭标签和面包屑栏
LAYOUT: 'menu',
//菜单是否折叠
MENU_IS_COLLAPSE: false,
//菜单是否启用手风琴效果
MENU_UNIQUE_OPENED: false,
//是否开启多标签
LAYOUT_TAGS: true,
//语言
LANG: 'zh-cn',
//主题颜色
COLOR: '#21A675',
//是否加密localStorage, 为空不加密可填写AES(模式ECB,移位Pkcs7)加密
LS_ENCRYPTION: '',
//localStorageAES加密秘钥位数建议填写8的倍数
LS_ENCRYPTION_key: '2XNN4K8LC0ELVWN4',
//控制台首页默认布局
DEFAULT_GRID: {
//布局 默认default | 通栏header | 经典menu | 功能坞dock
//dock将关闭标签和面包屑栏
APP_SET_LAYOUT: 'menu',
//菜单是否折叠
APP_SET_MENU_IS_COLLAPSE: false,
//菜单是否启用手风琴效果
APP_SET_MENU_UNIQUE_OPENED: false,
//是否开启多标签
APP_SET_MULTI_TAGS: true,
//语言
APP_SET_LANG: 'zh-cn',
//自动退出
APP_SET_AUTO_EXIT: 0,
//深色模式
APP_SET_DARK: false,
//主题颜色
APP_SET_COLOR: '#21A675',
//控制台首页布局
APP_SET_HOME_GRID: {
//默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
layout: [8, 8, 8, 12, 12, 12, 12],
//小组件分布com取值:views/home/components 文件名

View File

@ -3,21 +3,13 @@ export default {
{
icon: 'el-icon-view',
click: async (row, vue) => {
vue.loading = true
vue.dialog.save = true
await vue.$nextTick()
await vue.$refs.saveDialog.open('view', row)
vue.loading = false
vue.dialog.save = { row, mode: 'view' }
},
},
{
icon: 'el-icon-edit',
click: async (row, vue) => {
vue.loading = true
vue.dialog.save = true
await vue.$nextTick()
await vue.$refs.saveDialog.open('edit', row)
vue.loading = false
vue.dialog.save = { row, mode: 'edit' }
},
},
],

View File

@ -46,7 +46,6 @@ import scWaterMark from '@/components/scWaterMark'
// net-admin组件
import naArea from '@/components/naArea/index.vue'
import naButtonAdd from '@/components/naButtonAdd/index.vue'
import naButtonBulkDel from '@/components/naButtonBulkDel/index.vue'
import naColAvatar from '@/components/naColAvatar'
import naColId from '@/components/naColId/index.vue'
@ -87,7 +86,6 @@ export default {
// net-admin组件
app.component('naArea', naArea)
app.component('naButtonAdd', naButtonAdd)
app.component('naButtonBulkDel', naButtonBulkDel)
app.component('naColAvatar', naColAvatar)
app.component('naColId', naColId)

View File

@ -17,7 +17,7 @@
<el-menu
:default-active="$route.meta.active || $route.fullPath"
@select="select"
active-text-color="#409EFF"
active-text-color="#21A675"
background-color="#424c50"
router
text-color="#fff">

View File

@ -45,27 +45,25 @@
<el-button @click="refresh" circle icon="el-icon-refresh"></el-button>
</el-footer>
</el-container>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" ref="saveDialog"></save-dialog>
<save-dialog v-if="dialog.save" @closed="dialog.save = null" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from '@/views/sys/job/save.vue'
import { defineAsyncComponent } from 'vue'
const saveDialog = defineAsyncComponent(() => import('@/views/sys/job/all/save.vue'))
export default {
components: {
saveDialog,
},
data() {
return {
dialog: {
save: false,
},
dialog: {},
loading: false,
jobs: [],
}
},
mounted() {
this.getData()
},
inject: ['reload'],
methods: {
async getData() {
this.loading = true
@ -77,11 +75,14 @@ export default {
this.getData()
},
async view(job) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', { id: job.id })
this.dialog.save = { mode: 'view', row: { id: job.id }, tabId: 'record' }
},
},
mounted() {
this.getData()
},
props: [],
watch: {},
}
</script>

View File

@ -78,37 +78,31 @@
</template>
<script>
import search from './search.vue'
import tasks from './tasks.vue'
import message from '@/views/profile/message/components/list.vue'
import { defineAsyncComponent } from 'vue'
import avatar from '../../utils/avatar'
const search = defineAsyncComponent(() => import('./search.vue'))
const tasks = defineAsyncComponent(() => import('./tasks.vue'))
const message = defineAsyncComponent(() => import('@/views/profile/message/components/list.vue'))
export default {
computed: {
avatar() {
return avatar
},
},
components: {
search,
tasks,
message,
},
watch: {
'config.dark'(val) {
if (val) {
document.documentElement.classList.add('dark')
this.$TOOL.data.set('APP_DARK', val)
} else {
document.documentElement.classList.remove('dark')
this.$TOOL.data.remove('APP_DARK')
}
computed: {
avatar() {
return avatar
},
},
async created() {
this.user = this.$GLOBAL.user
const res = await this.$API.sys_sitemsg.unreadCount.post()
this.unreadCnt = res.data
},
data() {
return {
config: {
dark: this.$TOOL.data.get('APP_DARK') || false,
dark: this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK,
},
user: {},
userName: '',
@ -119,12 +113,21 @@ export default {
unreadCnt: 0,
}
},
async created() {
this.user = this.$GLOBAL.user
const res = await this.$API.sys_sitemsg.unreadCount.post()
this.unreadCnt = res.data
},
methods: {
clearData(fullClear) {
const loading = this.$loading()
this.$TOOL.cookie.clear()
if (fullClear) {
this.$TOOL.data.clear()
} else {
this.$TOOL.data.clearAppSet()
}
this.$router.replace({ path: '/guest/login' })
setTimeout(() => {
loading.close()
location.reload()
}, 1000)
},
configDark() {
this.config.dark = !this.config.dark
},
@ -133,7 +136,7 @@ export default {
this.msg = false
},
//个人信息
handleUser(command) {
async handleUser(command) {
if (command === 'uc') {
this.$router.push({ path: '/profile/account' })
}
@ -141,36 +144,22 @@ export default {
this.$router.push({ path: '/cmd' })
}
if (command === 'clearCache') {
this.$confirm(this.$t('清除缓存会将系统初始化,包括登录状态、主题、语言设置等,是否继续?'), this.$t('提示'), {
type: 'info',
})
.then(() => {
const loading = this.$loading()
this.$TOOL.data.clear()
this.$TOOL.cookie.clear()
this.$router.replace({ path: '/guest/login' })
setTimeout(() => {
loading.close()
location.reload()
}, 1000)
})
.catch(() => {
//取消
try {
await this.$confirm(this.$t('清除缓存会将系统初始化,包括登录状态、主题、语言设置等,是否继续?'), this.$t('提示'), {
type: 'info',
})
this.clearData(true)
} catch {}
}
if (command === 'outLogin') {
this.$confirm(this.$t('确认是否退出当前用户?'), this.$t('提示'), {
type: 'warning',
confirmButtonText: this.$t('退出'),
confirmButtonClass: 'el-button--danger',
})
.then(() => {
this.$TOOL.cookie.clear()
this.$router.replace({ path: '/guest/login' })
})
.catch(() => {
//取消退出
try {
await this.$confirm(this.$t('确认是否退出当前用户?'), this.$t('提示'), {
type: 'warning',
confirmButtonText: this.$t('退出'),
confirmButtonClass: 'el-button--danger',
})
this.clearData(false)
} catch {}
}
},
//全屏
@ -190,6 +179,18 @@ export default {
this.tasksVisible = true
},
},
props: [],
watch: {
'config.dark'(val) {
if (val) {
document.documentElement.classList.add('dark')
this.$TOOL.data.set('APP_SET_DARK', val)
} else {
document.documentElement.classList.remove('dark')
this.$TOOL.data.remove('APP_SET_DARK')
}
},
},
}
</script>

View File

@ -29,7 +29,7 @@
</div>
<div class="adminui-side-scroll">
<el-scrollbar>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED" router>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.APP_SET_MENU_UNIQUE_OPENED" router>
<NavMenu :navMenus="nextMenu"></NavMenu>
</el-menu>
</el-scrollbar>
@ -74,7 +74,7 @@
<div v-if="!ismobile" :class="menuIsCollapse ? 'aminui-side isCollapse' : 'aminui-side'">
<div class="adminui-side-scroll">
<el-scrollbar>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED" router>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.APP_SET_MENU_UNIQUE_OPENED" router>
<NavMenu :navMenus="menu"></NavMenu>
</el-menu>
</el-scrollbar>
@ -172,7 +172,7 @@
</div>
<div class="adminui-side-scroll">
<el-scrollbar>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.MENU_UNIQUE_OPENED" router>
<el-menu :collapse="menuIsCollapse" :default-active="active" :unique-opened="$CONFIG.APP_SET_MENU_UNIQUE_OPENED" router>
<NavMenu :navMenus="nextMenu"></NavMenu>
</el-menu>
</el-scrollbar>

View File

@ -2,7 +2,7 @@ export default {
render() {},
data() {
return {
logoutCount: this.$TOOL.data.get('AUTO_EXIT'),
logoutCount: this.$TOOL.data.get('APP_SET_AUTO_EXIT') || this.$CONFIG.APP_SET_AUTO_EXIT,
}
},
mounted() {

View File

@ -19,7 +19,7 @@ const messages = {
}
const i18n = createI18n({
locale: tool.data.get('APP_LANG') || sysConfig.LANG,
locale: tool.data.get('APP_SET_LANG') || sysConfig.APP_SET_LANG,
fallbackLocale: 'zh-cn',
globalInjection: true,
messages,

View File

@ -74,7 +74,7 @@ export default {
启用状态: 'Enabled status',
响应状态码: 'Response status code',
响应码: 'Response code',
唯一编码: 'Unique code',
唯一编码: 'Unique ID',
图标名称: 'Icon name',
图标选择器: 'Icon selector',
地区: 'Region',
@ -448,4 +448,28 @@ export default {
返回首页: 'Return to homepage',
重新登录: 'Re-login',
返回上一页: 'Return to previous page',
批量操作: 'Batch operation',
启用用户: 'Enable user',
禁用用户: 'Disable user',
'确定要 {operator} 选中的 {count} 项吗?': 'Are you sure you want to {operator} the selected {count} items?',
'操作成功 {count}/{total} 项': '{count}/{total} items operation successful',
启用角色: 'Enable role',
禁用角色: 'Disable role',
启用部门: 'Enable department',
禁用部门: 'Disable department',
启用配置: 'Enable configuration',
禁用配置: 'Disable configuration',
启用作业: 'Enable job',
禁用作业: 'Disable job',
重置为默认值: 'Reset to default value',
'确定将当前主题设置恢复默认值吗?': 'Are you sure you want to reset the current theme settings to default?',
'确定将当前设置恢复默认值吗?': 'Are you sure you want to reset the current settings to default?',
作业信息: 'Job information',
查看作业记录: 'View job records',
异常作业: 'Abnormal jobs',
所有作业: 'All jobs',
用户列表: 'User list',
: 'Yes',
: 'No',
手机: 'Mobile',
}

View File

@ -447,4 +447,27 @@ export default {
返回首页: '返回首页',
重新登录: '重新登录',
返回上一页: '返回上一页',
批量操作: '批量操作',
启用用户: '启用用户',
禁用用户: '禁用用户',
'确定要 {operator} 选中的 {count} 项吗?': '确定要 {operator} 选中的 {count} 项吗?',
'操作成功 {count}/{total} 项': '操作成功 {count}/{total} 项',
启用角色: '启用角色',
禁用角色: '禁用角色',
启用部门: '启用部门',
禁用部门: '禁用部门',
启用配置: '启用配置',
禁用配置: '禁用配置',
启用作业: '启用作业',
禁用作业: '禁用作业',
'确定将当前主题设置恢复默认值吗?': '确定将当前主题设置恢复默认值吗?',
'确定将当前设置恢复默认值吗?': '确定将当前设置恢复默认值吗?',
作业信息: '作业信息',
查看作业记录: '查看作业记录',
异常作业: '异常作业',
所有作业: '所有作业',
用户列表: '用户列表',
: '是',
: '否',
手机: '手机',
}

View File

@ -5,11 +5,11 @@ export default {
//移动端布局
ismobile: false,
//布局
layout: config.LAYOUT,
layout: config.APP_SET_LAYOUT,
//菜单是否折叠 toggle
menuIsCollapse: config.MENU_IS_COLLAPSE,
menuIsCollapse: config.APP_SET_MENU_IS_COLLAPSE,
//多标签栏
layoutTags: config.LAYOUT_TAGS,
layoutTags: config.APP_SET_MULTI_TAGS,
//主题
theme: config.THEME,
},

View File

@ -6,7 +6,7 @@ html {
height: 100%;
background-color: #f6f8f9;
font-size: var(--el-font-size-base);
font-family: 'Arial', 'Microsoft YaHei', 'monospace';
font-family: 'Microsoft YaHei', sans-serif;
}
a {
@ -92,7 +92,7 @@ textarea {
bottom: 10rem;
right: 0;
z-index: 100;
background: #409eff;
background: #21a675;
display: flex;
flex-direction: column;
align-items: center;
@ -235,7 +235,7 @@ textarea {
}
.aminui-side-split li.active {
background: #409eff;
background: #21a675;
}
.adminui-side-split-scroll::-webkit-scrollbar-thumb {
@ -410,7 +410,7 @@ textarea {
}
.adminui-tags li.active {
background: #409eff;
background: #21a675;
}
.adminui-tags li.active a {

View File

@ -1,7 +1,7 @@
/* 覆盖element-plus样式 */
:root {
--el-color-primary: #409eff;
--el-color-primary: #21a675;
--el-color-primary-light-1: #53a7ff;
--el-color-primary-light-2: #66b1ff;
--el-color-primary-light-3: #79bbff;
@ -44,6 +44,7 @@
.el-date-editor {
--el-date-editor-daterange-width: 20rem;
--el-date-editor-datetimerange-width: 30rem;
}
.el-menu {
@ -167,7 +168,7 @@
.el-table {
td {
font-family: 'Lucida Console', 'Microsoft YaHei', 'monospace';
font-family: 'Lucida Console', 'Microsoft YaHei', monospace;
}
.el-link:after {

View File

@ -26,7 +26,6 @@ export default {
text.setAttribute('text-anchor', 'middle')
text.setAttribute('font-size', '16')
text.setAttribute('font-weight', '900')
text.setAttribute('font-family', 'monospace')
// IE/Edge don't support alignment-baseline
// @see https://msdn.microsoft.com/en-us/library/gg558060(v=vs.85).aspx

View File

@ -12,7 +12,35 @@ const tool = {}
/* localStorage */
tool.data = {
set(key, data, datetime = 0) {
configJson: null,
async uploadConfig() {
try {
const json = JSON.stringify(Object.entries(localStorage).filter((x) => x[0].indexOf('APP_SET_') === 0))
if (this.configJson !== json) {
this.configJson = json
const userApi = await import('@/api/sys/user')
await userApi.default.setSessionUserAppConfig.post({
appConfig: this.configJson,
})
}
} catch {}
},
clearAppSet() {
Object.entries(localStorage)
.filter((x) => x[0].indexOf('APP_SET_') === 0)
.map((x) => localStorage.removeItem(x[0]))
},
async downloadConfig() {
try {
const userApi = await import('@/api/sys/user')
const res = await userApi.default.getSessionUserAppConfig.post({})
this.clearAppSet()
for (const item of JSON.parse(res.data.appConfig)) {
localStorage.setItem(item[0], item[1])
}
} catch {}
},
async set(key, data, datetime = 0) {
//加密
if (sysConfig.LS_ENCRYPTION === 'AES') {
data = tool.crypto.AES.encrypt(JSON.stringify(data), sysConfig.LS_ENCRYPTION_key)
@ -21,7 +49,9 @@ tool.data = {
content: data,
datetime: parseInt(datetime) === 0 ? 0 : new Date().getTime() + parseInt(datetime) * 1000,
}
return localStorage.setItem(key, JSON.stringify(cacheValue))
const ret = localStorage.setItem(key, JSON.stringify(cacheValue))
await this.uploadConfig()
return ret
},
get(key) {
try {
@ -43,11 +73,15 @@ tool.data = {
return null
}
},
remove(key) {
return localStorage.removeItem(key)
async remove(key) {
const ret = localStorage.removeItem(key)
await this.uploadConfig()
return ret
},
clear() {
return localStorage.clear()
async clear() {
const ret = localStorage.clear()
await this.uploadConfig()
return ret
},
}

View File

@ -49,7 +49,7 @@ export default {
},
},
mounted() {
this.autoLogin = this.$TOOL.data.get('AUTO_LOGIN')
this.autoLogin = this.$TOOL.data.get('AUTO_LOGIN') || false
},
methods: {
async login() {

View File

@ -69,8 +69,8 @@ export default {
data() {
return {
config: {
lang: this.$TOOL.data.get('APP_LANG') || this.$CONFIG.LANG,
dark: this.$TOOL.data.get('APP_DARK') || false,
lang: this.$TOOL.data.get('APP_SET_LANG') || this.$CONFIG.APP_SET_LANG,
dark: this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK,
},
lang: [
{
@ -88,15 +88,15 @@ export default {
'config.dark'(val) {
if (val) {
document.documentElement.classList.add('dark')
this.$TOOL.data.set('APP_DARK', val)
this.$TOOL.data.set('APP_SET_DARK', val)
} else {
document.documentElement.classList.remove('dark')
this.$TOOL.data.remove('APP_DARK')
this.$TOOL.data.remove('APP_SET_DARK')
}
},
'config.lang'(val) {
this.$i18n.locale = val
this.$TOOL.data.set('APP_LANG', val)
this.$TOOL.data.set('APP_SET_LANG', val)
},
},
created: function () {
@ -105,7 +105,7 @@ export default {
this.$TOOL.data.remove('MENU')
this.$TOOL.data.remove('PERMISSIONS')
this.$TOOL.data.remove('DASHBOARD_GRID')
this.$TOOL.data.remove('grid')
this.$TOOL.data.remove('APP_SET_HOME_GRID')
this.$store.commit('clearViewTags')
this.$store.commit('clearKeepLive')
this.$store.commit('clearIframeList')

View File

@ -1,7 +1,8 @@
<template>
<el-main>
<widgets v-if="dashboard" @on-mounted="onMounted"></widgets>
<work v-else @on-mounted="onMounted"></work>
<div v-if="loading" v-loading="true" style="height: 100%"></div>
<el-main v-else>
<widgets v-if="dashboard"></widgets>
<work v-else></work>
</el-main>
</template>
@ -18,20 +19,19 @@ export default {
},
data() {
return {
pageLoading: true,
loading: true,
dashboard: false,
}
},
created() {
async created() {
//下载配置
await this.$TOOL.data.downloadConfig()
this.dashboard = this.$GLOBAL.user.roles.findIndex((x) => x.displayDashboard) >= 0
this.loading = false
},
mounted() {},
methods: {
onMounted() {
this.pageLoading = false
},
},
methods: {},
}
</script>
<style></style>
<style scoped></style>

View File

@ -1,7 +1,7 @@
<template>
<el-card v-loading="loading" class="main" shadow="never">
<div class="wrap">
<img alt="" src="@/assets/img/logo.png" />
<img alt="" src="@/assets/img/logo.png" width="200" />
<h2>{{ packageJson.name }}</h2>
<p>{{ ver }}</p>
<el-link href="https://github.com/nsnail/NetAdmin" target="_blank">{{ $t('喜欢就点个 Star⭐ 吧!') }}</el-link>

View File

@ -146,7 +146,7 @@ export default {
customizing: false,
allComps: allComps,
selectLayout: [],
defaultGrid: this.$CONFIG.DEFAULT_GRID,
defaultGrid: this.$CONFIG.APP_SET_HOME_GRID,
grid: [],
}
},
@ -225,14 +225,14 @@ export default {
save() {
this.customizing = false
this.$refs.widgets.style.removeProperty('transform')
this.$TOOL.data.set('grid', this.grid)
this.$TOOL.data.set('APP_SET_HOME_GRID', this.grid)
},
//恢复默认
backDefault() {
this.customizing = false
this.$refs.widgets.style.removeProperty('transform')
this.grid = JSON.parse(JSON.stringify(this.defaultGrid))
this.$TOOL.data.remove('grid')
this.$TOOL.data.remove('APP_SET_HOME_GRID')
},
//关闭
close() {
@ -241,7 +241,7 @@ export default {
this.loadGrid()
},
loadGrid() {
this.grid = this.$TOOL.data.get('grid') || JSON.parse(JSON.stringify(this.defaultGrid))
this.grid = this.$TOOL.data.get('APP_SET_HOME_GRID') || JSON.parse(JSON.stringify(this.defaultGrid))
},
},
}
@ -259,6 +259,7 @@ export default {
flex: 1;
overflow: auto;
overflow-x: hidden;
font-family: 'Lucida Console', 'Microsoft YaHei', monospace;
}
.widgets-aside {

View File

@ -84,7 +84,7 @@ export default {
},
getMods() {
//这里可用改为读取远程数据
this.myModsName = this.$TOOL.data.get('MY_MODS') || []
this.myModsName = this.$TOOL.data.get('APP_SET_MY_MODS') || []
this.filterMenu(this.$GLOBAL.menu)
this.myMods = this.mods.filter((item) => {
return this.myModsName.includes(item.name)
@ -110,7 +110,7 @@ export default {
},
saveMods() {
this.$TOOL.data.set(
'MY_MODS',
'APP_SET_MY_MODS',
this.myMods.map((v) => v.name),
)
this.$message.success(this.$t('设置常用成功'))
@ -178,8 +178,8 @@ export default {
.modItem-add:hover,
.modItem-add:hover i {
border-color: #409eff;
color: #409eff !important;
border-color: #21a675;
color: #21a675 !important;
}
.setMods {

View File

@ -34,18 +34,42 @@
</el-form>
</el-card>
<set-mobile-dialog v-if="dialog.setMobile" @closed="dialog.setMobile = false" @success="setSuccess" ref="setMobileDialog"></set-mobile-dialog>
<set-password-dialog v-if="dialog.setPassword" @closed="dialog.setPassword = false" ref="setPasswordDialog"></set-password-dialog>
<set-email-dialog v-if="dialog.setEmail" @closed="dialog.setEmail = false" @success="setSuccess" ref="setEmailDialog"></set-email-dialog>
<set-mobile-dialog
v-if="dialog.setMobile"
@closed="dialog.setMobile = null"
@mounted="$refs.setMobileDialog.open(dialog.setMobile)"
@success="setSuccess"
ref="setMobileDialog"></set-mobile-dialog>
<set-password-dialog
v-if="dialog.setPassword"
@closed="dialog.setPassword = null"
@mounted="$refs.setPasswordDialog.open(dialog.setPassword)"
ref="setPasswordDialog"></set-password-dialog>
<set-email-dialog
v-if="dialog.setEmail"
@closed="dialog.setEmail = null"
@mounted="$refs.setEmailDialog.open(dialog.setEmail)"
@success="setSuccess"
ref="setEmailDialog"></set-email-dialog>
</template>
<script>
import setMobileDialog from '@/views/profile/account/set-mobile.vue'
import setPasswordDialog from '@/views/profile/account/set-password.vue'
import setEmailDialog from '@/views/profile/account/set-email.vue'
import { defineAsyncComponent } from 'vue'
const setMobileDialog = defineAsyncComponent(() => import('@/views/profile/account/set-mobile.vue'))
const setPasswordDialog = defineAsyncComponent(() => import('@/views/profile/account/set-password.vue'))
const setEmailDialog = defineAsyncComponent(() => import('@/views/profile/account/set-email.vue'))
export default {
components: { setMobileDialog, setPasswordDialog, setEmailDialog },
created() {
this.form = this.$GLOBAL.user
},
data() {
return {
dialog: {},
form: {},
}
},
methods: {
updateUser(res) {
try {
@ -59,34 +83,17 @@ export default {
this.form = this.$GLOBAL.user = data
},
async setPasswordClick() {
this.dialog.setPassword = true
await this.$nextTick()
this.$refs.setPasswordDialog.open()
this.dialog.setPassword = {}
},
async setEmailClick() {
this.dialog.setEmail = true
await this.$nextTick()
this.$refs.setEmailDialog.open()
this.dialog.setEmail = {}
},
async setMobileClick() {
this.dialog.setMobile = true
await this.$nextTick()
this.$refs.setMobileDialog.open(this.form.mobile ? 'edit' : 'add')
this.dialog.setMobile = { mode: this.form.mobile ? 'edit' : 'add' }
},
},
created() {
this.form = this.$GLOBAL.user
},
data() {
return {
dialog: {
setPassword: false,
setMobile: false,
},
form: {},
}
},
props: [],
watch: {},
}
</script>

View File

@ -29,7 +29,7 @@
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>
</template>
@ -40,17 +40,42 @@ import phoneConfig from '@/config/naFormPhone'
import emailConfig from '@/config/naFormEmail'
export default {
created() {},
components: {
naFormPhone,
},
created() {},
data() {
return {
//表单数据
form: {
verifySmsCodeReq: {},
},
loading: false,
//验证规则
rules: {
verifySmsCodeReq: {
destDevice: phoneConfig.mobile(this),
code: phoneConfig.code(this),
},
destDevice: [emailConfig.email(this)],
code: emailConfig.code(),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
open() {
this.visible = true
return this
},
//表单提交方法
async submit() {
if (!(await this.$refs.form.validate().catch(() => {}))) {
const valid = await this.$refs.form.validate().catch(() => {})
if (!valid) {
return false
}
@ -64,23 +89,8 @@ export default {
this.loading = false
},
},
data() {
return {
visible: false,
loading: false,
form: {
verifySmsCodeReq: {},
},
rules: {
verifySmsCodeReq: {
destDevice: phoneConfig.mobile(this),
code: phoneConfig.code(this),
},
destDevice: [emailConfig.email(this)],
code: emailConfig.code(),
},
}
mounted() {
this.$emit('mounted')
},
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" :width="800" @closed="$emit('closed')" destroy-on-close>
<el-dialog v-model="visible" :title="`${titleMap[mode]}`" :width="800" @closed="$emit('closed')" destroy-on-close>
<el-form :model="form" :rules="rules" label-position="top" ref="form">
<el-row class="items-center justify-content-center">
<el-col v-if="mode === 'edit'" :lg="10">
@ -34,7 +34,7 @@
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>
</template>
@ -44,18 +44,50 @@ import naFormPhone from '@/components/naFormPhone/index.vue'
import phoneConfig from '@/config/naFormPhone'
export default {
created() {},
components: {
naFormPhone,
},
created() {},
data() {
return {
//表单数据
form: {
newverifySmsCodeReq: {},
originverifySmsCodeReq: {},
},
loading: false,
mode: 'add',
//验证规则
rules: {
originverifySmsCodeReq: {
destDevice: phoneConfig.mobile(this),
code: phoneConfig.code(this),
},
newverifySmsCodeReq: {
destDevice: [phoneConfig.mobile(this), phoneConfig.mobileNoUsed(this, () => this.$GLOBAL.user.id)],
code: phoneConfig.code(this),
},
},
titleMap: {
add: this.$t('绑定手机'),
edit: this.$t('更换手机'),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
open(mode = 'add') {
this.mode = mode
//显示
open(data) {
this.mode = data.mode
this.visible = true
return this
},
//表单提交方法
async submit() {
if (!(await this.$refs.form.validate().catch(() => {}))) {
const valid = await this.$refs.form.validate().catch(() => {})
if (!valid) {
return false
}
@ -69,30 +101,8 @@ export default {
this.loading = false
},
},
data() {
return {
mode: 'add',
titleMap: {
add: this.$t('绑定手机'),
edit: this.$t('更换手机'),
},
visible: false,
loading: false,
form: {
newverifySmsCodeReq: {},
originverifySmsCodeReq: {},
},
rules: {
originverifySmsCodeReq: {
destDevice: phoneConfig.mobile(this),
code: phoneConfig.code(this),
},
newverifySmsCodeReq: {
destDevice: [phoneConfig.mobile(this), phoneConfig.mobileNoUsed(this, () => this.$GLOBAL.user.id)],
code: phoneConfig.code(this),
},
},
}
mounted() {
this.$emit('mounted')
},
}
</script>

View File

@ -32,9 +32,10 @@
type="password"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</el-dialog>
</template>
@ -44,43 +45,51 @@ import scPasswordStrength from '@/components/scPasswordStrength/index.vue'
import naFormPassword from '@/config/naFormPassword'
export default {
created() {},
components: {
scPasswordStrength,
},
created() {},
data() {
return {
form: {},
loading: false,
//验证规则
rules: {
oldPassword: naFormPassword.passwordText(this),
newPassword: naFormPassword.passwordText(this),
confirmNewPassword: naFormPassword.passwordText2(() => this.form.newPassword),
},
visible: false,
}
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
open() {
this.visible = true
return this
},
//表单提交方法
async submit() {
if (!(await this.$refs.form.validate().catch(() => {}))) {
const valid = await this.$refs.form.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
try {
const res = await this.$API.sys_user.setPassword.post(this.form)
this.$emit('success', res.data, this.mode)
this.$emit('success', res.data)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
}
} catch {}
this.loading = false
},
},
data() {
return {
visible: false,
loading: false,
form: {},
rules: {
oldPassword: naFormPassword.passwordText(this),
newPassword: naFormPassword.passwordText(this),
confirmNewPassword: naFormPassword.passwordText2(() => this.form.newPassword),
},
}
mounted() {
this.$emit('mounted')
},
}
</script>
</script>
<style scoped></style>

View File

@ -1,6 +1,6 @@
<template>
<el-card :header="$t('主题样式')" shadow="never">
<el-form class="mt-4" label-width="15rem">
<el-form class="mt-4" label-width="10rem">
<el-form-item :label="$t('黑夜模式')">
<el-switch v-model="config.dark" active-icon="el-icon-moon" inactive-icon="el-icon-sunny" inline-prompt />
</el-form-item>
@ -24,10 +24,17 @@
<el-form-item :label="$t('标签栏')">
<el-switch v-model="config.layoutTags"></el-switch>
</el-form-item>
<el-form-item>
<el-popconfirm :title="$t('确定将当前主题设置恢复默认值吗?')" @confirm="themeReset" width="20rem">
<template #reference>
<el-button>{{ $t('重置为默认值') }}</el-button>
</template>
</el-popconfirm>
</el-form-item>
</el-form>
</el-card>
<el-card :header="$t('个人设置')" class="mt-4" shadow="never">
<el-form class="mt-4" label-width="15rem">
<el-form class="mt-4" label-width="10rem">
<el-form-item :label="$t('界面语言')">
<el-select v-model="config.lang">
<el-option :label="$t('简体中文')" value="zh-cn" />
@ -52,6 +59,13 @@
<el-option :label="$t('60分钟')" :value="60" />
</el-select>
</el-form-item>
<el-form-item>
<el-popconfirm :title="$t('确定将当前设置恢复默认值吗?')" @confirm="personalReset" width="20rem">
<template #reference>
<el-button>{{ $t('重置为默认值') }}</el-button>
</template>
</el-popconfirm>
</el-form-item>
</el-form>
</el-card>
</template>
@ -62,16 +76,16 @@ import colorTool from '@/utils/color'
export default {
data() {
return {
colorList: ['#409EFF', '#009688', '#536dfe', '#ff5c93', '#c62f2f', '#fd726d'],
colorList: ['#21A675', '#009688', '#536dfe', '#ff5c93', '#c62f2f', '#fd726d'],
config: {
layout: this.$TOOL.data.get('LAYOUT') ?? this.$CONFIG.LAYOUT,
menuIsCollapse: this.$TOOL.data.get('MENU_IS_COLLAPSE') ?? this.$CONFIG.MENU_IS_COLLAPSE,
menuUniqueOpened: this.$TOOL.data.get('MENU_UNIQUE_OPENED') ?? this.$CONFIG.MENU_UNIQUE_OPENED,
layoutTags: this.$TOOL.data.get('LAYOUT_TAGS') ?? this.$CONFIG.LAYOUT_TAGS,
lang: this.$TOOL.data.get('APP_LANG') ?? this.$CONFIG.LANG,
dark: this.$TOOL.data.get('APP_DARK') ?? false,
colorPrimary: this.$TOOL.data.get('APP_COLOR') ?? this.$CONFIG.COLOR ?? '#409EFF',
autoExit: this.$TOOL.data.get('AUTO_EXIT') ?? 0,
layout: this.$TOOL.data.get('APP_SET_LAYOUT') || this.$CONFIG.APP_SET_LAYOUT,
menuIsCollapse: this.$TOOL.data.get('APP_SET_MENU_IS_COLLAPSE') || this.$CONFIG.APP_SET_MENU_IS_COLLAPSE,
menuUniqueOpened: this.$TOOL.data.get('APP_SET_MENU_UNIQUE_OPENED') || this.$CONFIG.APP_SET_MENU_UNIQUE_OPENED,
layoutTags: this.$TOOL.data.get('APP_SET_MULTI_TAGS') || this.$CONFIG.APP_SET_MULTI_TAGS,
lang: this.$TOOL.data.get('APP_SET_LANG') || this.$CONFIG.APP_SET_LANG,
dark: this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK,
colorPrimary: this.$TOOL.data.get('APP_SET_COLOR') || this.$CONFIG.APP_SET_COLOR || '#17ABE3',
autoExit: this.$TOOL.data.get('APP_SET_AUTO_EXIT') || this.$CONFIG.APP_SET_AUTO_EXIT,
},
}
},
@ -79,51 +93,51 @@ export default {
'config.dark'(val) {
if (val) {
document.documentElement.classList.add('dark')
this.$TOOL.data.set('APP_DARK', val)
this.$TOOL.data.set('APP_SET_DARK', val)
} else {
document.documentElement.classList.remove('dark')
this.$TOOL.data.remove('APP_DARK')
this.$TOOL.data.remove('APP_SET_DARK')
}
},
'config.layout'(val) {
if (val) {
this.$TOOL.data.set('LAYOUT', val)
this.$TOOL.data.set('APP_SET_LAYOUT', val)
this.$store.commit('SET_layout', val)
} else {
this.$TOOL.data.remove('LAYOUT')
this.$TOOL.data.remove('APP_SET_LAYOUT')
}
},
'config.menuIsCollapse'(val) {
if (typeof val === 'boolean') {
this.$TOOL.data.set('MENU_IS_COLLAPSE', val)
this.$TOOL.data.set('APP_SET_MENU_IS_COLLAPSE', val)
this.$store.commit('TOGGLE_menuIsCollapse')
} else {
this.$TOOL.data.remove('MENU_IS_COLLAPSE')
this.$TOOL.data.remove('APP_SET_MENU_IS_COLLAPSE')
}
},
'config.layoutTags'(val) {
if (typeof val === 'boolean') {
this.$TOOL.data.set('LAYOUT_TAGS', val)
this.$TOOL.data.set('APP_SET_MULTI_TAGS', val)
this.$store.commit('TOGGLE_layoutTags')
} else {
this.$TOOL.data.remove('LAYOUT_TAGS')
this.$TOOL.data.remove('APP_SET_MULTI_TAGS')
}
},
'config.menuUniqueOpened'(val) {
if (typeof val === 'boolean') {
this.$TOOL.data.set('MENU_UNIQUE_OPENED', val)
this.$TOOL.data.set('APP_SET_MENU_UNIQUE_OPENED', val)
} else {
this.$TOOL.data.remove('MENU_UNIQUE_OPENED')
this.$TOOL.data.remove('APP_SET_MENU_UNIQUE_OPENED')
}
},
'config.lang'(val) {
this.$i18n.locale = val
this.$TOOL.data.set('APP_LANG', val)
this.$TOOL.data.set('APP_SET_LANG', val)
},
'config.colorPrimary'(val) {
if (!val) {
val = '#409EFF'
this.config.colorPrimary = '#409EFF'
val = '#21A675'
this.config.colorPrimary = '#21A675'
}
document.documentElement.style.setProperty('--el-color-primary', val)
for (let i = 1; i <= 9; i++) {
@ -132,16 +146,38 @@ export default {
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(val, i / 10))
}
this.$TOOL.data.set('APP_COLOR', val)
this.$TOOL.data.set('APP_SET_COLOR', val)
},
'config.autoExit'(val) {
if (val === 0) {
this.$TOOL.data.remove('AUTO_EXIT')
this.$TOOL.data.remove('APP_SET_AUTO_EXIT')
} else {
this.$TOOL.data.set('AUTO_EXIT', val)
this.$TOOL.data.set('APP_SET_AUTO_EXIT', val)
}
},
},
methods: {
async themeReset() {
this.$loading()
this.$message.success(this.$t('操作成功'))
localStorage.removeItem('APP_SET_LAYOUT')
localStorage.removeItem('APP_SET_MENU_IS_COLLAPSE')
localStorage.removeItem('APP_SET_MENU_UNIQUE_OPENED')
localStorage.removeItem('APP_SET_MULTI_TAGS')
localStorage.removeItem('APP_SET_DARK')
localStorage.removeItem('APP_SET_COLOR')
await this.$TOOL.data.uploadConfig()
window.location.reload()
},
async personalReset() {
this.$loading()
this.$message.success(this.$t('操作成功'))
localStorage.removeItem('APP_SET_AUTO_EXIT')
localStorage.removeItem('APP_SET_LANG')
await this.$TOOL.data.uploadConfig()
window.location.reload()
},
},
}
</script>

View File

@ -66,25 +66,22 @@
</template>
<script>
import scStatistic from '@/components/scStatistic'
import naInfo from '@/components/naInfo/index.vue'
import tool from '@/utils/tool'
export default {
components: {
scStatistic,
naInfo,
},
data() {
return {
dialog: {
info: false,
},
query: {
filter: {
dbIndex: 1,
},
},
dialog: {
info: false,
},
statistics: {
keyspaceHits: 0,
keyspaceMisses: 0,
@ -95,21 +92,11 @@ export default {
},
}
},
mounted() {
this.cacheStatistics()
},
watch: {
'query.filter.dbIndex': {
handler() {
this.$refs.table.upData()
},
},
},
methods: {
async rowClick(row) {
this.dialog.info = true
await this.$nextTick()
this.$refs.info.open(tool.sortProperties(row), `缓存详情`)
this.$refs.info.open(this.$TOOL.sortProperties(row), this.$t('缓存详情'))
},
async cacheStatistics() {
try {
@ -122,6 +109,16 @@ export default {
}
},
},
mounted() {
this.cacheStatistics()
},
watch: {
'query.filter.dbIndex': {
handler() {
this.$refs.table.upData()
},
},
},
}
</script>

View File

@ -13,8 +13,8 @@
],
},
]"
:label-width="10"
@on-change="filterChange"
label-width="10"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
@ -27,13 +27,26 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_config.bulkDelete" :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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_config.pagedQuery"
:context-menus="['id', 'userRegisterConfirm', 'enabled', 'createdTime']"
:params="query"
@ -47,22 +60,21 @@
row-key="id"
stripe>
<el-table-column type="selection" />
<el-table-column :label="$t('配置编号')" align="center" prop="id" width="170" />
<na-col-id :label="$t('配置编号')" prop="id" width="170" />
<el-table-column :label="$t('用户注册')" align="center">
<el-table-column :label="$t('默认部门')" align="center" prop="userRegisterDept.name" width="150" />
<el-table-column :label="$t('默认角色')" align="center" prop="userRegisterRole.name" width="150" />
<el-table-column :label="$t('人工审核')" align="center" prop="userRegisterConfirm" width="120">
<template #default="scope">
<el-switch v-model="scope.row.userRegisterConfirm" @change="changeSwitch($event, scope.row)"></el-switch>
<template #default="{ row }">
<el-switch v-model="row.userRegisterConfirm" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="$t('启用')" align="center" prop="enabled" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
<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="center" prop="createdTime" width="170" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -73,25 +85,25 @@
type: 'danger',
})
"
:vue="this"
width="170" />
:vue="this" />
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => $refs.table.upData()"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import naColOperation from '@/config/naColOperation'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import tool from '@/utils/tool'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
@ -104,13 +116,14 @@ export default {
return table
},
},
created() {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {
info: false,
save: false,
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
@ -123,6 +136,33 @@ export default {
},
inject: ['reload'],
methods: {
async setEnabled(enabled) {
let loading
try {
await this.$confirm(
this.$t('确定要 {operator} 选中的 {count} 项吗?', {
operator: enabled ? this.$t('启用') : this.$t('禁用'),
count: this.selection.length,
}),
this.$t('提示'),
{
type: 'warning',
},
)
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_config.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(
this.$t('操作成功 {count}/{total} 项', {
count: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
total: this.selection.length,
}),
)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
async changeSwitch(event, row) {
try {
await this.$API.sys_config.edit.post(row)
@ -135,10 +175,9 @@ export default {
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()
})
this.$refs.search.search()
},
async rowDel(row) {
try {
const res = await this.$API.sys_config.delete.post({ id: row.id })
@ -168,7 +207,14 @@ export default {
this.$refs.table.upData()
},
},
mounted() {},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -35,7 +35,7 @@
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -45,7 +45,7 @@
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
@ -53,37 +53,37 @@
<script>
export default {
components: {},
emits: ['success', 'closed'],
data() {
return {
//表单数据
form: {
enabled: true,
},
loading: false,
mode: 'add',
//验证规则
rules: {
userRegisterDeptId: [{ required: true, message: '请选择默认部门' }],
userRegisterRoleId: [{ required: true, message: '请选择默认角色' }],
},
titleMap: {
add: this.$t('新增配置'),
edit: this.$t('编辑配置'),
view: this.$t('查看配置'),
},
visible: false,
loading: false,
//表单数据
form: {
enabled: true,
},
//验证规则
rules: {
userRegisterDeptId: [{ required: true, message: '请选择默认部门' }],
userRegisterRoleId: [{ required: true, message: '请选择默认角色' }],
},
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
Object.assign(this.form, (await this.$API.sys_config.get.post({ id: data.id })).data)
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_config.get.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
@ -95,20 +95,21 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_config.create : this.$API.sys_config.edit
try {
const method = this.mode === 'add' ? this.$API.sys_config.create : this.$API.sys_config.edit
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
///
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>

View File

@ -34,13 +34,26 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_dept.bulkDelete" :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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_dept.query"
:context-menus="['id', 'name', 'sort', 'enabled', 'createdTime', 'summary']"
:default-sort="{ prop: 'sort', order: 'descending' }"
@ -58,17 +71,16 @@
remote-sort
row-key="id"
stripe>
<el-table-column type="selection" width="50" />
<el-table-column :label="$t('部门编号')" prop="id" sortable="custom" />
<el-table-column type="selection" />
<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="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
<template #default="{ row }">
<el-switch v-model="row.enabled" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('备注')" prop="summary" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -86,16 +98,18 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import naColOperation from '@/config/naColOperation'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
@ -108,12 +122,14 @@ export default {
return table
},
},
created() {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {
save: false,
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
@ -126,6 +142,33 @@ export default {
},
inject: ['reload'],
methods: {
async setEnabled(enabled) {
let loading
try {
await this.$confirm(
this.$t('确定要 {operator} 选中的 {count} 项吗?', {
operator: enabled ? this.$t('启用') : this.$t('禁用'),
count: this.selection.length,
}),
this.$t('提示'),
{
type: 'warning',
},
)
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_dept.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(
this.$t('操作成功 {count}/{total} 项', {
count: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
total: this.selection.length,
}),
)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
async changeSwitch(event, row) {
try {
await this.$API.sys_dept.setEnabled.post(row)
@ -138,8 +181,8 @@ export default {
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()
})
this.$refs.search.search()
},
async rowDel(row) {
try {
@ -170,7 +213,13 @@ export default {
this.$refs.table.upData()
},
},
mounted() {},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" :width="800" @closed="$emit('closed')" destroy-on-close>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<div v-loading="loading">
<el-tabs tab-position="top">
<el-tabs v-model="tabId" tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item :label="$t('上级部门')" prop="parentId">
@ -27,10 +27,13 @@
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('用户列表')" name="user">
<user v-if="tabId === 'user'" :dept-id="form.id"></user>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -40,27 +43,31 @@
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $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>
import { defineAsyncComponent } from 'vue'
const User = defineAsyncComponent(() => import('@/views/sys/user/index.vue'))
export default {
components: {},
emits: ['success', 'closed'],
components: { User },
data() {
return {
mode: 'add',
titleMap: {
add: this.$t('新增部门'),
edit: this.$t('编辑部门'),
view: this.$t('查看部门'),
//所需数据选项
depts: [],
deptsProps: {
label: 'name',
value: 'id',
emitPath: false,
checkStrictly: true,
},
visible: false,
loading: false,
//表单数据
form: { enabled: true, sort: 100 },
loading: false,
mode: 'add',
//验证规则
rules: {
sort: [
@ -72,27 +79,25 @@ export default {
],
name: [{ required: true, message: '请输入部门名称' }],
},
//所需数据选项
depts: [],
deptsProps: {
label: 'name',
value: 'id',
emitPath: false,
checkStrictly: true,
tabId: '0',
titleMap: {
add: this.$t('新增部门'),
edit: this.$t('编辑部门'),
view: this.$t('查看部门'),
},
visible: false,
}
},
mounted() {
this.getGroup()
},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
Object.assign(this.form, (await this.$API.sys_dept.get.post({ id: data.id })).data)
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_dept.get.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
@ -103,25 +108,27 @@ export default {
this.depts = res.data
},
//表单提交方法
submit() {
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.loading = true
try {
const method = this.mode === 'add' ? this.$API.sys_dept.create : this.$API.sys_dept.edit
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.loading = false
}
})
async submit() {
const valid = await this.$refs.dialogForm.validate().catch(() => {})
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_dept.create : this.$API.sys_dept.edit
try {
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
this.getGroup()
},
}
</script>
<style></style>
<style scoped></style>

View File

@ -42,27 +42,38 @@
</el-aside>
<list :catalogId="form.catalogId" />
</el-container>
<save-dialog v-if="dialog.save" @closed="dialog.save = false" @success="getData" ref="saveDialog"></save-dialog>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => getData()"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import { defineAsyncComponent } from 'vue'
import list from './list'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
list,
saveDialog,
},
computed: {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
form: {},
dialog: {
save: false,
},
loading: false,
data: [],
dialog: {},
filterText: '',
form: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
@ -71,14 +82,7 @@ export default {
},
}
},
watch: {
filterText(val) {
this.$refs.dic.filter(val)
},
},
mounted() {
this.getData()
},
inject: ['reload'],
methods: {
// 获取字典目录
async getData() {
@ -102,15 +106,11 @@ export default {
},
//字典目录增加
async add() {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open()
this.dialog.save = { mode: 'add' }
},
//字典目录编辑
async edit(data) {
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('edit', data)
this.dialog.save = { mode: 'edit', row: data }
},
//字典目录点击
click(data) {
@ -131,6 +131,15 @@ export default {
await this.getData()
},
},
mounted() {
this.getData()
},
props: ['keywords'],
watch: {
filterText(val) {
this.$refs.dic.filter(val)
},
},
}
</script>

View File

@ -16,7 +16,7 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :data="{ catalogId: this.catalogId }" :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_dic.bulkDeleteContent" :vue="this" />
</div>
</el-header>
@ -55,18 +55,25 @@
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
},
computed: {
naColOperation() {
return naColOperation
@ -75,28 +82,25 @@ export default {
return table
},
},
components: { saveDialog },
props: { catalogId: Number },
inject: ['reload'],
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {
save: false,
},
selection: [],
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
},
selection: [],
}
},
watch: {
catalogId() {
this.$refs.search.reset()
},
},
inject: ['reload'],
methods: {
//搜索
onSearch(form) {
@ -146,5 +150,19 @@ export default {
this.$refs.table.refresh()
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: { catalogId: Number, keywords: String },
watch: {
catalogId() {
this.$refs.search.reset()
},
},
}
</script>
</script>
<style scoped></style>

View File

@ -18,7 +18,7 @@
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -28,7 +28,7 @@
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
@ -36,58 +36,62 @@
<script>
export default {
components: {},
emits: ['success', 'closed'],
data() {
return {
mode: 'add',
titleMap: {
view: this.$t('查看字典项'),
add: this.$t('新增字典项'),
edit: this.$t('编辑字典项'),
},
visible: false,
loading: false,
//表单数据
form: {},
loading: false,
mode: 'add',
//验证规则
rules: {
catalogId: [{ required: true, message: '请选择所属字典' }],
key: [{ required: true, message: '请输入项名' }],
value: [{ required: true, message: '请输入项值' }],
},
titleMap: {
add: this.$t('新增字典项'),
edit: this.$t('编辑字典项'),
view: this.$t('查看字典项'),
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
Object.assign(this.form, (await this.$API.sys_dic.getContent.post({ id: data.id })).data)
this.mode = data.mode
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
const method = this.mode === 'add' ? this.$API.sys_dic.createContent : this.$API.sys_dic.editContent
try {
const method = this.mode === 'add' ? this.$API.sys_dic.createContent : this.$API.sys_dic.editContent
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>

View File

@ -13,39 +13,41 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button :loading="loading" @click="submit" type="primary">{{ $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 {
emits: ['success', 'closed'],
data() {
return {
//表单数据
form: {},
loading: false,
mode: 'add',
//验证规则
rules: {
code: [{ required: true, message: '请输入编码' }],
name: [{ required: true, message: '请输入字典名称' }],
},
titleMap: {
add: this.$t('新增字典'),
edit: this.$t('编辑字典'),
},
visible: false,
loading: false,
form: {},
rules: {
code: [{ required: true, message: '请输入编码' }],
name: [{ required: true, message: '请输入字典名称' }],
},
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
Object.assign(this.form, (await this.$API.sys_dic.getCatalog.post({ id: data.id })).data)
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_dic.getCatalog.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
@ -57,21 +59,21 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_dic.createCatalog : this.$API.sys_dic.editCatalog
try {
const method = this.mode === 'add' ? this.$API.sys_dic.createCatalog : this.$API.sys_dic.editCatalog
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style></style>
<style scoped></style>

View File

@ -0,0 +1,352 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('作业状态'),
key: 'status',
options: [
{ label: $t('全部'), value: '' },
...Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], label: x[1][1] }
}),
],
},
{
title: $t('启用状态'),
key: 'enabled',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('启用'), value: true },
{ label: $t('禁用'), value: false },
],
},
]"
:label-width="10"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
:controls="[
{
type: 'input',
field: ['root', 'keywords'],
placeholder: $t('作业编号 / 作业名称'),
style: 'width:20rem',
},
{
type: 'select',
field: ['dy', 'httpMethod'],
options: Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('请求方式'),
style: 'width:15rem',
},
]"
:vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel">
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_job.bulkDelete" :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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
:apiObj="$API.sys_job.pagedQuery"
:context-menus="[
'id',
'jobName',
'executionCron',
'status',
'httpMethod',
'lastExecTime',
'lastStatusCode',
'nextExecTime',
'enabled',
'createdTime',
]"
:default-sort="{ prop: 'lastExecTime', order: 'descending' }"
:page-size="100"
:params="query"
:vue="this"
@selection-change="
(items) => {
selection = items
}
"
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection" />
<na-col-id :label="$t('作业编号')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('作业名称')" prop="jobName" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('执行计划')" align="right" prop="executionCron" sortable="custom" width="150" />
<na-col-indicator
:label="$t('作业状态')"
:options="
Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="status"
sortable="custom"
width="100" />
<na-col-indicator
:label="$t('请求方式')"
:options="
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="httpMethod"
sortable="custom"
width="150" />
<el-table-column :label="$t('上次执行')" align="center">
<el-table-column :label="$t('状态')" align="center" prop="lastExecTime" sortable="custom" width="100">
<template #default="{ row }">
<na-indicator
:data="row"
:options="
Object.entries(this.$GLOBAL.enums.httpStatusCodes).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2] }
})
"
prop="lastStatusCode" />
</template>
</el-table-column>
<el-table-column :label="$t('时间')" align="right" prop="lastExecTime" sortable="custom" width="100">
<template #default="{ row }">
<span v-if="row.lastExecTime" v-time.tip="row.lastExecTime"></span>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => (row.lastDuration ? `${$TOOL.groupSeparator(row.lastDuration.toFixed(0))} ms` : `-`)"
:label="$t('耗时')"
align="right"
prop="lastDuration"
sortable="custom"
width="100">
</el-table-column>
</el-table-column>
<el-table-column :label="$t('下次执行时间')" align="right" prop="nextExecTime" sortable="custom" width="170" />
<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>
<na-col-operation
:buttons="
naColOperation.buttons.concat(
{
icon: 'el-icon-video-play',
click: execute,
},
{
icon: 'el-icon-delete',
confirm: true,
title: $t('删除作业'),
click: rowDel,
type: 'danger',
},
)
"
:vue="this" />
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import naIndicator from '@/components/naIndicator/index.vue'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
naIndicator,
saveDialog,
},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
created() {
if (this.keywords || this.$route.query.keywords) {
this.query.keywords = this.keywords || this.$route.query.keywords
}
},
data() {
return {
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
},
selection: [],
timer: null,
}
},
inject: ['reload'],
methods: {
async setEnabled(enabled) {
let loading
try {
await this.$confirm(
this.$t('确定要 {operator} 选中的 {count} 项吗?', {
operator: enabled ? this.$t('启用') : this.$t('禁用'),
count: this.selection.length,
}),
this.$t('提示'),
{
type: 'warning',
},
)
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_job.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(
this.$t('操作成功 {count}/{total} 项', {
count: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
total: 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_job.setEnabled.post(row)
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.$refs.table.refresh()
},
async execute(row) {
try {
await this.$API.sys_job.execute.post({ id: row.id })
this.$notify.success({
dangerouslyUseHTMLString: true,
message: `<div id="countdown">${this.$t('已发起执行请求5 秒后弹出执行结果')}</div>`,
onClose: async () => {
clearInterval(this.timer)
this.dialog.save = { row, mode: 'view', tabId: 'record' }
},
})
this.timer = setInterval(() => {
const countdown = new RegExp('\\d+').exec(document.getElementById('countdown').innerText)[0]
const num = parseInt(countdown) - 1
document.getElementById('countdown').innerText = document
.getElementById('countdown')
.innerText.replace(countdown, `${num < 0 ? 0 : num}`)
}, 1000)
} catch {}
this.$refs.table.refresh()
},
async rowDel(row) {
try {
const res = await this.$API.sys_job.delete.post({ id: row.id })
this.$message.success(this.$t('删除 {count} 项', { count: res.data }))
} catch {
//
}
this.$refs.table.refresh()
},
onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
})
}
if (typeof form.dy.status === 'string' && form.dy.status.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'status',
operator: 'eq',
value: form.dy.status,
})
}
if (typeof form.dy.enabled === 'boolean') {
this.query.dynamicFilter.filters.push({
field: 'enabled',
operator: 'eq',
value: form.dy.enabled,
})
}
if (typeof form.dy.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'httpMethod',
operator: 'eq',
value: form.dy.httpMethod,
})
}
this.$refs.table.upData()
},
},
mounted() {
if (this.keywords || this.$route.query.keywords) {
this.$refs.search.form.root.keywords = this.keywords || this.$route.query.keywords
this.$refs.search.keepKeywords = this.keywords || this.$route.query.keywords
}
},
props: ['keywords'],
watch: {},
}
</script>
<style scoped></style>

View File

@ -1,13 +1,7 @@
<template>
<sc-dialog
v-model="visible"
:title="`${titleMap[mode]}${form?.id ?? '...'}`"
:width="800"
@closed="$emit('closed')"
destroy-on-close
full-screen>
<el-tabs v-model="tabIndex" tab-position="top">
<el-tab-pane :label="$t('基本信息')" :name="0">
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<el-tabs v-model="tabId" tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form
v-loading="loading"
:disabled="mode === 'view'"
@ -48,7 +42,7 @@
<el-form-item :label="$t('请求头')" prop="requestHeader">
<v-ace-editor
v-model:value="form.requestHeader"
:theme="this.$TOOL.data.get('APP_DARK') ? 'github_dark' : 'github'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
lang="json"
style="height: 5rem; width: 100%" />
<el-button @click="form.requestHeader = jsonFormat(form.requestHeader)" type="text">{{ $t('JSON格式化') }}</el-button>
@ -56,7 +50,7 @@
<el-form-item :label="$t('请求体')" prop="requestBody">
<v-ace-editor
v-model:value="form.requestBody"
:theme="this.$TOOL.data.get('APP_DARK') ? 'github_dark' : 'github'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'github_dark' : 'github'"
lang="json"
style="height: 10rem; width: 100%" />
<el-button @click="form.requestBody = jsonFormat(form.requestBody)" type="text">{{ $t('JSON格式化') }}</el-button>
@ -96,13 +90,13 @@
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('执行记录')" :name="1">
<record v-if="tabIndex === 1" :keywords="form.id" />
<el-tab-pane v-if="mode === 'view'" :label="$t('执行记录')" name="record">
<record v-if="tabId === 'record'" :keywords="form.id" />
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')" :name="2">
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -112,31 +106,20 @@
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $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>
import Record from '@/views/sys/job/record/index.vue'
import { defineAsyncComponent } from 'vue'
import vkbeautify from 'vkbeautify/index'
const Record = defineAsyncComponent(() => import('@/views/sys/job/record/index.vue'))
export default {
components: {
Record,
},
emits: ['success', 'closed'],
components: { Record },
data() {
return {
tabIndex: 0,
mode: 'add',
titleMap: {
view: this.$t('查看作业'),
add: this.$t('新增作业'),
edit: this.$t('编辑作业'),
},
visible: false,
loading: false,
//
form: {
executionCron: '0 * * * * ?',
@ -144,6 +127,8 @@ export default {
requestHeader: `{ "Content-Type": "application/json" }`,
requestBody: '{}',
},
loading: false,
mode: 'add',
//
rules: {
executionCron: [
@ -197,9 +182,16 @@ export default {
},
],
},
tabId: '0',
titleMap: {
add: this.$t('新增作业'),
edit: this.$t('编辑作业'),
view: this.$t('查看作业'),
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
jsonFormat(obj) {
try {
@ -213,16 +205,16 @@ export default {
return vkbeautify
},
//
async open(mode = 'add', data, tabIndex = 0) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
const res = await this.$API.sys_job.get.post({ id: data.id })
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_job.get.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
this.tabIndex = tabIndex
this.tabId = data.tabId ?? '0'
return this
},
@ -232,14 +224,12 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_job.create : this.$API.sys_job.edit
try {
const method = this.mode === 'add' ? this.$API.sys_job.create : this.$API.sys_job.edit
this.loading = true
const res = await method.post(
Object.assign({}, this.form, { userId: this.form.user.id, requestHeaders: JSON.parse(this.form.requestHeader) }),
)
this.loading = false
if (res.data) {
this.$emit('success', res.data, this.mode)
this.$message.success(this.$t('操作成功'))
@ -247,11 +237,14 @@ export default {
this.$message.error(this.$t('操作失败'))
}
this.visible = false
} catch {
//
this.loading = false
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
</script>
<style scoped></style>

View File

@ -1,326 +1,35 @@
<template>
<el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('作业状态'),
key: 'status',
options: [
{ label: $t('全部'), value: '' },
...Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], label: x[1][1] }
}),
],
},
{
title: $t('启用状态'),
key: 'enabled',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('启用'), value: true },
{ label: $t('禁用'), value: false },
],
},
]"
:label-width="10"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header>
<div class="left-panel">
<na-search
:controls="[
{
type: 'input',
field: ['root', 'keywords'],
placeholder: $t('作业编号 / 作业名称'),
style: 'width:20rem',
},
{
type: 'select',
field: ['dy', 'httpMethod'],
options: Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('请求方式'),
style: 'width:15rem',
},
]"
:vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch"
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<na-button-bulk-del :api="$API.sys_job.bulkDelete" :vue="this" />
</div>
<el-header style="border: none">
<el-tabs v-model="tabId" class="w100p">
<el-tab-pane :label="$t('所有作业')" name="all"></el-tab-pane>
<el-tab-pane :label="$t('异常作业')" name="fail"></el-tab-pane>
</el-tabs>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_job.pagedQuery"
:context-menus="[
'id',
'jobName',
'executionCron',
'status',
'httpMethod',
'lastExecTime',
'lastStatusCode',
'nextExecTime',
'enabled',
'createdTime',
]"
:default-sort="{ prop: 'lastExecTime', order: 'descending' }"
:page-size="100"
:params="query"
:vue="this"
@selection-change="
(items) => {
selection = items
}
"
ref="table"
remote-filter
remote-sort
row-key="id"
stripe>
<el-table-column type="selection" />
<el-table-column :label="$t('作业编号')" prop="id" sortable="custom" width="150" />
<el-table-column :label="$t('作业名称')" prop="jobName" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('执行计划')" align="center" prop="executionCron" sortable="custom" width="150" />
<na-col-indicator
:label="$t('作业状态')"
:options="
Object.entries(this.$GLOBAL.enums.jobStatues).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="status"
sortable="custom"
width="100" />
<na-col-indicator
:label="$t('请求方式')"
:options="
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="httpMethod"
sortable="custom"
width="150" />
<el-table-column :label="$t('上次执行')" align="center">
<el-table-column :label="$t('状态')" align="center" prop="lastExecTime" sortable="custom" width="100">
<template #default="scope">
<sc-status-indicator :type="scope.row.lastStatusCode === 'ok' ? 'success' : 'danger'" />
{{
this.$GLOBAL.enums.httpStatusCodes[scope.row.lastStatusCode]
? this.$GLOBAL.enums.httpStatusCodes[scope.row.lastStatusCode][1]
: scope.row.lastStatusCode
}}
</template>
</el-table-column>
<el-table-column :label="$t('时间')" align="right" prop="lastExecTime" sortable="custom" width="100">
<template #default="scope">
<span v-if="scope.row.lastExecTime" v-time.tip="scope.row.lastExecTime"></span>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => (row.lastDuration ? `${tool.groupSeparator(row.lastDuration.toFixed(0))} ms` : `-`)"
:label="$t('耗时')"
align="right"
prop="lastDuration"
sortable="custom"
width="100">
</el-table-column>
</el-table-column>
<el-table-column :label="$t('下次执行时间')" align="right" prop="nextExecTime" sortable="custom" width="170" />
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="130">
<template #default="scope">
<span v-if="scope.row.createdTime" v-time.tip="scope.row.createdTime"></span>
</template>
</el-table-column>
<na-col-operation
:buttons="
naColOperation.buttons.concat(
{
icon: 'el-icon-video-play',
click: execute,
},
{
icon: 'el-icon-delete',
confirm: true,
type: 'danger',
title: $t('删除作业'),
click: rowDel,
},
)
"
:vue="this"
width="180" />
</sc-table>
<component :is="tabId" :status-codes="['300,399', '400,499', '500,599', '900,999']" />
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import ScSelectFilter from '@/components/scSelectFilter/index.vue'
import tool from '@/utils/tool'
import { defineAsyncComponent } from 'vue'
const fail = defineAsyncComponent(() => import('@/views/sys/job/record/index.vue'))
const all = defineAsyncComponent(() => import('@/views/sys/job/all/index.vue'))
export default {
components: {
ScSelectFilter,
saveDialog,
},
inject: ['reload'],
components: { all, fail },
computed: {},
created() {},
data() {
return {
timer: null,
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'enabled',
operator: 'eq',
value: true,
},
],
},
filter: {},
},
dialog: {
save: false,
},
selection: [],
tabId: 'all',
}
},
inject: ['reload'],
methods: {},
mounted() {},
watch: {},
computed: {
tool() {
return tool
},
naColOperation() {
return naColOperation
},
table() {
return table
},
},
mounted() {
this.$refs.search.form.dy.enabled = true
},
created() {},
methods: {
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_job.setEnabled.post(row)
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.$refs.table.refresh()
},
async execute(row) {
try {
await this.$API.sys_job.execute.post({ id: row.id })
this.$notify.success({
dangerouslyUseHTMLString: true,
message: `<div id="countdown">${this.$t('已发起执行请求5 秒后弹出执行结果')}</div>`,
onClose: async () => {
clearInterval(this.timer)
this.loading = true
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', row, 1)
this.loading = false
},
})
this.timer = setInterval(() => {
const countdown = new RegExp('\\d+').exec(document.getElementById('countdown').innerText)[0]
document.getElementById('countdown').innerText = document
.getElementById('countdown')
.innerText.replace(countdown, `${parseInt(countdown) - 1}`)
}, 1000)
} catch {}
this.$refs.table.refresh()
},
//删除
async rowDel(row) {
try {
const res = await this.$API.sys_job.delete.post({ id: row.id })
this.$message.success(this.$t('删除 {count} 项', { count: res.data }))
} catch {
//
}
this.$refs.table.refresh()
},
//搜索
onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
field: 'createdTime',
operator: 'dateRange',
value: form.dy.createdTime,
})
}
if (typeof form.dy.status === 'string' && form.dy.status.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'status',
operator: 'eq',
value: form.dy.status,
})
}
if (typeof form.dy.enabled === 'boolean') {
this.query.dynamicFilter.filters.push({
field: 'enabled',
operator: 'eq',
value: form.dy.enabled,
})
}
if (typeof form.dy.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'httpMethod',
operator: 'eq',
value: form.dy.httpMethod,
})
}
this.$refs.table.upData()
},
},
}
</script>
<style scoped></style>

View File

@ -17,7 +17,7 @@
return { value: x[0], label: x[1][1] }
}),
placeholder: $t('请求方式'),
style: 'width:10rem',
style: 'width:15rem',
},
{
multiple: true,
@ -45,9 +45,8 @@
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_job.recordPagedQuery"
:context-menus="['id', 'duration', 'httpMethod', 'requestUrl', 'httpStatusCode', 'createdTime']"
:context-menus="['id', 'duration', 'httpMethod', 'requestUrl', 'httpStatusCode', 'createdTime', 'jobId']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query"
:vue="this"
@ -56,37 +55,51 @@
remote-sort
row-key="id"
stripe>
<el-table-column :label="$t('唯一编码')" prop="id" sortable="custom" width="150" />
<na-col-id :label="$t('唯一编码')" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('响应状态码')" prop="httpStatusCode" sortable="custom" width="200">
<template #default="{ row }">
<p>
<na-indicator
:data="row"
:options="
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2] }
})
"
prop="httpMethod" />
</p>
<p>
<na-indicator
:data="row"
:options="
Object.entries(this.$GLOBAL.enums.httpStatusCodes).map((x) => {
return { value: x[0], text: `${x[1][1]}`, type: x[1][2] }
})
"
prop="httpStatusCode" />
</p>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => `${tool.groupSeparator(row.duration.toFixed(0))} ms`"
:formatter="(row) => `${$TOOL.groupSeparator(row.duration.toFixed(0))} ms`"
:label="$t('执行耗时')"
align="right"
prop="duration"
sortable="custom"
width="150" />
<na-col-indicator
:label="$t('请求方式')"
:options="
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], text: x[1][1], type: x[1][2] }
})
"
align="center"
prop="httpMethod"
sortable="custom"
width="150" />
<el-table-column :label="$t('响应状态码')" align="center" prop="httpStatusCode" sortable="custom" width="200">
<template #default="scope">
<sc-status-indicator :type="scope.row.httpStatusCode === 'ok' ? 'success' : 'danger'" />
{{
this.$GLOBAL.enums.httpStatusCodes[scope.row.httpStatusCode]
? this.$GLOBAL.enums.httpStatusCodes[scope.row.httpStatusCode][1]
: scope.row.httpStatusCode
}}
<el-table-column :label="$t('作业信息')" prop="jobId" show-overflow-tooltip sortable="custom" width="500">
<template #default="{ row }">
<p>
<el-link :href="`/sys/job?keywords=${row.jobId}`" target="_blank">
{{ row.job.jobName }}
</el-link>
</p>
<p>
{{ row.requestUrl }}
</p>
</template>
</el-table-column>
<el-table-column :label="$t('请求的网络地址')" prop="requestUrl" sortable="custom" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
<el-table-column :label="$t('响应体')" prop="responseBody" show-overflow-tooltip sortable="custom" />
<na-col-operation :buttons="[naColOperation.buttons[0]]" :vue="this" width="100" />
</sc-table>
</el-main>
@ -94,48 +107,25 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import tool from '@/utils/tool'
import naIndicator from '@/components/naIndicator/index.vue'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
props: ['keywords'],
components: {
naIndicator,
saveDialog,
},
inject: ['reload'],
data() {
return {
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
dialog: {
save: false,
},
}
},
watch: {},
computed: {
tool() {
return tool
},
naColOperation() {
return naColOperation
},
@ -143,21 +133,42 @@ export default {
return table
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
if (this.statusCodes) {
this.query.dynamicFilter.filters.push({
logic: 'or',
filters: this.statusCodes.map((x) => {
return {
field: 'httpStatusCode',
operator: 'range',
value: x,
}
}),
})
}
},
data() {
return {
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
}
},
inject: ['reload'],
methods: {
//搜索
onSearch(form) {
@ -191,6 +202,7 @@ export default {
filters: filters,
})
}
if (typeof form.dy.httpMethod === 'string' && form.dy.httpMethod.trim() !== '') {
this.query.dynamicFilter.filters.push({
field: 'httpMethod',
@ -198,10 +210,25 @@ export default {
value: form.dy.httpMethod,
})
}
this.$refs.table.upData()
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
if (this.statusCodes) {
this.$refs.search.form.dy.httpStatusCode = this.statusCodes
this.$refs.search.keepHttpStatusCode = this.statusCodes
}
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
props: ['keywords', 'statusCodes'],
watch: {},
}
</script>

View File

@ -1,11 +1,5 @@
<template>
<sc-dialog
v-model="visible"
:title="`${titleMap[mode]}${form?.id ?? '...'}`"
:width="800"
@closed="$emit('closed')"
destroy-on-close
full-screen>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<el-form
v-loading="loading"
:disabled="mode === 'view'"
@ -36,7 +30,7 @@
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -53,37 +47,41 @@
<script>
export default {
components: {},
emits: ['success', 'closed'],
data() {
return {
mode: 'add',
titleMap: {
view: this.$t('查看作业记录'),
add: this.$t('新增作业记录'),
edit: this.$t('编辑作业记录'),
},
visible: false,
loading: false,
//表单数据
form: {},
loading: false,
mode: 'add',
//验证规则
rules: {},
titleMap: {
add: this.$t('新增作业记录'),
edit: this.$t('编辑作业记录'),
view: this.$t('查看作业记录'),
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
const res = await this.$API.sys_job.recordGet.post({ id: data.id })
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_job.recordGet.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
this.loading = false
return this
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
</script>
<style scoped></style>

View File

@ -40,7 +40,6 @@
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'createdClientIp', 'createdUserAgent', 'createdTime']"
:context-opers="['view']"
@ -53,18 +52,17 @@
remote-sort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150" />
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<na-col-id label="日志编号" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('结果')" align="center" prop="httpStatusCode" sortable="custom" width="100">
<template #default="scope">
<sc-status-indicator :type="scope.row.httpStatusCode === 200 ? 'success' : 'danger'" />
{{ scope.row.httpStatusCode === 200 ? '成功' : '失败' }}
<template #default="{ row }">
<sc-status-indicator :type="row.httpStatusCode === 200 ? 'success' : 'danger'" />
{{ row.httpStatusCode === 200 ? '成功' : '失败' }}
</template>
</el-table-column>
<el-table-column :label="$t('登录名')" prop="loginName" width="150" />
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
<template #default="{ row }">
<na-ip :ip="row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" />
@ -78,47 +76,12 @@
<script>
import naInfo from '@/components/naInfo/index.vue'
import tool from '@/utils/tool'
import ScTable from '@/components/scTable/index.vue'
export default {
components: {
ScTable,
naInfo,
},
watch: {},
inject: ['reload'],
data() {
return {
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
dialog: {
info: false,
},
}
},
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.apiId = 'api/sys/user/login.by.pwd'
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
computed: {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
@ -129,6 +92,28 @@ export default {
value: 'api/sys/user/login.by.pwd',
})
},
data() {
return {
dialog: {
info: false,
},
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
selection: [],
}
},
inject: ['reload'],
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
@ -176,9 +161,22 @@ export default {
const res = await this.$API.sys_log.query.post({
filter: { id: row.id },
})
this.$refs.info.open(tool.sortProperties(res.data[0]), this.$t('日志详情:{id}', { id: row.id }))
this.$refs.info.open(this.$TOOL.sortProperties(res.data[0]), this.$t('日志详情:{id}', { id: row.id }))
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.apiId = 'api/sys/user/login.by.pwd'
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -73,8 +73,7 @@
remote-sort
row-key="id"
stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150" />
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<na-col-id label="日志编号" prop="id" sortable="custom" width="170" />
<el-table-column :label="$t('响应码')" align="center" prop="httpStatusCode" sortable="custom" width="150">
<template #default="{ row }">
<sc-status-indicator :type="row.httpStatusCode >= 200 && row.httpStatusCode < 300 ? 'success' : 'danger'" />
@ -83,20 +82,20 @@
</el-table-column>
<el-table-column :label="$t('请求服务')" align="center">
<el-table-column :label="$t('路径')" prop="apiId" show-overflow-tooltip sortable="custom">
<template #default="scope">
<p>{{ scope.row.apiId }}</p>
<p>{{ scope.row.apiSummary }}</p>
<template #default="{ row }">
<p>{{ row.apiId }}</p>
<p>{{ row.apiSummary }}</p>
</template>
</el-table-column>
<el-table-column :label="$t('方法')" align="center" prop="method" sortable="custom" width="100">
<template #default="scope">
<template #default="{ row }">
<sc-status-indicator
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(scope.row.method)).toString(16).substring(0, 6)}`" />
{{ scope.row.method }}
:style="`background: #${Math.abs(this.$TOOL.crypto.hashCode(row.method)).toString(16).substring(0, 6)}`" />
{{ row.method }}
</template>
</el-table-column>
<el-table-column
:formatter="(row) => `${tool.groupSeparator((row.duration / 1000).toFixed(0))} ms`"
:formatter="(row) => `${$TOOL.groupSeparator((row.duration / 1000).toFixed(0))} ms`"
:label="$t('耗时')"
align="right"
prop="duration"
@ -114,8 +113,8 @@
sortable="custom"
width="170"></na-col-user>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip>
<template #default="{ row }">
<na-ip :ip="row.createdClientIp"></na-ip>
</template>
</el-table-column>
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" />
@ -137,57 +136,39 @@
<script>
import naInfo from '@/components/naInfo/index.vue'
import tool from '@/utils/tool'
import ScTable from '@/components/scTable/index.vue'
import ScStatusIndicator from '@/components/scMini/scStatusIndicator.vue'
export default {
computed: {
tool() {
return tool
},
},
components: {
ScStatusIndicator,
ScTable,
naInfo,
},
watch: {},
inject: ['reload'],
computed: {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {
info: false,
},
loading: false,
query: {
dynamicFilter: {
filters: [
{
field: 'createdTime',
operator: 'dateRange',
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
},
],
},
filter: {},
},
dialog: {
info: false,
},
}
},
async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
selection: [],
}
},
inject: ['reload'],
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
@ -243,9 +224,21 @@ export default {
const res = await this.$API.sys_log.query.post({
filter: { id: row.id },
})
this.$refs.info.open(tool.sortProperties(res.data[0]), this.$t('日志详情:{id}', { id: row.id }))
this.$refs.info.open(this.$TOOL.sortProperties(res.data[0]), this.$t('日志详情:{id}', { id: row.id }))
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
]
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -60,10 +60,7 @@
import save from './save'
export default {
inject: ['reload'],
components: {
save,
},
components: { save },
data() {
return {
loading: false,
@ -71,14 +68,7 @@ export default {
filterText: '',
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
},
},
mounted() {
this.getTree()
},
inject: ['reload'],
methods: {
//加载树数据
async getTree() {
@ -167,6 +157,14 @@ export default {
this.reload()
},
},
mounted() {
this.getTree()
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
},
},
}
</script>
<style lang="scss" scoped>

View File

@ -78,38 +78,21 @@
import scIconSelect from '@/components/scIconSelect'
export default {
components: {
scIconSelect,
},
props: {
tree: {
type: Object,
default: () => {},
},
},
components: { scIconSelect },
data() {
return {
form: {
meta: {},
},
loading: false,
treeOptions: [],
rules: {
meta: {
title: [{ required: true, message: '请输入显示名称' }],
},
},
loading: false,
}
},
watch: {
tree: {
handler() {
this.treeOptions = this.treeToMap(this.tree)
},
deep: true,
},
},
mounted() {},
methods: {
//简单化菜单
treeToMap(tree) {
@ -146,6 +129,21 @@ export default {
this.form = Object.assign({}, data, { parentId: pid })
},
},
mounted() {},
props: {
tree: {
type: Object,
default: () => {},
},
},
watch: {
tree: {
handler() {
this.treeOptions = this.treeToMap(this.tree)
},
deep: true,
},
},
}
</script>

View File

@ -35,13 +35,12 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_sitemsg.bulkDelete" :vue="this" />
</div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_sitemsg.pagedQuery"
:context-menus="['id', 'createdUserName', 'msgType', 'title', 'summary', 'createdTime']"
:default-sort="{ prop: 'createdTime', order: 'descending' }"
@ -58,7 +57,7 @@
row-key="id"
stripe>
<el-table-column type="selection" />
<el-table-column :label="$t('消息编号')" prop="id" sortable="custom" width="150" />
<na-col-id :label="$t('消息编号')" prop="id" sortable="custom" width="170" />
<na-col-avatar :label="$t('用户名')" prop="createdUserName" />
<na-col-indicator
:label="$t('消息类型')"
@ -73,7 +72,7 @@
width="150" />
<el-table-column :label="$t('消息主题')" prop="title" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('消息摘要')" prop="summary" show-overflow-tooltip sortable="custom" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -91,37 +90,22 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
},
inject: ['reload'],
data() {
return {
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
},
dialog: {
save: false,
},
selection: [],
}
},
watch: {},
computed: {
naColOperation() {
return naColOperation
@ -130,16 +114,32 @@ export default {
return table
},
},
mounted() {},
created() {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
},
selection: [],
}
},
inject: ['reload'],
methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
})
this.$refs.search.search()
},
//删除
async rowDel(row) {
try {
const res = await this.$API.sys_sitemsg.delete.post({ id: row.id })
@ -149,8 +149,6 @@ export default {
}
this.$refs.table.refresh()
},
//搜索
onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
this.query.dynamicFilter.filters.push({
@ -171,6 +169,14 @@ export default {
this.$refs.table.upData()
},
},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -1,11 +1,5 @@
<template>
<sc-dialog
v-model="visible"
:title="`${titleMap[mode]}${form?.id ?? '...'}`"
:width="800"
@closed="$emit('closed')"
destroy-on-close
full-screen>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<el-form
v-loading="loading"
:disabled="mode === 'view'"
@ -29,7 +23,7 @@
</el-form-item>
<el-form-item :label="$t('消息内容')" prop="content">
<div
:class="this.$TOOL.data.get('APP_DARK') ? 'aie-theme-dark' : 'aie-theme-light'"
:class="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'aie-theme-dark' : 'aie-theme-light'"
ref="editor"
style="width: 100%; height: 30rem"></div>
</el-form-item>
@ -62,7 +56,7 @@
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -72,7 +66,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
@ -87,20 +81,14 @@ export default {
emits: ['success', 'closed'],
data() {
return {
mode: 'add',
titleMap: {
view: this.$t('查看消息'),
add: this.$t('新增消息'),
edit: this.$t('编辑消息'),
},
visible: false,
loading: false,
//表单数据
form: {
userIds: [],
roleIds: [],
deptIds: [],
},
loading: false,
mode: 'add',
//验证规则
rules: {
title: [
@ -122,17 +110,23 @@ export default {
},
],
},
titleMap: {
add: this.$t('新增消息'),
edit: this.$t('编辑消息'),
view: this.$t('查看消息'),
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
const res = await this.$API.sys_sitemsg.get.post({ id: data.id })
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_sitemsg.get.post({ id: data.row.id })
Object.assign(this.form, res.data, {
roleIds: res.data.roles?.map((x) => x.id) ?? [],
deptIds: res.data.depts?.map((x) => x.id) ?? [],
@ -150,7 +144,7 @@ export default {
this.form.content = content.getHtml()
},
})
aiEditor.changeLang(this.$TOOL.data.get('APP_LANG') || sysConfig.LANG)
aiEditor.changeLang(this.$TOOL.data.get('APP_SET_LANG') || sysConfig.APP_SET_LANG)
return this
},
@ -160,20 +154,21 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_sitemsg.create : this.$API.sys_sitemsg.edit
try {
const method = this.mode === 'add' ? this.$API.sys_sitemsg.create : this.$API.sys_sitemsg.edit
this.loading = true
const res = await method.post(Object.assign({}, this.form, { userIds: this.form.userIds.map((x) => x.id) }))
this.loading = false
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
this.loading = false
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
</script>
<style scoped></style>

View File

@ -12,8 +12,26 @@
{ label: $t('禁用'), value: false },
],
},
{
title: $t('无限权限'),
key: 'ignorePermissionControl',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('是'), value: true },
{ label: $t('否'), value: false },
],
},
{
title: $t('显示仪表板'),
key: 'displayDashboard',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('是'), value: true },
{ label: $t('否'), value: false },
],
},
]"
:label-width="10"
:label-width="15"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
@ -27,26 +45,6 @@
placeholder: $t('角色编号 / 角色名称 / 备注'),
style: 'width:20rem',
},
{
type: 'select',
field: ['dy', 'ignorePermissionControl'],
options: [
{ label: '是', value: true },
{ label: '否', value: false },
],
placeholder: $t('无限权限'),
style: 'width:15rem',
},
{
type: 'select',
field: ['dy', 'displayDashboard'],
options: [
{ label: '是', value: true },
{ label: '否', value: false },
],
placeholder: $t('显示仪表板'),
style: 'width:15rem',
},
]"
:vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@ -54,13 +52,26 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<na-button-bulk-del :api="$API.sys_role.bulkDelete" :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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_role.pagedQuery"
:context-menus="['id', 'name', 'sort', 'enabled', 'ignorePermissionControl', 'dataScope', 'displayDashboard', 'createdTime']"
:default-sort="{ prop: 'sort', order: 'descending' }"
@ -77,23 +88,14 @@
row-key="id"
stripe>
<el-table-column type="selection" />
<el-table-column :label="$t('角色编号')" prop="id" sortable="custom" />
<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('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
<el-table-column :label="$t('无限权限')" align="center" prop="ignorePermissionControl" sortable="custom">
<template #default="{ row }">
<el-switch v-model="row.ignorePermissionControl" @change="changeIgnorePermissionControl($event, row)"></el-switch>
</template>
</el-table-column>
<na-col-indicator
:label="$t('无限权限')"
:options="[
{ text: '是', type: 'success', value: true, pulse: true },
{ text: '否', type: 'danger', value: false },
]"
align="center"
prop="ignorePermissionControl"
sortable="custom"></na-col-indicator>
<na-col-indicator
:label="$t('数据范围')"
:options="
@ -107,26 +109,33 @@
width="120">
</na-col-indicator>
<na-col-indicator
:label="$t('显示仪表板')"
:options="[
{ text: '是', type: 'success', value: true },
{ text: '否', type: 'danger', value: false },
]"
align="center"
prop="displayDashboard"
sortable="custom" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" />
<el-table-column :label="$t('显示仪表板')" align="center" prop="displayDashboard" sortable="custom">
<template #default="{ row }">
<el-switch v-model="row.displayDashboard" @change="changeDisplayDashboard($event, row)"></el-switch>
</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="changeEnabled($event, row)"></el-switch>
</template>
</el-table-column>
<na-col-operation
:buttons="
naColOperation.buttons.concat({
icon: 'el-icon-delete',
confirm: true,
title: '删除角色',
click: rowDel,
type: 'danger',
})
naColOperation.buttons.concat(
{
icon: 'el-icon-document-copy',
confirm: true,
title: '复制角色',
click: copyRole,
},
{
icon: 'el-icon-delete',
confirm: true,
title: '删除角色',
click: rowDel,
type: 'danger',
},
)
"
:vue="this" />
</sc-table>
@ -135,16 +144,18 @@
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
</template>
<script>
import saveDialog from './save'
import naColOperation from '@/config/naColOperation'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
saveDialog,
@ -157,12 +168,14 @@ export default {
return table
},
},
created() {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
data() {
return {
dialog: {
save: false,
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
@ -175,7 +188,58 @@ export default {
},
inject: ['reload'],
methods: {
async changeSwitch(event, row) {
async copyRole(row) {
const loading = this.$loading()
await this.$API.sys_role.create.post(Object.assign({}, row, { id: null, name: row.name + '-copy' }))
this.$refs.table.refresh()
loading.close()
},
async setEnabled(enabled) {
let loading
try {
await this.$confirm(
this.$t('确定要 {operator} 选中的 {count} 项吗?', {
operator: enabled ? this.$t('启用') : this.$t('禁用'),
count: this.selection.length,
}),
this.$t('提示'),
{
type: 'warning',
},
)
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_role.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(
this.$t('操作成功 {count}/{total} 项', {
count: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
total: this.selection.length,
}),
)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
async changeIgnorePermissionControl(event, row) {
try {
await this.$API.sys_role.setIgnorePermissionControl.post(row)
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.$refs.table.refresh()
},
async changeDisplayDashboard(event, row) {
try {
await this.$API.sys_role.setDisplayDashboard.post(row)
this.$message.success(this.$t('操作成功'))
} catch {
//
}
this.$refs.table.refresh()
},
async changeEnabled(event, row) {
try {
await this.$API.sys_role.setEnabled.post(row)
this.$message.success(this.$t('操作成功'))
@ -187,8 +251,8 @@ export default {
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()
})
this.$refs.search.search()
},
async rowDel(row) {
try {
@ -234,7 +298,13 @@ export default {
this.$refs.table.upData()
},
},
mounted() {},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: ['keywords'],
watch: {},
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<sc-dialog v-model="visible" :title="`${titleMap[mode]}${form?.id ?? '...'}`" @closed="$emit('closed')" destroy-on-close full-screen>
<div v-loading="loading">
<el-tabs tab-position="top">
<el-tabs v-model="tabId" tab-position="top">
<el-tab-pane :label="$t('基本信息')">
<el-form :disabled="mode === 'view'" :model="form" :rules="rules" label-width="15rem" ref="dialogForm">
<el-form-item :label="$t('角色名称')" prop="name">
@ -76,10 +76,13 @@
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('用户列表')" name="user">
<user v-if="tabId === 'user'" :role-id="form.id"></user>
</el-tab-pane>
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -89,32 +92,23 @@
</div>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $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>
import { defineAsyncComponent } from 'vue'
const User = defineAsyncComponent(() => import('@/views/sys/user/index.vue'))
export default {
components: {},
emits: ['success', 'closed'],
components: { User },
data() {
return {
mode: 'add',
titleMap: {
add: this.$t('新增角色'),
edit: this.$t('编辑角色'),
view: this.$t('查看角色'),
},
visible: false,
loading: false,
trees: {
menu: [],
api: [],
dept: [],
},
//表单数据
form: { displayDashboard: false, sort: 100, enabled: true },
loading: false,
mode: 'add',
//验证规则
rules: {
sort: [
@ -126,12 +120,25 @@ export default {
],
name: [{ required: true, message: '请输入角色名称' }],
},
tabId: '0',
titleMap: {
add: this.$t('新增角色'),
edit: this.$t('编辑角色'),
view: this.$t('查看角色'),
},
trees: {
menu: [],
api: [],
dept: [],
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
async getTrees(name) {
this.trees[name] = (await this.$API[`sys_${name}`].query.post()).data
const res = await this.$API[`sys_${name}`].query.post()
this.trees[name] = res.data
await this.$nextTick()
await this.$refs[name].setCheckedKeys(
(this.form[`${name}Ids`] || []).filter((key) => this.$refs[name].getNode(key).isLeaf),
@ -139,12 +146,13 @@ export default {
)
},
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (data) {
Object.assign(this.form, (await this.$API.sys_role.get.post({ id: data.id })).data)
this.mode = data.mode
if (data.row?.id) {
const res = await this.$API.sys_role.get.post({ id: data.row.id })
Object.assign(this.form, res.data)
}
await this.getTrees('menu')
await this.getTrees('api')
@ -159,26 +167,26 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_role.create : this.$API.sys_role.edit
const postData = Object.assign({}, this.form, {
deptIds: this.$refs.dept.getCheckedKeys().concat(this.$refs.dept.getHalfCheckedKeys()),
menuIds: this.$refs.menu.getCheckedKeys().concat(this.$refs.menu.getHalfCheckedKeys()),
apiIds: this.$refs.api.getCheckedKeys().concat(this.$refs.api.getHalfCheckedKeys()),
})
const method = this.mode === 'add' ? this.$API.sys_role.create : this.$API.sys_role.edit
try {
const res = await method.post(postData)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
//
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>
<style></style>
<style scoped></style>

View File

@ -50,12 +50,25 @@
ref="search" />
</div>
<div class="right-panel">
<na-button-add :vue="this" />
<el-button @click="this.dialog.save = { mode: 'add' }" icon="el-icon-plus" type="primary"></el-button>
<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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<sc-table
v-loading="loading"
:apiObj="$API.sys_user.pagedQuery"
:context-menus="['id', 'userName', 'mobile', 'email', 'enabled', 'createdTime']"
:context-opers="['view', 'edit']"
@ -73,38 +86,56 @@
row-key="id"
stripe>
<el-table-column type="selection" />
<el-table-column :label="$t('用户编号')" prop="id" sortable="custom" width="150" />
<na-col-avatar :label="$t('用户名')" prop="userName" />
<na-col-id :label="$t('用户编号')" prop="id" sortable="custom" width="170" />
<na-col-avatar :label="$t('用户名')" prop="userName" width="170" />
<el-table-column :label="$t('手机号')" align="center" prop="mobile" sortable="custom" width="120" />
<el-table-column :label="$t('邮箱')" prop="email" sortable="custom" />
<na-col-tags :label="$t('所属角色')" @click="(item) => openDialog('sys_role', item.id, 'roleSave')" field="name" prop="roles" />
<na-col-tags :label="$t('所属部门')" @click="(item) => openDialog('sys_dept', item.id, 'deptSave')" field="name" prop="dept" />
<el-table-column :label="$t('邮箱')" align="right" prop="email" sortable="custom" />
<na-col-tags
:label="$t('所属角色')"
@click="(item) => (this.dialog.roleSave = { row: item, mode: 'view' })"
field="name"
prop="roles" />
<na-col-tags
:label="$t('所属部门')"
@click="(item) => (this.dialog.deptSave = { row: item, mode: 'view' })"
field="name"
prop="dept"
width="200" />
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
<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" />
<na-col-operation :vue="this" />
<na-col-operation :vue="this" width="120" />
</sc-table>
</el-main>
</el-container>
<save-dialog
v-if="dialog.save"
@closed="dialog.save = false"
@closed="dialog.save = null"
@mounted="$refs.saveDialog.open(dialog.save)"
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
ref="saveDialog"></save-dialog>
<role-save-dialog v-if="dialog.roleSave" @closed="dialog.roleSave = false" ref="roleSaveDialog"></role-save-dialog>
<dept-save-dialog v-if="dialog.deptSave" @closed="dialog.deptSave = false" ref="deptSaveDialog"></dept-save-dialog>
<role-save-dialog
v-if="dialog.roleSave"
@closed="dialog.roleSave = null"
@mounted="$refs.roleSaveDialog.open(dialog.roleSave)"
ref="roleSaveDialog"></role-save-dialog>
<dept-save-dialog
v-if="dialog.deptSave"
@closed="dialog.deptSave = null"
@mounted="$refs.deptSaveDialog.open(dialog.deptSave)"
ref="deptSaveDialog"></dept-save-dialog>
</template>
<script>
import saveDialog from './save'
import roleSaveDialog from '@/views/sys/role/save.vue'
import deptSaveDialog from '@/views/sys/dept/save.vue'
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
const roleSaveDialog = defineAsyncComponent(() => import('@/views/sys/role/save.vue'))
const deptSaveDialog = defineAsyncComponent(() => import('@/views/sys/dept/save.vue'))
const saveDialog = defineAsyncComponent(() => import('./save.vue'))
export default {
components: {
deptSaveDialog,
@ -116,14 +147,20 @@ export default {
return table
},
},
created() {},
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
if (this.roleId) {
this.query.filter.roleId = this.roleId
}
if (this.deptId) {
this.query.filter.deptId = this.deptId
}
},
data() {
return {
dialog: {
deptSave: false,
roleSave: false,
save: false,
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
@ -136,6 +173,33 @@ export default {
},
inject: ['reload'],
methods: {
async setEnabled(enabled) {
let loading
try {
await this.$confirm(
this.$t('确定要 {operator} 选中的 {count} 项吗?', {
operator: enabled ? this.$t('启用') : this.$t('禁用'),
count: this.selection.length,
}),
this.$t('提示'),
{
type: 'warning',
},
)
loading = this.$loading()
const res = await Promise.all(this.selection.map((x) => this.$API.sys_user.setEnabled.post(Object.assign(x, { enabled: enabled }))))
this.$message.success(
this.$t('操作成功 {count}/{total} 项', {
count: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
total: this.selection.length,
}),
)
} catch {
//
}
this.$refs.table.refresh()
loading?.close()
},
async changeSwitch(event, row) {
try {
await this.$API.sys_user.setEnabled.post(row)
@ -148,18 +212,8 @@ export default {
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 openDialog(api, id, dialog) {
this.loading = true
const res = await this.$API[api].query.post({
filter: { id: id },
})
this.loading = false
this.dialog[dialog] = true
await this.$nextTick()
this.$refs[`${dialog}Dialog`].open('view', res.data[0])
this.$refs.search.search()
},
onSearch(form) {
if (Array.isArray(form.dy.createdTime)) {
@ -181,7 +235,13 @@ export default {
this.$refs.table.upData()
},
},
mounted() {},
mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
},
props: ['keywords', 'roleId', 'deptId'],
watch: {},
}
</script>

View File

@ -2,10 +2,10 @@
<sc-dialog
v-model="visible"
:title="`${titleMap[mode]}${form?.id ?? '...'}`"
:width="800"
@closed="$emit('closed')"
append-to-body
destroy-on-close>
destroy-on-close
full-screen>
<el-form
v-loading="loading"
:disabled="mode === 'view'"
@ -225,7 +225,7 @@
<el-tab-pane v-if="mode === 'view'" :label="$t('原始数据')">
<json-viewer
:expand-depth="5"
:theme="this.$TOOL.data.get('APP_DARK') ? 'dark' : 'light'"
:theme="this.$TOOL.data.get('APP_SET_DARK') || this.$CONFIG.APP_SET_DARK ? 'dark' : 'light'"
:value="form"
copyable
expanded
@ -235,7 +235,7 @@
</el-form>
<template #footer>
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
<el-button v-if="mode !== 'view'" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
<el-button v-if="mode !== 'view'" :disabled="loading" :loading="loading" @click="submit" type="primary">{{ $t('保存') }}</el-button>
</template>
</sc-dialog>
</template>
@ -243,17 +243,8 @@
<script>
export default {
components: {},
emits: ['success', 'closed'],
data() {
return {
mode: 'add',
titleMap: {
view: this.$t('查看用户'),
add: this.$t('新增用户'),
edit: this.$t('编辑用户'),
},
visible: false,
loading: false,
//表单数据
form: {
profile: {
@ -263,6 +254,8 @@ export default {
emergencyContactArea: '',
},
},
loading: false,
mode: 'add',
//验证规则
rules: {
userName: [
@ -300,23 +293,29 @@ export default {
},
],
},
titleMap: {
add: this.$t('新增用户'),
edit: this.$t('编辑用户'),
view: this.$t('查看用户'),
},
visible: false,
}
},
mounted() {},
emits: ['success', 'closed', 'mounted'],
methods: {
//显示
async open(mode = 'add', data) {
async open(data) {
this.visible = true
this.loading = true
this.mode = mode
if (mode === 'add') {
this.mode = data.mode
if (this.mode === 'add') {
this.rules.passwordText[0].required = true
}
if (data) {
const user = (await this.$API.sys_user.get.post({ id: data.id })).data
Object.assign(this.form, user, {
roleIds: user.roles.map((x) => x.id),
deptId: user.dept.id,
if (data.row?.id) {
const res = await this.$API.sys_user.get.post({ id: data.row.id })
Object.assign(this.form, res.data, {
roleIds: res.data.roles.map((x) => x.id),
deptId: res.data.dept.id,
})
await this.getProfile()
}
@ -341,20 +340,20 @@ export default {
if (!valid) {
return false
}
this.loading = true
const method = this.mode === 'add' ? this.$API.sys_user.create : this.$API.sys_user.edit
try {
const method = this.mode === 'add' ? this.$API.sys_user.create : this.$API.sys_user.edit
this.loading = true
const res = await method.post(Object.assign({}, this.form))
this.loading = false
const res = await method.post(this.form)
this.$emit('success', res.data, this.mode)
this.visible = false
this.$message.success(this.$t('操作成功'))
} catch {
this.loading = false
}
} catch {}
this.loading = false
},
},
mounted() {
this.$emit('mounted')
},
}
</script>