xy 2 years ago
parent 3d2934e04f
commit 1c43b46293

@ -0,0 +1,10 @@
import request from '@/utils/request'
export function execSql(data) {
return request({
method: 'post',
url: '/api/backend/user/sql',
data,
isLoading: true
})
}

@ -0,0 +1,97 @@
import Vue from 'vue'
import { MessageBox } from 'element-ui'
import moment from 'moment'
export class ErrorDBManager {
db
request
name = 'Errors'
constructor() {
this.init()
}
init() {
if (!indexedDB) {
console.log('浏览器不支持indexedDB')
return
}
this.request = indexedDB.open('ErrorDatabase')
this.request.onerror = function(event) {
console.log('error', event)
}
this.request.onupgradeneeded = e => {
this.db = e.target.result
if (!this.db.objectStoreNames.contains('Errors')) {
const objectStore = this.db.createObjectStore('Errors', { keyPath: 'id', autoIncrement: true }) // 创建表
objectStore.createIndex('msg', 'msg', { unique: false })
objectStore.createIndex('stack', 'stack', { unique: false })
objectStore.createIndex('data', 'data', { unique: false })
objectStore.createIndex('create_at', 'create_at', { unique: false })
}
}
this.request.onsuccess = e => {
this.db = e.target.result
Vue.config.errorHandler = (err, vm, info) => {
this.addError(err.message, err.stack)
console.error(err)
}
}
}
getErrors() {
if (!indexedDB) {
console.log('浏览器不支持indexedDB')
return Promise.reject('浏览器不支持indexedDB')
}
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['Errors'], 'readonly')
const objectStore = transaction.objectStore('Errors')
const getAllRequest = objectStore.getAll()
getAllRequest.onsuccess = (event) => {
resolve(event.target.result)
}
getAllRequest.onerror = (event) => {
reject(event)
}
})
}
addError(msg, stack, data = '') {
if (!indexedDB) {
console.log('浏览器不支持indexedDB')
return Promise.reject('浏览器不支持indexedDB')
}
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['Errors'], 'readwrite')
const objectStore = transaction.objectStore('Errors')
objectStore.add({
msg,
stack,
data,
create_at: moment().format('YYYY-MM-DD HH:mm:ss')
})
objectStore.onsuccess = e => {
resolve(e)
}
objectStore.onerror = err => {
reject(err)
}
})
}
clearError() {
if (!indexedDB) {
console.log('浏览器不支持indexedDB')
return Promise.reject('浏览器不支持indexedDB')
}
return new Promise((resolve, reject) => {
MessageBox.confirm('确认删除?', '提示').then(_ => {
const transaction = this.db.transaction(['Errors'], 'readwrite')
const objectStore = transaction.objectStore('Errors')
const clear = objectStore.clear()
clear.onsuccess = e => {
resolve(e)
}
clear.onerror = err => {
reject(err)
}
})
})
}
}

@ -0,0 +1 @@
<svg t="1733713185556" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4297" width="200" height="200"><path d="M512.7 812.4c-194 0-364.5-52.1-415.3-125.3V767c0 86.8 186.3 157.5 415.3 157.5S928 853.8 928 767v-80c-50.8 73.2-221.3 125.4-415.3 125.4z m0-170.7c-194 0-364.5-52.1-415.3-125.3v79.9c0 86.8 186.3 157.5 415.3 157.5S928 683.2 928 596.4v-79.9c-50.8 73.1-221.3 125.2-415.3 125.2z m0-170.6c-194 0-364.5-52.1-415.3-125.3v79.9c0 86.8 186.3 157.5 415.3 157.5S928 512.6 928 425.8v-79.9C877.2 419 706.7 471.1 512.7 471.1zM216.8 365.2c78.9 30.6 184 47.5 295.9 47.5 229 0 415.2-70.7 415.2-157.5S741.6 97.7 512.7 97.7 97.5 168.3 97.5 255.2c0 41 42.4 80.1 119.3 110z" fill="" p-id="4298"></path></svg>

After

Width:  |  Height:  |  Size: 740 B

@ -0,0 +1 @@
<svg t="1733732029094" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4293" width="200" height="200"><path d="M875.392 347.52a32 32 0 0 1-6.954667 40.405333l-4.736 3.328-83.2 48.042667c1.493333 4.906667 2.56 9.984 3.072 15.232l0.426667 8.192-0.384 113.28h80.426667a32 32 0 0 1 5.76 63.488L864 640h-81.706667l-1.792 9.045333c-8.661333 40.106667-20.864 76.032-36.693333 107.648l96.554667 55.68a32 32 0 0 1-26.752 57.856l-5.248-2.432-98.432-56.832a276.394667 276.394667 0 0 1-89.472 75.648c-5.546667 2.986667-12.373333 4.821333-23.893334 6.784l-17.834666 2.602667h-5.973334l-2.986666-0.469333-5.12-1.450667-4.992-2.389333-4.437334-3.2-2.218666-2.048a32 32 0 0 1-8.533334-16.64l-0.512-5.674667v-272a32 32 0 0 0-63.488-5.76l-0.512 5.76v272l-0.512 5.674667a31.701333 31.701333 0 0 1-8.533333 16.64l-3.541333 3.157333-4.138667 2.645333-4.778667 2.133334-4.266666 1.152-5.333334 0.597333-5.290666-0.298667-16.213334-2.432-11.818666-2.389333a51.925333 51.925333 0 0 1-12.074667-4.394667 272.853333 272.853333 0 0 1-87.338667-73.216L215.936 871.253333a32 32 0 0 1-36.693333-52.096l4.693333-3.328 98.048-56.490666c-16.213333-32.170667-28.885333-69.077333-37.973333-110.848l-1.706667-8.32H160a32 32 0 0 1-5.76-63.488l5.76-0.512h80v-113.408c0-7.424 1.024-14.592 2.901333-21.418667l-83.413333-50.346667a32 32 0 0 1 27.861333-57.344l5.205334 2.517334 91.392 55.168c8.533333-4.309333 17.92-7.168 27.861333-8.149334l8.192-0.426666h384c12.032 0 23.424 2.645333 33.621333 7.381333l94.08-54.272a32 32 0 0 1 43.690667 11.690667zM512.042667 128c112.469333 0 212.437333 63.36 253.44 158.421333 8.277333 19.072-4.693333 39.978667-25.002667 42.666667l-4.48 0.298667-448 0.213333c-22.826667 0-38.314667-22.528-29.525333-42.965333C299.434667 191.488 399.488 128 512 128z" fill="" p-id="4294" class="custom-cursor-on-hover"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -76,6 +76,10 @@
<SvgIcon icon-class="password" />
<span style="padding-left: 8px;">修改密码</span>
</el-dropdown-item>
<el-dropdown-item divided command="debugger">
<SvgIcon icon-class="debugger" />
<span style="padding-left: 8px;">错误日志</span>
</el-dropdown-item>
<el-dropdown-item divided command="logout">
<SvgIcon icon-class="logout" />
<span style="padding-left: 8px;">退出</span>
@ -265,6 +269,9 @@ export default {
case 'attendance':
this.$router.push('/oa/?to=/attendance')
break
case 'debugger':
this.$router.push('/error')
break
case 'password':
this.$router.push('/info')
break

@ -4,7 +4,7 @@ import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
//import locale from 'element-ui/lib/locale/lang/en' // lang i18n
// import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import '@/styles/index.scss' // global css
@ -14,7 +14,9 @@ import router from './router'
import '@/icons' // icon
import '@/permission' // permission control
import { ErrorDBManager } from '@/error-handle'
new ErrorDBManager()
import '@/provide'
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
@ -69,9 +71,6 @@ Vue.component('CardContainer', CardContainer)
// exec: true,
// });
Vue.config.productionTip = false
Vue.config.errorHandler = (err, vm, info) => {
console.error(info, err)
}
new Vue({
el: '#app',

@ -8,7 +8,7 @@ import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login','/preview'] // no redirect whitelist
const whiteList = ['/login', '/preview'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
@ -48,16 +48,23 @@ router.beforeEach(async(to, from, next) => {
}
if (store.state.permission.addRoutes && store.state.permission.addRoutes instanceof Array && store.state.permission.addRoutes.length > 0) {
if (to.path === '/sql/index' && store.state.user.name !== '管理员') {
next(from.path)
return
}
next()
} else {
try {
// get user info
const { roles } = await store.dispatch('user/getInfo')
const { roles, username } = await store.dispatch('user/getInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
if (to.path === '/sql/index' && username !== '管理员') {
next(from.path)
return
}
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })

@ -0,0 +1,10 @@
import store from '@/store'
import { ErrorDBManager } from './error-handle'
window._logout = () => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
}
const errorDBManager = new ErrorDBManager()
window._addError = errorDBManager.addError.bind(errorDBManager)

@ -47,6 +47,36 @@ export const constantRoutes = [
component: () => import('@/views/preview'),
hidden: true
},
{
path: '/sql',
component: Layout,
redirect: '/sql/index',
children: [{
path: 'index',
component: () => import('@/views/sql'),
name: '编辑',
meta: {
title: '编辑',
icon: 'database'
}
}],
hidden: true
},
{
path: '/error',
component: Layout,
redirect: '/error/index',
children: [{
path: 'index',
component: () => import('@/views/error'),
name: '错误日志',
meta: {
title: '错误日志',
icon: 'debugger'
}
}],
hidden: true
},
{
path: '/',
component: Layout,

@ -3,8 +3,10 @@ import { MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import { throttle } from '@/utils'
import { ErrorDBManager } from '@/error-handle'
import Vue from 'vue'
const errorDBManager = new ErrorDBManager()
let loadingInstance
const loading = throttle(() => {
loadingInstance = Loading.service({
@ -41,6 +43,7 @@ service.interceptors.request.use(
// do something with request error
console.log(error) // for debug
Vue.prototype.$nextTick().then(r => loadingInstance?.close())
errorDBManager.addError(error, 'axios-request', error.config.params || error.config.data)
return Promise.reject(error)
}
)
@ -89,6 +92,7 @@ service.interceptors.response.use(
})
})
}
errorDBManager.addError(res.msg, `axios-response ${response.config.url}`, response.config.params || response.config.data)
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res.data
@ -97,13 +101,14 @@ service.interceptors.response.use(
error => {
loadingInstance?.close()
console.error('err' + error) // for debug
if (!response.config.notShowError) {
if (!error.config.notShowError) {
Message({
message: /Network Error/g.test(error) ? '网络错误' : '系统错误',
message: /Network Error/g.test(error) ? "网络错误" : (/timeout/g.test(error) ? "网络超时" : "系统错误"),
type: 'error',
duration: 5 * 1000
})
}
errorDBManager.addError(error.toString(), `axios-request ${error.config.url}`, error.config.params || error.config.data)
return Promise.reject(error)
}
)

@ -36,6 +36,7 @@
<script>
import VueGridLayout from 'vue-grid-layout'
import request from '@/utils/request'
const files = require.context(
'@/views/system/DashboardComponents',
false,

@ -0,0 +1,67 @@
<template>
<div>
<card-container>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getDatas"></el-button>
<el-button icon="el-icon-delete-solid" type="danger" plain size="small" @click="deleteDatas"></el-button>
</template>
</vxe-toolbar>
<vxe-table
ref="table"
stripe
style="margin-top: 10px;"
show-overflow
:scroll-y="{ enabled: true, gt: 0 }"
:row-config="{isHover: true}"
:column-config="{ resizable: true }"
:data="datas"
max-height="800"
>
<vxe-column field="id" title="id" width="60" />
<vxe-column field="msg" title="信息" min-width="200" show-overflow="title" />
<vxe-column field="stack" title="栈" min-width="240" show-overflow="title" />
<vxe-column field="data" title="数据" min-width="240" show-overflow="title" :formatter="({ cellValue }) => formatterData(cellValue)" />
<vxe-column field="create_at" title="时间" width="220" align="center" />
</vxe-table>
</card-container>
</div>
</template>
<script>
import { ErrorDBManager } from '@/error-handle'
const errorDbManager = new ErrorDBManager()
export default {
data() {
return {
datas: []
}
},
computed: {},
created() {
this.getDatas()
},
methods: {
formatterData(data) {
try {
return JSON.stringify(data)
} catch (err) {
return data
}
},
getDatas() {
errorDbManager.getErrors().then(res => {
this.datas = res
})
},
deleteDatas() {
errorDbManager.clearError().then(_ => {
this.getDatas()
})
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,177 @@
<template>
<div>
<card-container>
<el-button type="primary" icon="el-icon-caret-right" size="small" @click="submit"></el-button>
<el-input
v-model="sql"
type="textarea"
:autosize="{ minRows: 3 }"
clearable
style="margin-top: 10px;"
/>
<div class="result">
<h4>执行结果</h4>
<el-input
v-model="result"
readonly
type="textarea"
:autosize="{ minRows: 3, maxRows: 10 }"
clearable
/>
</div>
<div class="history">
<h4>历史记录</h4>
<vxe-toolbar>
<template #buttons>
<el-button icon="el-icon-search" type="primary" plain size="small" @click="getHistories"></el-button>
<el-button icon="el-icon-delete-solid" type="danger" plain size="small" @click="deleteHistories"></el-button>
</template>
</vxe-toolbar>
<vxe-table
ref="table"
stripe
style="margin-top: 10px;"
show-overflow
:scroll-y="{ enabled: true, gt: 0 }"
:row-config="{isHover: true}"
:column-config="{ resizable: true }"
:data="histories"
max-height="800"
>
<vxe-column field="id" title="id" width="60" />
<vxe-column type="expand" width="60" align="center">
<template #content="{ row }">
<p style="word-break: break-word;white-space: pre-wrap;">{{ row.result }}</p>
</template>
</vxe-column>
<vxe-column field="sql" title="sql" min-width="200" show-overflow="title" />
<vxe-column field="result" title="结果" min-width="240" show-overflow="title" :formatter="({ cellValue }) => formatterData(cellValue)" />
<vxe-column field="create_at" title="时间" width="220" align="center" />
</vxe-table>
</div>
</card-container>
</div>
</template>
<script>
import { execSql } from '@/api/sql'
export default {
data() {
return {
sql: '',
result: '',
histories: []
}
},
computed: {},
created() {
this.init()
},
mounted() {
this.$nextTick(() => {
if (this.$refs['table'] && this.$refs['toolbar']) {
this.$refs['table'].connect(this.$refs['toolbar'])
}
})
},
beforeDestroy() {
const request = indexedDB.open('SqlDatabase')
request.onsuccess = e => {
const db = e.target.result
db.close()
}
},
methods: {
formatterData(data) {
try {
return JSON.stringify(data)
} catch (err) {
return data
}
},
async submit() {
try {
await this.$confirm('确认执行?', '提示')
const res = await execSql({
sql: this.sql
})
this.result = JSON.stringify(res)
const request = indexedDB.open('SqlDatabase')
request.onsuccess = e => {
const db = e.target.result
const transaction = db.transaction(['SqlLogs'], 'readwrite')
const objectStore = transaction.objectStore('SqlLogs')
objectStore.add({
sql: this.sql,
result: res,
create_at: this.$moment().format('YYYY-MM-DD HH:mm:ss')
})
this.getHistories()
}
} catch (err) {
console.error(err)
}
},
getHistories() {
const request = indexedDB.open('SqlDatabase')
request.onsuccess = e => {
const db = e.target.result
const transaction = db.transaction(['SqlLogs'], 'readonly')
const objectStore = transaction.objectStore('SqlLogs')
const getAllRequest = objectStore.getAll()
getAllRequest.onsuccess = (event) => {
this.histories = event.target.result
}
getAllRequest.onerror = (event) => {
console.error('获取历史记录失败', event)
}
}
},
deleteHistories() {
this.$confirm('确认删除?', '提示').then(_ => {
const request = indexedDB.open('SqlDatabase')
request.onsuccess = e => {
const db = e.target.result
const transaction = db.transaction(['SqlLogs'], 'readwrite')
const objectStore = transaction.objectStore('SqlLogs')
const clear = objectStore.clear()
clear.onsuccess = e => {
this.getHistories()
}
}
})
},
init() {
if (!indexedDB) {
console.log('浏览器不支持indexedDB')
}
const request = indexedDB.open('SqlDatabase')
request.onerror = function(event) {
console.log('error', event)
}
request.onupgradeneeded = e => {
const db = e.target.result
if (!db.objectStoreNames.contains('SqlLogs')) {
const objectStore = db.createObjectStore('SqlLogs', { keyPath: 'id', autoIncrement: true }) //
//
objectStore.createIndex('sql', 'sql', { unique: false })
objectStore.createIndex('result', 'result', { unique: false })
objectStore.createIndex('create_at', 'create_at', { unique: false })
}
}
this.getHistories()
}
}
}
</script>
<style scoped lang="scss">
.result {
margin-top: 10px;
}
.history {
margin-top: 10px;
}
</style>

@ -4,7 +4,7 @@
title="设置模块权限"
:visible.sync="visible"
direction="rtl"
size="72%"
size="78%"
append-to-body
@close="$emit('update:isShow',false)"
>
@ -110,15 +110,16 @@
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column field="name" width="160" title="角色" :edit-render="{ name: 'input', attrs: { type: 'text'} }" />
<vxe-column field="permissions" title="权限菜单" show-overflow="tooltip">
<vxe-column field="permissions" title="权限菜单" min-width="140" show-overflow="tooltip">
<template #default="{ row }">
<div>
<el-tag v-for="item in row.permissions" :key="item.id">{{ item.name }}</el-tag>
</div>
</template>
</vxe-column>
<vxe-column field="allow_level" title="数据权限" width="120" align="center" :edit-render="{ name: 'select', options: [{ label: '', value: 0 },{ label: '', value: 1 },{ label: '', value: 2 }]}" />
<vxe-column field="sortnumber" width="80" title="排序" align="center" :edit-render="{ name: 'input', attrs: { type: 'number' } }" />
<vxe-column field="operate" title="操作" min-width="220">
<vxe-column field="operate" title="操作" min-width="300">
<template #default="{ row }">
<template v-if="isActiveStatus(row,'roleTable')">
<el-button size="small" type="primary" @click="saveRoleRowEvent(row)"></el-button>
@ -127,7 +128,7 @@
<template v-else>
<el-button size="small" type="primary" @click="$refs['ModuleBatchUsers'].setForm(['role_id'], [row.id]),isShowBatchUsers = true">绑定用户</el-button>
<el-button size="small" type="success" @click="bindPermission(row)"></el-button>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
<el-button size="small" type="warning" @click="editRowEvent(row,'roleTable')"></el-button>
<el-button size="small" type="danger" @click="destroyRoleRowEvent(row,'roleTable')"></el-button>
</template>
</template>
@ -276,7 +277,8 @@ export default {
id: '',
module_id: '',
name: '',
sortnumber: 0
sortnumber: 0,
allow_level: 0
},
isShowBindPermissions: false,

@ -15,6 +15,11 @@
<el-form-item label="角色" prop="name" required>
<el-input v-model="form.name" clearable />
</el-form-item>
<el-form-item label="数据权限" prop="allow_level" required>
<el-select v-model="form.allow_level">
<el-option v-for="item in [{ label: '', value: 0 },{ label: '', value: 1 },{ label: '', value: 2 }]" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="排序" prop="sortnumber">
<el-input-number v-model="form.sortnumber" controls-position="right" :precision="0" />
</el-form-item>
@ -45,6 +50,7 @@ export default {
loading: false,
form: {
name: '',
allow_level: 0,
sortnumber: 0
},
rules: {

@ -30,7 +30,7 @@
</template>
<script>
const moduleHistoryMap = JSON.parse(localStorage.getItem('moduleHistoryMap')) ?? {}
const moduleHistoryMap = window._moduleHistoryMap ?? {}
import { getToken } from '@/utils/auth'
// import Wujie from 'wujie-vue2'
import { mapGetters } from 'vuex'
@ -100,7 +100,7 @@ export default {
if (this.$route.meta.isModule) {
moduleHistoryMap[this.$route.meta.moduleName] = new URL(document.querySelector('#iframe-cpn').contentDocument.URL).hash?.replace('#', '')
}
localStorage.setItem('moduleHistoryMap', JSON.stringify(moduleHistoryMap))
window._moduleHistoryMap = moduleHistoryMap
},
postMessage() {
if (this.props.auth_token && this.props.module_name) {

Loading…
Cancel
Save