Compare commits
131 Commits
main
...
heads/orig
| Author | SHA1 | Date | |
|---|---|---|---|
| d7d4c67156 | |||
|
|
c5869e3233 | ||
|
|
c47cb4a508 | ||
|
|
1fe328fbee | ||
|
|
e964662f7a | ||
|
|
7c6e21531b | ||
|
|
43485dddfa | ||
|
|
e1879c706c | ||
|
|
fb517dd799 | ||
|
|
ad87248ab3 | ||
|
|
53ca3df03f | ||
|
|
833598bed1 | ||
|
|
580740ff0a | ||
|
|
6d44b481fc | ||
|
|
07b165f0c0 | ||
|
|
2bd1e39457 | ||
|
|
181ba7fa21 | ||
|
|
8151ba9d2f | ||
|
|
685597f921 | ||
|
|
5471a13628 | ||
|
|
833698168b | ||
|
|
405a8ce511 | ||
|
|
b4a37d06c1 | ||
|
|
6eb2f3da8c | ||
|
|
12e23a9264 | ||
|
|
35cc1579d8 | ||
|
|
9df1b77aee | ||
|
|
4f8ddaf416 | ||
|
|
d48866b248 | ||
|
|
c1bfaabcb2 | ||
|
|
e6f98d4a2d | ||
|
|
93555320bb | ||
|
|
db4e67bb73 | ||
|
|
cd3a90c8f3 | ||
|
|
001a61ce8e | ||
|
|
f63d3a5bca | ||
|
|
4551427965 | ||
|
|
5c26108a4e | ||
|
|
98c87425af | ||
|
|
3dba400a80 | ||
|
|
9e2a566f67 | ||
|
|
9dfe258421 | ||
|
|
b827c47497 | ||
|
|
03839d3d1d | ||
|
|
911d2ec8d0 | ||
|
|
1f46b6a95e | ||
|
|
f5da308123 | ||
|
|
452fb89dc3 | ||
|
|
1cc6193504 | ||
|
|
2c922c5fdd | ||
|
|
a6fc1168d7 | ||
|
|
1f8ecd5756 | ||
|
|
8e83c51106 | ||
|
|
fc3bcbf225 | ||
|
|
05fdc8d4a2 | ||
|
|
740061502e | ||
|
|
aef0b436c4 | ||
|
|
cde0d3ab54 | ||
|
|
e90e8a0904 | ||
|
|
5869d43985 | ||
|
|
bbcbf37eea | ||
|
|
94d7cb492a | ||
|
|
38cf81bcfd | ||
|
|
e1a4f0faf8 | ||
|
|
a481bec282 | ||
|
|
f9ee814adc | ||
|
|
673a773741 | ||
|
|
51ef89bcb7 | ||
|
|
05715b40c4 | ||
|
|
460594f116 | ||
|
|
352161ac96 | ||
|
|
9b7a1e870c | ||
|
|
b2d7a3fc14 | ||
|
|
e40da9d7cd | ||
|
|
92e032c948 | ||
|
|
a59b4c1328 | ||
|
|
761a75ed40 | ||
|
|
e7fd1cfaa6 | ||
|
|
97b480c143 | ||
|
|
0797a69b9a | ||
|
|
beb98ce525 | ||
|
|
7a989d3db5 | ||
|
|
8eb8480265 | ||
|
|
61a842b0cd | ||
|
|
b378aca3ab | ||
|
|
50156e770d | ||
|
|
a1845585a6 | ||
|
|
9bd6f19581 | ||
|
|
343235bc40 | ||
|
|
f411cda24e | ||
|
|
e62a10a6f2 | ||
|
|
a5a4a42a96 | ||
|
|
c379a8300c | ||
|
|
53d5b044db | ||
|
|
bca5d80cb3 | ||
|
|
b57ef56c19 | ||
|
|
b6ea3e685c | ||
|
|
4b101f97d7 | ||
|
|
56b8deaea4 | ||
|
|
99801bd01d | ||
|
|
a503de8d5d | ||
|
|
b2e5ea03f6 | ||
|
|
00ec12bac3 | ||
|
|
5e19346641 | ||
|
|
1fa0fff2cf | ||
|
|
7a9635921c | ||
|
|
851ba00b0d | ||
|
|
05b7b4d6db | ||
|
|
5e2232d7a1 | ||
|
|
44041620a1 | ||
|
|
89b6be286d | ||
|
|
b45d893a60 | ||
|
|
b8713729d3 | ||
|
|
10c67a15bf | ||
|
|
dd87d9f305 | ||
|
|
09c57e6ac1 | ||
|
|
44eed48054 | ||
|
|
d95fdf0021 | ||
|
|
032d88c996 | ||
|
|
91ded061e7 | ||
|
|
ce81beaf8c | ||
|
|
0c561459e9 | ||
|
|
9dd69d768d | ||
|
|
15d09bdf3f | ||
|
|
7a22b57038 | ||
|
|
0a208ede08 | ||
|
|
67916c5a9e | ||
|
|
57a2564bda | ||
|
|
19edbc6703 | ||
|
|
d1b1575535 | ||
|
|
e6508d1a11 |
19
.vscode/settings.json
vendored
@ -5,11 +5,7 @@
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnType": true,
|
||||
// 配置eslint适用于vue代码
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"vue"
|
||||
],
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
@ -22,12 +18,15 @@
|
||||
"[js]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
// 保存时 prettier 自动格式化
|
||||
"editor.formatOnSave": true,
|
||||
// 保存时自动启用 eslint --fix 自动修复
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true,
|
||||
"eslint.autoFixOnSave": true,
|
||||
"eslint.autoFixOnSave": true
|
||||
},
|
||||
"eslint.options": {
|
||||
"overrideConfig": {
|
||||
@ -49,16 +48,12 @@
|
||||
},
|
||||
"i18n-ally.displayLanguage": "zh-cn",
|
||||
"i18n-ally.enabledParsers": ["json", "js"],
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/i18n/lang",
|
||||
"src/i18n/pages/login",
|
||||
"src/i18n/pages/menu",
|
||||
],
|
||||
"i18n-ally.localesPaths": ["src/i18n/lang", "src/i18n/pages/login", "src/i18n/pages/menu"],
|
||||
"i18n-ally.extract.parsers.html": {
|
||||
"attributes": ["text", "title", "alt", "placeholder", "label", "aria-label"],
|
||||
"ignoredTags": ["script", "style"],
|
||||
"vBind": true,
|
||||
"inlineText": true
|
||||
},
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
@ -130,7 +130,7 @@
|
||||
<div class="loading-wrp">
|
||||
<span class="dot dot-spin"> <i></i> <i></i> <i></i> <i></i> </span>
|
||||
</div>
|
||||
<h5>Loading...</h5>
|
||||
<h5>正在加载系统资源...</h5>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
21
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zr.admin",
|
||||
"version": "3.8.2",
|
||||
"version": "v20230920",
|
||||
"description": "ZRAdmin.NET管理系统",
|
||||
"author": "ZR",
|
||||
"license": "MIT",
|
||||
@ -16,38 +16,45 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^0.2.7",
|
||||
"@microsoft/signalr": "^6.0.5",
|
||||
"@microsoft/signalr": "^6.0.21",
|
||||
"@vueuse/core": "^8.9.4",
|
||||
"@wangeditor/editor": "^5.1.1",
|
||||
"@wangeditor/editor-for-vue": "^5.1.11",
|
||||
"axios": "^0.27.2",
|
||||
"countup.js": "^2.1.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"echarts": "5.2.2",
|
||||
"element-plus": "^2.3.6",
|
||||
"element-plus": "^2.3.12",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "6.4.6",
|
||||
"highlight.js": "^11.5.1",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-md5": "^0.7.3",
|
||||
"jsencrypt": "3.2.1",
|
||||
"md-editor-v3": "^1.11.11",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "^2.0.33",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"qrcodejs2-fixes": "^0.0.2",
|
||||
"qs": "^6.11.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"v-code-diff": "^1.8.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-cropper": "1.0.2",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.2.2"
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.2.2",
|
||||
"vxe-table": "^4.5.12",
|
||||
"xe-utils": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"consola": "^3.2.3",
|
||||
"sass": "1.45.0",
|
||||
"unplugin-auto-import": "0.5.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-compression": "^0.3.6",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vite-plugin-svg-icons": "1.0.5",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0"
|
||||
}
|
||||
|
||||
16
src/App.vue
@ -7,9 +7,9 @@
|
||||
import useUserStore from './store/modules/user'
|
||||
import useAppStore from './store/modules/app'
|
||||
import { ElConfigProvider } from 'element-plus'
|
||||
import zh from 'element-plus/lib/locale/lang/zh-cn' // 中文语言
|
||||
import en from 'element-plus/lib/locale/lang/en' // 英文语言
|
||||
import tw from 'element-plus/lib/locale/lang/zh-tw' //繁体
|
||||
import zh from 'element-plus/dist/locale/zh-cn.mjs' // 中文语言
|
||||
import en from 'element-plus/dist/locale/en.mjs' // 英文语言
|
||||
import tw from 'element-plus/dist/locale/zh-tw.mjs' //繁体
|
||||
import defaultSettings from '@/settings'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
@ -28,7 +28,11 @@ watch(
|
||||
token,
|
||||
(val) => {
|
||||
if (val) {
|
||||
proxy.signalr.start()
|
||||
proxy.signalr.start().then(async (res) => {
|
||||
if (res) {
|
||||
await proxy.signalr.SR.invoke('logOut')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -53,4 +57,8 @@ watch(
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
console.log('🎉源码地址: https://gitee.com/izory/ZrAdminNetCore')
|
||||
console.log('📖官方文档:http://www.izhaorui.cn/doc')
|
||||
console.log('💰打赏作者:http://www.izhaorui.cn/doc/support.html')
|
||||
console.log('📱移动端体验:http://www.izhaorui.cn/h5')
|
||||
</script>
|
||||
|
||||
@ -5,7 +5,7 @@ export function upload(data) {
|
||||
url: '/common/UploadFile',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
@ -18,6 +18,6 @@ export function sendEmail(data) {
|
||||
return request({
|
||||
url: '/common/SendEmail',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,9 +10,19 @@ export function listOnline(query) {
|
||||
}
|
||||
|
||||
// 强退用户
|
||||
export function forceLogout(tokenId) {
|
||||
export function forceLogout(data) {
|
||||
return request({
|
||||
url: '/monitor/online/' + tokenId,
|
||||
method: 'delete'
|
||||
url: '/monitor/online/force',
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 批量强退用户
|
||||
export function forceLogoutAll(data) {
|
||||
return request({
|
||||
url: '/monitor/online/batchForce',
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
29
src/api/monitor/sqldifflog.js
Normal file
@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 数据差异日志分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listSqlDiffLog(query) {
|
||||
return request({
|
||||
url: 'monitor/SqlDiffLog/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据差异日志
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delSqlDiffLog(pid) {
|
||||
return request({
|
||||
url: 'monitor/SqlDiffLog/' + pid,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 导出数据差异日志
|
||||
export async function exportSqlDiffLog(query) {
|
||||
await downFile('monitor/SqlDiffLog/export', { ...query })
|
||||
}
|
||||
@ -8,7 +8,7 @@ export function listCommonLang(query) {
|
||||
return request({
|
||||
url: 'system/CommonLang/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
params: query
|
||||
})
|
||||
}
|
||||
/**
|
||||
@ -18,7 +18,7 @@ export function listCommonLang(query) {
|
||||
export function listLangByLocale(locale) {
|
||||
return request({
|
||||
url: 'system/CommonLang/list/' + locale,
|
||||
method: 'get',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export function addCommonLang(data) {
|
||||
return request({
|
||||
url: 'system/CommonLang',
|
||||
method: 'post',
|
||||
data: data,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ export function updateCommonLang(data) {
|
||||
return request({
|
||||
url: 'system/CommonLang',
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@ -67,7 +67,6 @@ export function getCommonLangByKey(key) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除多语言配置
|
||||
* @param {主键} pid
|
||||
@ -79,6 +78,18 @@ export function delCommonLang(pid) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除多语言配置
|
||||
* @param {key} langkey
|
||||
*/
|
||||
export function delCommonLangByKey(langkey) {
|
||||
return request({
|
||||
url: 'system/CommonLang/ByKey',
|
||||
method: 'delete',
|
||||
params: { langkey }
|
||||
})
|
||||
}
|
||||
|
||||
// 导出多语言配置
|
||||
export function exportCommonLang(query) {
|
||||
return request({
|
||||
|
||||
46
src/api/system/emaillog.js
Normal file
@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 邮件发送记录分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listEmailLog(query) {
|
||||
return request({
|
||||
url: 'system/EmailLog/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增邮件发送记录
|
||||
* @param data
|
||||
*/
|
||||
export function sendEmail(data) {
|
||||
return request({
|
||||
url: 'system/EmailLog/sendEmail',
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取邮件发送记录详情
|
||||
* @param {Id}
|
||||
*/
|
||||
export function getEmailLog(id) {
|
||||
return request({
|
||||
url: 'system/EmailLog/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除邮件发送记录
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delEmailLog(pid) {
|
||||
return request({
|
||||
url: 'system/EmailLog/' + pid,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
57
src/api/system/emailtpl.js
Normal file
@ -0,0 +1,57 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 邮件模板分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listEmailTpl(query) {
|
||||
return request({
|
||||
url: 'system/EmailTpl/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增邮件模板
|
||||
* @param data
|
||||
*/
|
||||
export function addEmailTpl(data) {
|
||||
return request({
|
||||
url: 'system/EmailTpl',
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 修改邮件模板
|
||||
* @param data
|
||||
*/
|
||||
export function updateEmailTpl(data) {
|
||||
return request({
|
||||
url: 'system/EmailTpl',
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取邮件模板详情
|
||||
* @param {Id}
|
||||
*/
|
||||
export function getEmailTpl(id) {
|
||||
return request({
|
||||
url: 'system/EmailTpl/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除邮件模板
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delEmailTpl(pid) {
|
||||
return request({
|
||||
url: 'system/EmailTpl/' + pid,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
export function login(username, password, code, uuid, clientId) {
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
uuid,
|
||||
clientId
|
||||
}
|
||||
return request({
|
||||
url: '/login',
|
||||
@ -67,3 +68,51 @@ export function oauthCallback(data, params) {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function generateQrcode(data) {
|
||||
return request({
|
||||
url: '/GenerateQrcode',
|
||||
method: 'GET',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新二维码
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function verifyScan(data) {
|
||||
return request({
|
||||
url: '/VerifyScan',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function checkMobile(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
data: data,
|
||||
url: '/checkMobile'
|
||||
})
|
||||
}
|
||||
|
||||
// 登录方法
|
||||
export function phoneLogin(data) {
|
||||
return request({
|
||||
url: '/phoneLogin',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import request from '@/utils/request'
|
||||
// 查询菜单列表
|
||||
export function listMenu(query) {
|
||||
return request({
|
||||
url: '/system/menu/list',
|
||||
url: '/system/menu/treelist',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
// 查询角色列表
|
||||
export function listRole(query) {
|
||||
@ -22,7 +23,7 @@ export const addRole = (data) => {
|
||||
return request({
|
||||
url: '/system/role/edit',
|
||||
method: 'post',
|
||||
data: data,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,3 +74,7 @@ export function exportRole(query) {
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 导出角色菜单
|
||||
export async function exportRoleMenu(query) {
|
||||
await downFile('/system/role/exportRoleMenu', { ...query })
|
||||
}
|
||||
|
||||
40
src/api/system/smscodelog.js
Normal file
@ -0,0 +1,40 @@
|
||||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 短信验证码记录分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listSmscodeLog(query) {
|
||||
return request({
|
||||
url: 'system/SmscodeLog/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信验证码记录详情
|
||||
* @param {Id}
|
||||
*/
|
||||
export function getSmscodeLog(id) {
|
||||
return request({
|
||||
url: 'system/SmscodeLog/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除短信验证码记录
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delSmscodeLog(pid) {
|
||||
return request({
|
||||
url: 'system/SmscodeLog/' + pid,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 导出短信验证码记录
|
||||
export async function exportSmscodeLog(query) {
|
||||
await downFile('system/SmscodeLog/export', { ...query })
|
||||
}
|
||||
@ -22,7 +22,7 @@ export function getUser(userId) {
|
||||
// 新增用户
|
||||
export function addUser(data) {
|
||||
return request({
|
||||
url: '/system/user/edit',
|
||||
url: '/system/user/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
@ -116,7 +116,8 @@ export function uploadAvatar(data) {
|
||||
return request({
|
||||
url: '/system/user/profile/avatar',
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4017520 */
|
||||
src: url('iconfont.woff2?t=1681548759797') format('woff2'),
|
||||
url('iconfont.woff?t=1681548759797') format('woff'),
|
||||
url('iconfont.ttf?t=1681548759797') format('truetype');
|
||||
src: url('iconfont.woff2?t=1695878634619') format('woff2'),
|
||||
url('iconfont.woff?t=1695878634619') format('woff'),
|
||||
url('iconfont.ttf?t=1695878634619') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,18 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-exit-fullscreen:before {
|
||||
content: "\e66d";
|
||||
}
|
||||
|
||||
.icon-validCode:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.icon-index:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.icon-tree-table:before {
|
||||
content: "\e637";
|
||||
}
|
||||
@ -89,10 +101,6 @@
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.icon-index:before {
|
||||
content: "\e63b";
|
||||
}
|
||||
|
||||
.icon-ipvisits:before {
|
||||
content: "\e63c";
|
||||
}
|
||||
|
||||
@ -5,6 +5,27 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "37140510",
|
||||
"name": "exit-fullscreen",
|
||||
"font_class": "exit-fullscreen",
|
||||
"unicode": "e66d",
|
||||
"unicode_decimal": 58989
|
||||
},
|
||||
{
|
||||
"icon_id": "37139279",
|
||||
"name": "validCode",
|
||||
"font_class": "validCode",
|
||||
"unicode": "e621",
|
||||
"unicode_decimal": 58913
|
||||
},
|
||||
{
|
||||
"icon_id": "35896169",
|
||||
"name": "index",
|
||||
"font_class": "index",
|
||||
"unicode": "e61f",
|
||||
"unicode_decimal": 58911
|
||||
},
|
||||
{
|
||||
"icon_id": "35076965",
|
||||
"name": "tree-table",
|
||||
@ -138,13 +159,6 @@
|
||||
"unicode": "e63a",
|
||||
"unicode_decimal": 58938
|
||||
},
|
||||
{
|
||||
"icon_id": "35076497",
|
||||
"name": "index",
|
||||
"font_class": "index",
|
||||
"unicode": "e63b",
|
||||
"unicode_decimal": 58939
|
||||
},
|
||||
{
|
||||
"icon_id": "35076498",
|
||||
"name": "ipvisits",
|
||||
|
||||
1
src/assets/icons/svg/emailLog.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700531242903" class="icon" viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4238" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.5859375" height="200"><path d="M984.064 461.824q20.48-12.288 31.232-9.728t10.752 16.896l0 318.464q0 36.864-8.704 58.88t-23.552 32.768-34.816 13.824-43.52 3.072l-45.056 0q-33.792 0-81.408-0.512t-105.984-0.512l-119.808 0-120.832 0-110.592 0-90.112 0-57.344 0q-36.864 0-60.416-9.728t-36.864-25.088-18.432-35.328-5.12-41.472l0-304.128q0-20.48 10.24-26.112t25.6 4.608q4.096 3.072 18.432 12.288t33.28 20.992 40.448 25.088 39.936 24.576 31.744 19.968 17.408 10.752q13.312 8.192 12.288 17.92t-6.144 17.92q-4.096 8.192-12.288 25.6t-17.408 36.352-17.408 35.84-12.288 24.064q-5.12 8.192-3.584 13.824t5.632 8.704 10.24 1.536 11.264-7.68q3.072-3.072 15.872-17.92t28.16-32.256 29.184-32.256 17.92-18.944q5.12-5.12 15.872-11.776t22.016-1.536q6.144 4.096 20.992 13.824t32.768 20.992 35.84 23.04 30.208 18.944q12.288 8.192 26.112 10.24t26.112 1.024 22.528-4.608 15.36-6.656q6.144-3.072 23.552-13.312t37.888-23.04 38.4-24.064 25.088-15.36q11.264-6.144 19.456-3.584t17.408 10.752q4.096 3.072 16.384 18.432t27.136 33.28 27.648 34.304 17.92 22.528q6.144 7.168 12.8 7.68t11.264-3.072 6.144-10.24-3.584-14.848q-2.048-4.096-8.192-19.968t-13.824-35.328-15.872-37.888-12.288-26.624q-8.192-15.36-8.704-24.576t10.752-16.384q3.072-2.048 25.6-16.384t50.176-32.256 53.76-33.792 35.328-22.016zM544.768 637.952q-5.12 1.024-17.408-5.12t-27.648-15.36q-54.272-29.696-108.544-61.44-47.104-26.624-100.864-57.856t-99.84-57.856l0-184.32 2.048 0 0-1.024q0-26.624 10.24-49.664t27.648-40.448 40.448-27.648 49.664-10.24l448.512 0q26.624 0 50.176 10.24t40.96 27.648 27.648 40.448 10.24 49.664l0 1.024 2.048 0 0 184.32q-47.104 26.624-100.864 57.856t-100.864 57.856q-55.296 31.744-108.544 62.464-9.216 5.12-24.064 12.288t-20.992 7.168zM385.024 319.488l320.512 0 0-64.512-320.512 0 0 64.512zM385.024 447.488l320.512 0 0-64.512-320.512 0 0 64.512z" p-id="4239"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/icons/svg/emailSend.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700531099119" class="icon" viewBox="0 0 1044 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4078" xmlns:xlink="http://www.w3.org/1999/xlink" width="203.90625" height="200"><path d="M16.384 157.696q-11.264-8.192-14.336-13.312t-3.072-14.336l0-9.216q0-26.624 13.312-41.984t46.08-15.36l843.776 1.024q33.792 0 47.104 14.336t13.312 39.936l1.024 11.264q0 9.216-2.048 12.8t-16.384 14.848l-418.816 251.904q-10.24 4.096-25.088 11.264t-19.968 8.192q-6.144 0-18.432-5.632t-27.648-14.848zM646.144 572.416q-21.504 0-34.816 9.216t-20.992 23.552-10.24 32.256-2.56 34.304l0 71.68 0 12.288q0 6.144 1.024 11.264l-141.312 0q-72.704 0-136.192-0.512t-111.616-0.512l-70.656 0q-36.864 0-60.416-9.728t-36.864-25.088-18.432-35.328-5.12-41.472l0-378.88q0-21.504 10.24-27.136t25.6 5.632q5.12 3.072 18.432 11.776t31.744 19.968 38.4 24.064 37.888 24.064 30.72 19.456 16.896 10.24q14.336 9.216 16.384 23.04t-3.072 24.064q-4.096 10.24-12.288 26.624t-17.408 33.28-17.92 32.256-11.776 23.552q-5.12 14.336 2.048 19.456t22.528-4.096q3.072-2.048 16.896-15.872t29.184-30.72 29.184-31.744 18.944-17.92q9.216-8.192 24.064-11.776t26.112 2.56q7.168 4.096 19.456 12.288t27.136 17.92 30.208 19.968l27.648 18.432q12.288 8.192 26.112 10.24t26.624 1.024 23.04-4.608 15.36-6.656 19.456-11.776 31.232-18.944 31.744-19.456 22.016-13.312l129.024-79.872q2.048-1.024 12.8-8.192t26.624-17.408 34.816-22.528 35.84-23.04 31.232-19.968 20.48-13.312q19.456-12.288 30.208-9.728t10.752 16.896l0 266.24q-28.672-23.552-55.808-44.032t-49.664-34.816q-22.528-15.36-39.424-10.752t-27.648 19.968-16.384 35.84-5.632 36.864q0 11.264-0.512 18.432t-0.512 12.288q-1.024 5.12-1.024 8.192l-15.36 0-104.448 0zM1028.096 679.936q13.312 10.24 13.824 28.672t-10.752 26.624q-15.36 12.288-35.84 29.184t-42.496 34.304-43.008 34.816-38.4 30.72q-21.504 17.408-30.208 18.432t-8.704-26.624l0-46.08q0-17.408-8.704-28.672t-23.04-11.264l-118.784 0q-14.336 0-28.16-10.24t-13.824-26.624l0-52.224q0-28.672 9.216-34.816t32.768-6.144l20.48 0q9.216 0 20.992 0.512t28.16 0.512l43.008 0q20.48 0 29.184-7.168t8.704-25.6l0-45.056q0-18.432 6.144-23.552t22.528 8.192q16.384 12.288 37.888 29.696t44.544 35.328 45.056 35.84 39.424 31.232z" p-id="4079"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@ -1 +0,0 @@
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
1
src/assets/icons/svg/new.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1692263225130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4039" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M245.76 286.72h552.96c124.928 0 225.28 100.352 225.28 225.28s-100.352 225.28-225.28 225.28H0V532.48c0-135.168 110.592-245.76 245.76-245.76z m133.12 348.16V401.408H348.16v178.176l-112.64-178.176H204.8V634.88h30.72v-178.176L348.16 634.88h30.72z m182.272-108.544v-24.576h-96.256v-75.776h110.592v-24.576h-141.312V634.88h143.36v-24.576h-112.64v-83.968h96.256z m100.352 28.672l-34.816-151.552h-34.816l55.296 233.472H675.84l47.104-161.792 4.096-20.48 4.096 20.48 47.104 161.792h28.672l57.344-233.472h-34.816l-32.768 151.552-4.096 30.72-6.144-30.72-40.96-151.552h-30.72l-40.96 151.552-6.144 30.72-6.144-30.72z" fill="#EE502F" p-id="4040"></path></svg>
|
||||
|
After Width: | Height: | Size: 976 B |
1
src/assets/icons/svg/pc.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691394989170" class="icon" viewBox="0 0 1170 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8595" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.515625" height="200"><path d="M585.142857 365.714286m-36.571428 0a36.571429 36.571429 0 1 0 73.142857 0 36.571429 36.571429 0 1 0-73.142857 0Z" fill="#54C3F1" p-id="8596"></path><path d="M731.428571 365.714286m-36.571428 0a36.571429 36.571429 0 1 0 73.142857 0 36.571429 36.571429 0 1 0-73.142857 0Z" fill="#54C3F1" p-id="8597"></path><path d="M438.857143 365.714286m-36.571429 0a36.571429 36.571429 0 1 0 73.142857 0 36.571429 36.571429 0 1 0-73.142857 0Z" fill="#54C3F1" p-id="8598"></path><path d="M1060.571429 877.714286H109.714286a109.714286 109.714286 0 0 1-109.714286-109.714286V109.714286a109.714286 109.714286 0 0 1 109.714286-109.714286h950.857143a109.714286 109.714286 0 0 1 109.714285 109.714286v658.285714a109.714286 109.714286 0 0 1-109.714285 109.714286zM109.714286 73.142857a36.571429 36.571429 0 0 0-36.571429 36.571429v658.285714a36.571429 36.571429 0 0 0 36.571429 36.571429h950.857143a36.571429 36.571429 0 0 0 36.571428-36.571429V109.714286a36.571429 36.571429 0 0 0-36.571428-36.571429z" fill="#54C3F1" p-id="8599"></path><path d="M36.571429 658.285714h1097.142857v73.142857H36.571429zM402.285714 841.142857h73.142857v146.285714h-73.142857zM694.857143 841.142857h73.142857v146.285714h-73.142857z" fill="#54C3F1" p-id="8600"></path><path d="M292.571429 950.857143h585.142857v73.142857H292.571429z" fill="#54C3F1" p-id="8601"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
src/assets/icons/svg/qr.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691394871411" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2379" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M480 544H368v-64h112V368h64v288h-64V544z m368 304V592h64v320H592v-64h256zM656 656v144h-64V592h208v64H656zM176 176v192h192V176H176z m-64-64h320v320H112V112z m544 64v192h192V176H656z m-64-64h320v320H592V112zM112 480h160v64H112v-64z m640 0h160v64H752v-64zM544 112v160h-64V112h64z m0 640v160h-64V752h64z m-368-96v192h192V656H176z m-64-64h320v320H112V592z m112-368h96v96h-96v-96z m0 480h96v96h-96v-96z m480-480h96v96h-96v-96z" fill="#5090F1" p-id="2380"></path></svg>
|
||||
|
After Width: | Height: | Size: 795 B |
@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569580729849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M513.3 958.5c-142.2 0-397.9-222.1-401.6-440.5V268c1.7-39.6 31.7-72.3 71.1-77.3 49-4.6 97.1-16.5 142.7-35.3 47.8-14 91.9-38.3 129.4-71.1 30.3-24.4 72.9-26.3 105.3-4.6 39.9 30.7 83.8 55.9 130.5 74.6 48.6 14.7 98.2 25.9 148.4 33.7 38.5 7.6 67.1 40.3 69.5 79.5 3.3 84.9 2.5 169.9-2.6 254.7-33.7 281.6-253.7 436.4-392.7 436.3z m-0.1-813.7c-7.2-0.2-14.3 2-20 6.4-39.7 35.2-86.8 61.1-137.7 75.7-46.8 19.2-96.2 31-146.6 35.2-11 3.2-18.8 13-19.5 24.4v230.1c3.5 180.3 223.3 361 323.9 361s287.3-120.2 317.6-360.5c7.3-142.7 0-228.6 0-229.6-1.3-13.3-11-24.3-24-27.3-49.6-7.7-98.6-19-146.5-33.7-46.3-19.5-89.7-45.3-129-76.7-5.8-3.8-12.7-5.5-19.5-4.9l1.3-0.1z" fill="#C6CCDA" p-id="1940"></path><path d="M750.1 428L490.7 673.2c-11.7 11.1-29.5 12.9-43.1 4.2l-6.8-5.8-141.2-149.4c-9.3-9.3-12.7-22.9-9-35.5 3.8-12.6 14.1-22.1 27-24.8 12.9-2.7 26.1 1.9 34.6 11.9L469 597.5l233.7-221c14.6-12.8 36.8-11.6 49.9 2.7 13.2 14.2 11.5 35.3-2.5 48.8" fill="#C6CCDA" p-id="1941"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/qrcode.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/qrcodeH5.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
@ -128,3 +128,23 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
// el-table 表头样式
|
||||
.el-table-header-cell {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
width: 47px !important;
|
||||
}
|
||||
|
||||
// el-tree
|
||||
|
||||
.tree-item-flex {
|
||||
&.is-expanded .el-tree-node__children {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
.login {
|
||||
.login-wrap {
|
||||
background: radial-gradient(220% 105% at top center, #1b2947 10%, #4b76a7 40%, #81acae 65%, #f7f7b6);
|
||||
background-attachment: fixed;
|
||||
overflow: hidden;
|
||||
@ -8,24 +8,31 @@
|
||||
height: 100%;
|
||||
// background-image: url('@/assets/images/login-bg.jpg');
|
||||
background-size: cover;
|
||||
flex-direction: column;
|
||||
|
||||
.login {
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
width: var(--base-login-width);
|
||||
position: relative;
|
||||
// height: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
margin: 10px auto 15px auto;
|
||||
text-align: center;
|
||||
// color: #fff;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
// background-color: hsla(0, 0%, 100%, 0.3);
|
||||
width: var(--base-login-width);
|
||||
padding: 25px 15px 5px 15px;
|
||||
padding: 5px 25px 5px 25px;
|
||||
position: relative;
|
||||
height: 230px;
|
||||
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
height: 30px;
|
||||
width: 14px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
@ -68,6 +75,49 @@
|
||||
}
|
||||
.langSet {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.scan-wrap {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.3s;
|
||||
overflow: hidden;
|
||||
|
||||
.icon {
|
||||
width: 48px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
font-size: 48px;
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 0px;
|
||||
}
|
||||
.scan-delta {
|
||||
position: absolute;
|
||||
width: 35px;
|
||||
height: 70px;
|
||||
z-index: 2;
|
||||
top: 2px;
|
||||
right: 21px;
|
||||
background: var(--el-color-white);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.login-scan-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ $panGreen: #30b08f;
|
||||
// 默认菜单主题风格
|
||||
:root {
|
||||
--base-text-color-rgba: rgba(0, 0, 0, 0.85);
|
||||
--base-menu-background: #fff;
|
||||
--base-sidebar-width: 220px;
|
||||
// 左侧菜单宽度
|
||||
--el-aside-width: 220px;
|
||||
@ -19,7 +20,10 @@ $panGreen: #30b08f;
|
||||
--base-tags-height: 34px;
|
||||
--base-header-height: 50px;
|
||||
//登录框宽度
|
||||
--base-login-width: 280px;
|
||||
--base-login-width: 360px;
|
||||
|
||||
// 侧边栏图标大小
|
||||
--el-menu-icon-width: 14px;
|
||||
}
|
||||
|
||||
/***侧边栏深色配置***/
|
||||
@ -31,11 +35,29 @@ $panGreen: #30b08f;
|
||||
--el-text-color-primary: #e5eaf3;
|
||||
--el-menu-text-color: var(--el-text-color-primary);
|
||||
}
|
||||
// 黑色主题
|
||||
html.dark {
|
||||
/* custom dark bg color */
|
||||
// --el-bg-color: #141414;
|
||||
--base-color-white: #ffffff;
|
||||
--base-text-color-rgba: #ffffff;
|
||||
--base-menu-background: #000;
|
||||
|
||||
// vxe-table黑色样式
|
||||
--vxe-font-color: #98989e;
|
||||
--vxe-primary-color: #2c7ecf;
|
||||
--vxe-icon-background-color: #98989e;
|
||||
--vxe-table-font-color: #98989e;
|
||||
--vxe-table-resizable-color: #95969a;
|
||||
--vxe-table-header-background-color: #28282a;
|
||||
--vxe-table-body-background-color: #151518;
|
||||
--vxe-table-background-color: #4a5663;
|
||||
--vxe-table-border-width: 1px;
|
||||
--vxe-table-border-color: #37373a;
|
||||
|
||||
.current-row {
|
||||
color: #e65d6e;
|
||||
}
|
||||
}
|
||||
html.cafe {
|
||||
filter: sepia(0.9) hue-rotate(315deg) brightness(0.9);
|
||||
|
||||
16
src/components.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import UploadImage from '@/components/ImageUpload.vue'
|
||||
import UploadFile from '@/components/FileUpload'
|
||||
import ImagePreview from '@/components/ImagePreview'
|
||||
import DictTag from '@/components/DictTag'
|
||||
import Dialog from '@/components/Dialog'
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
SvgIcon: typeof SvgIcon,
|
||||
UploadImage: typeof UploadImage,
|
||||
DictTag: typeof DictTag,
|
||||
ImagePreview: typeof ImagePreview,
|
||||
UploadFile: typeof UploadFile,
|
||||
ZrDialog: typeof Dialog
|
||||
}
|
||||
}
|
||||
@ -35,12 +35,10 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="7">
|
||||
指定
|
||||
<el-radio v-model="radioValue" :label="7"> 指定 </el-radio>
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
|
||||
<el-option v-for="item in 31" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
@ -20,12 +20,10 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-radio v-model="radioValue" :label="4"> 指定 </el-radio>
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
|
||||
<el-option v-for="item in 24" :key="item" :label="item - 1" :value="item - 1" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
@ -20,12 +20,10 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-radio v-model="radioValue" :label="4"> 指定 </el-radio>
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
|
||||
<el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
@ -15,17 +15,15 @@
|
||||
<el-radio v-model="radioValue" :label="3">
|
||||
从
|
||||
<el-input-number v-model="average01" :min="1" :max="11" /> 月开始,每
|
||||
<el-input-number v-model="average02" :min="1" :max="12 - average01" /> 月月执行一次
|
||||
<el-input-number v-model="average02" :min="1" :max="maxMonth" /> 月执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8">
|
||||
<el-radio v-model="radioValue" :label="4"> 指定 </el-radio>
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8" style="width: 80%">
|
||||
<el-option v-for="item in monthList" :key="item.key" :label="item.value" :value="item.key" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
@ -81,6 +79,9 @@ const averageTotal = computed(() => {
|
||||
average02.value = props.check(average02.value, 1, 12 - average01.value)
|
||||
return average01.value + '/' + average02.value
|
||||
})
|
||||
const maxMonth = computed(() => {
|
||||
return 12 - average01.value
|
||||
})
|
||||
const checkboxString = computed(() => {
|
||||
return checkboxList.value.join(',')
|
||||
})
|
||||
|
||||
@ -20,12 +20,10 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
指定
|
||||
<el-radio v-model="radioValue" :label="4"> 指定 </el-radio>
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
|
||||
<el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<el-radio :label="2" v-model="radioValue"> 每年 </el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<!-- <el-form-item>
|
||||
<el-radio :label="3" v-model="radioValue">
|
||||
周期从
|
||||
<el-input-number v-model="cycle01" :min="fullYear" :max="maxFullYear - 1" /> -
|
||||
@ -22,7 +22,7 @@
|
||||
<el-input-number v-model="average01" :min="fullYear" :max="maxFullYear - 1" /> 年开始,每
|
||||
<el-input-number v-model="average02" :min="1" :max="10" /> 年执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item>
|
||||
<el-radio :label="5" v-model="radioValue">
|
||||
@ -74,6 +74,9 @@ const averageTotal = computed(() => {
|
||||
average02.value = props.check(average02.value, 1, 10)
|
||||
return average01.value + '/' + average02.value
|
||||
})
|
||||
const maxFullYearLabel = computed(() => {
|
||||
return maxFullYear.value - 1
|
||||
})
|
||||
const checkboxString = computed(() => {
|
||||
return checkboxList.value.join(',')
|
||||
})
|
||||
|
||||
75
src/components/Dialog/index.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<el-dialog v-bind="$attrs" :fullscreen="isFullscreen" ref="myDialogRef" v-model="open">
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="custom-header">
|
||||
<span :id="titleId" :class="titleClass">{{ title }}</span>
|
||||
<span class="fullscreen" @click="handleScreen()">
|
||||
<svg-icon :name="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template v-slot:footer>
|
||||
<slot name="footer"> </slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const isFullscreen = ref(false)
|
||||
const open = ref(false)
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
open.value = val
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
watch(
|
||||
() => props.fullScreen,
|
||||
(val) => {
|
||||
isFullscreen.value = val
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
function handleScreen() {
|
||||
isFullscreen.value = !isFullscreen.value
|
||||
}
|
||||
const expose = {}
|
||||
const myDialogRef = ref()
|
||||
onMounted(() => {
|
||||
const entries = Object.entries(myDialogRef.value)
|
||||
for (const [method, fn] of entries) {
|
||||
expose[method] = fn
|
||||
}
|
||||
})
|
||||
defineExpose(expose)
|
||||
</script>
|
||||
<style>
|
||||
.custom-header {
|
||||
position: relative;
|
||||
}
|
||||
.fullscreen {
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
top: 5px;
|
||||
cursor: pointer;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@ -1,17 +1,28 @@
|
||||
<template>
|
||||
<template v-for="(item, index) in props.options">
|
||||
<template v-for="(item, index) in dataList">
|
||||
<template v-if="values.includes(item.dictValue)">
|
||||
<span v-if="item.listClass == 'default' || item.listClass == ''" :key="item.dictValue" :index="index" :class="item.cssClass">
|
||||
{{ item.dictLabel }} <i v-if="showValue">#{{ item.dictValue }}</i>
|
||||
<template v-if="item.langKey">
|
||||
{{ $t(item.langKey) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ item.dictLabel }}
|
||||
</template>
|
||||
<i v-if="showValue">#{{ item.dictValue }}</i>
|
||||
</span>
|
||||
<el-tag
|
||||
size="small"
|
||||
v-else
|
||||
size="small"
|
||||
:disable-transitions="true"
|
||||
:index="index"
|
||||
:type="item.listClass == 'primary' ? '' : item.listClass"
|
||||
:class="item.cssClass">
|
||||
<template v-if="item.langKey">
|
||||
{{ $t(item.langKey) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ item.dictLabel }}
|
||||
</template>
|
||||
<i v-if="showValue">#{{ item.dictValue }}</i>
|
||||
</el-tag>
|
||||
</template>
|
||||
@ -23,16 +34,43 @@ const props = defineProps({
|
||||
// 数据
|
||||
options: {
|
||||
type: Array,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
// 当前的值
|
||||
value: [Number, String, Array, Boolean],
|
||||
showValue: false,
|
||||
split: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
//自定义属性值 例如:{ label: 'name', value: 'id'}
|
||||
config: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
const dataList = computed(() => {
|
||||
if (props.config) {
|
||||
let config = props.config
|
||||
var newList = []
|
||||
|
||||
for (let d of props.options) {
|
||||
let label = d[config.label]
|
||||
let value = d[config.value]
|
||||
|
||||
newList.push({ dictLabel: label, dictValue: value, ...d })
|
||||
}
|
||||
return newList
|
||||
}
|
||||
return props.options
|
||||
})
|
||||
const values = computed(() => {
|
||||
if (props.value !== null && typeof props.value !== 'undefined') {
|
||||
if (props.split != null && props.split != '') {
|
||||
return props.value.split(props.split) ?? []
|
||||
} else {
|
||||
return Array.isArray(props.value) ? props.value : [String(props.value)]
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -21,17 +21,21 @@ export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => '请输入内容',
|
||||
default: () => '请输入内容'
|
||||
},
|
||||
modelValue: String,
|
||||
// 工具栏
|
||||
toolbarConfig: {
|
||||
type: [Object],
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const editorRef = shallowRef()
|
||||
const valueHtml = ref(props.modelValue)
|
||||
const toolbarConfig = {}
|
||||
const editorConfig = {
|
||||
MENU_CONF: {},
|
||||
placeholder: props.placeholder,
|
||||
placeholder: props.placeholder
|
||||
}
|
||||
//上传图片
|
||||
editorConfig.MENU_CONF['uploadImage'] = {
|
||||
@ -49,7 +53,7 @@ export default {
|
||||
// 自定义增加 http header
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getToken(),
|
||||
userid: useUserStore().userId,
|
||||
userid: useUserStore().userId
|
||||
},
|
||||
// 跨域是否传递 cookie ,默认为 false
|
||||
withCredentials: true,
|
||||
@ -61,7 +65,7 @@ export default {
|
||||
// 从 res 中找到 url alt href ,然后插图图片
|
||||
insertFn(res.data.url)
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
//上传视频
|
||||
editorConfig.MENU_CONF['uploadVideo'] = {
|
||||
@ -84,7 +88,7 @@ export default {
|
||||
// 自定义增加 http header
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getToken(),
|
||||
userid: useUserStore().userId,
|
||||
userid: useUserStore().userId
|
||||
},
|
||||
|
||||
// 跨域是否传递 cookie ,默认为 false
|
||||
@ -97,7 +101,7 @@ export default {
|
||||
// 从 res 中找到 url alt href ,然后插图图片
|
||||
insertFn(res.data.url)
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
@ -118,18 +122,18 @@ export default {
|
||||
editor.clear()
|
||||
return
|
||||
}
|
||||
valueHtml.value = value;
|
||||
},
|
||||
valueHtml.value = value
|
||||
}
|
||||
)
|
||||
return {
|
||||
editorRef,
|
||||
valueHtml,
|
||||
mode: 'default',
|
||||
toolbarConfig,
|
||||
editorConfig,
|
||||
handleCreated,
|
||||
handleChange,
|
||||
toolbarConfig: props.toolbarConfig
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -26,16 +26,16 @@
|
||||
<!-- 上传按钮 -->
|
||||
<el-button type="primary" icon="upload" v-if="!drag">选取文件</el-button>
|
||||
<!-- 上传提示 -->
|
||||
<template #tip>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip" v-if="showTip">
|
||||
请上传
|
||||
<slot name="tip">
|
||||
<template v-if="fileSize">
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||
大小不超过 <b class="text-danger">{{ fileSize }}MB</b>
|
||||
</template>
|
||||
<template v-if="fileType && fileType.length > 0">
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
|
||||
<template v-if="fileType">
|
||||
格式为 <b class="text-danger">{{ fileType.join('/') }}</b>
|
||||
</template>
|
||||
的文件
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
@ -68,13 +68,18 @@ function close() {
|
||||
}
|
||||
function change(val) {
|
||||
const path = val.path
|
||||
const query = val.query
|
||||
if (isHttp(path)) {
|
||||
// http(s):// 路径新窗口打开
|
||||
const pindex = path.indexOf('http')
|
||||
window.open(path.substr(pindex, path.length), '_blank')
|
||||
} else {
|
||||
if (query) {
|
||||
router.push({ path: path, query: JSON.parse(query) })
|
||||
} else {
|
||||
router.push(path)
|
||||
}
|
||||
}
|
||||
|
||||
search.value = ''
|
||||
options.value = []
|
||||
@ -104,7 +109,7 @@ function initFuse(list) {
|
||||
}
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
function generateRoutes(routes, basePath = '', prefixTitle = []) {
|
||||
function generateRoutes(routes, basePath = '', prefixTitle = [], query = {}) {
|
||||
let res = []
|
||||
|
||||
for (const r of routes) {
|
||||
@ -130,6 +135,9 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
|
||||
res.push(data)
|
||||
}
|
||||
}
|
||||
if (r.query) {
|
||||
data.query = r.query
|
||||
}
|
||||
|
||||
// recursive child routes
|
||||
if (r.children) {
|
||||
|
||||
@ -66,7 +66,7 @@ function reset() {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset,
|
||||
reset
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -76,25 +76,14 @@ defineExpose({
|
||||
padding: 10px;
|
||||
.icon-list {
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
display: grid;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
height: 200px;
|
||||
grid-template-columns: repeat(5, 90px);
|
||||
|
||||
.icon-item {
|
||||
// height: 30px;
|
||||
// line-height: 30px;
|
||||
// margin-bottom: -5px;
|
||||
cursor: pointer;
|
||||
width: 19%;
|
||||
text-align: center;
|
||||
// float: left;
|
||||
}
|
||||
.name {
|
||||
// display: inline-block;
|
||||
// vertical-align: -0.15em;
|
||||
// fill: currentColor;
|
||||
// overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<div class="component-upload-image">
|
||||
<el-upload
|
||||
multiple
|
||||
v-bind="$attrs"
|
||||
:action="uploadImgUrl"
|
||||
list-type="picture-card"
|
||||
:on-success="handleUploadSuccess"
|
||||
@ -10,7 +11,7 @@
|
||||
:on-error="handleUploadError"
|
||||
:on-exceed="handleExceed"
|
||||
name="file"
|
||||
:data="data"
|
||||
:data="uploadData"
|
||||
:on-remove="handleRemove"
|
||||
:show-file-list="true"
|
||||
:headers="headers"
|
||||
@ -18,21 +19,38 @@
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:class="{ hide: fileList.length >= limit }">
|
||||
<el-icon class="avatar-uploader-icon"><plus /></el-icon>
|
||||
</el-upload>
|
||||
<!-- 上传提示 -->
|
||||
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip" v-if="showTip">
|
||||
请上传
|
||||
<slot name="tip">
|
||||
<template v-if="fileSize">
|
||||
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
||||
大小不超过 <b class="text-danger">{{ fileSize }}MB</b>
|
||||
</template>
|
||||
<template v-if="fileType">
|
||||
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
|
||||
格式为 <b class="text-danger">{{ fileType.join('/') }}</b>
|
||||
</template>
|
||||
的文件
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
|
||||
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
|
||||
<el-dialog v-model="dialogVisible" append-to-body>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="预览">
|
||||
<el-image style="display: block; max-width: 50%" :src="dialogImageUrl">
|
||||
<template #error>
|
||||
<div class="image-slot">加载失败</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="访问路径">
|
||||
<el-link type="warning" :href="dialogImageUrl" target="_blank">{{ dialogImageUrl }}</el-link>
|
||||
<el-button type="danger" text icon="document-copy" plain class="ml10" v-clipboard:success="copySuccess" v-clipboard:copy="dialogImageUrl"
|
||||
>复制</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@ -45,27 +63,27 @@ const props = defineProps({
|
||||
// 图片数量限制
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 大小限制(MB)
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ['png', 'jpg', 'jpeg'],
|
||||
default: () => ['png', 'jpg', 'jpeg']
|
||||
},
|
||||
// 是否显示提示
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
// 上传携带的参数
|
||||
data: {
|
||||
type: Object,
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
@ -79,6 +97,7 @@ const uploadImgUrl = ref(baseUrl + import.meta.env.VITE_APP_UPLOAD_URL) // 上
|
||||
const headers = ref({ Authorization: 'Bearer ' + getToken() })
|
||||
const fileList = ref([])
|
||||
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
|
||||
const uploadData = computed(() => props.data)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
@ -89,11 +108,7 @@ watch(
|
||||
// 然后将数组转为对象数组
|
||||
fileList.value = list.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
// if (item.indexOf(baseUrl) === -1) {
|
||||
// item = { name: baseUrl + item, url: baseUrl + item }
|
||||
// } else {
|
||||
item = { name: item, url: item }
|
||||
// }
|
||||
}
|
||||
return item
|
||||
})
|
||||
@ -102,7 +117,7 @@ watch(
|
||||
return []
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
// 删除图片
|
||||
@ -187,4 +202,7 @@ function listToString(list, separator) {
|
||||
}
|
||||
return strs != '' ? strs.substr(0, strs.length - 1) : ''
|
||||
}
|
||||
function copySuccess() {
|
||||
proxy.$modal.msgSuccess('复制成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
81
src/components/ImportData/index.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="uploadData">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
name="file"
|
||||
accept=".xlsx,.xls"
|
||||
:data="uploadData"
|
||||
:headers="headers"
|
||||
:action="uploadFileUrl"
|
||||
:disabled="isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="handleFileError"
|
||||
:auto-upload="true">
|
||||
<el-button type="primary" icon="Upload">上传文件</el-button>
|
||||
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" @click="importTemplate" icon="Bottom"> 下载模板 </el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const emit = defineEmits()
|
||||
|
||||
const props = defineProps({
|
||||
importUrl: {
|
||||
type: String
|
||||
},
|
||||
templateUrl: {
|
||||
type: String
|
||||
},
|
||||
// 上传携带的参数
|
||||
data: {
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const baseUrl = import.meta.env.VITE_APP_BASE_API
|
||||
const uploadFileUrl = ref(baseUrl + props.importUrl) // 上传数据url
|
||||
const headers = ref({ Authorization: 'Bearer ' + getToken() })
|
||||
const uploadData = computed(() => props.data)
|
||||
const isUploading = ref(false)
|
||||
|
||||
/**文件上传中处理 */
|
||||
const handleFileUploadProgress = (event, file, fileList) => {
|
||||
isUploading.value = true
|
||||
}
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response, file, fileList) => {
|
||||
const { code, msg } = response
|
||||
|
||||
isUploading.value = false
|
||||
proxy.$refs['uploadRef'].clearFiles()
|
||||
proxy.$refs['uploadRef'].handleRemove(file)
|
||||
|
||||
if (code != 200) {
|
||||
proxy.$modal.msgError('导入数据失败,原因:' + msg)
|
||||
} else {
|
||||
emit('success', response)
|
||||
}
|
||||
}
|
||||
const handleFileError = function (error) {
|
||||
proxy.$modal.msgError('导入数据失败,原因:' + error)
|
||||
}
|
||||
function importTemplate() {
|
||||
proxy.downFile(props.templateUrl)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.uploadData {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
@ -1,55 +1,109 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-popover placement="bottom" trigger="hover" width="300px" popper-class="el-popover-pupop-user-news">
|
||||
<el-popover placement="bottom" trigger="click" width="400px" popper-class="el-popover-pupop-user-news">
|
||||
<template #reference>
|
||||
<el-badge :is-dot="noticeDot" style="line-height: 18px">
|
||||
<el-badge :hidden="allDotNum <= 0" :value="allDotNum" style="line-height: 18px">
|
||||
<el-icon><bell /></el-icon>
|
||||
</el-badge>
|
||||
</template>
|
||||
<div class="layout-navbars-breadcrumb-user-news">
|
||||
<div class="head-box">
|
||||
<div class="head-box-title">通知</div>
|
||||
<div class="head-box-btn" v-if="noticeList.length > 0" @click="onAllReadClick">全部已读</div>
|
||||
</div>
|
||||
<div class="content-box">
|
||||
<template v-if="noticeList.length > 0">
|
||||
<div class="content-box-item" v-for="(v, k) in noticeList" :key="k">
|
||||
<div>{{ v.noticeTitle }}</div>
|
||||
<div class="content-box-msg" v-html="v.noticeContent"></div>
|
||||
<div class="content-box-time">{{ v.updateTime }}</div>
|
||||
</div>
|
||||
<div class="read" @click="onAllReadClick">全部已读</div>
|
||||
<el-tabs v-model="noticeType">
|
||||
<el-tab-pane name="0">
|
||||
<template #label>
|
||||
<el-badge :hidden="newsDot <= 0" :value="newsDot" class="new-item"> 通知 </el-badge>
|
||||
</template>
|
||||
<div class="content-box-empty" v-else>
|
||||
<div class="content-box-empty-margin">
|
||||
<el-icon><Promotion /></el-icon>
|
||||
<div class="mt15">全部已读</div>
|
||||
<div class="content-box">
|
||||
<div class="content-box-item" v-for="item in noticeList" @click="handleDetails(item, 0)">
|
||||
<el-icon :size="30" color="#409EFF"><bell /></el-icon>
|
||||
<div class="content">
|
||||
<div class="title">{{ item.noticeTitle }}</div>
|
||||
<div class="content-box-time">{{ dayjs(item.create_time).format('YYYY-MM-DD') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="noticeList.length <= 0" :image-size="60" description="暂无公告"></el-empty>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane name="1">
|
||||
<template #label>
|
||||
<el-badge :hidden="chatDotNum <= 0" :value="chatDotNum" class="new-item"> 私信 </el-badge>
|
||||
</template>
|
||||
<div class="content-box">
|
||||
<div class="content-box-item" v-for="item in chatList" @click="handleDetails(item, 1)">
|
||||
<el-avatar :src="item.fromUser.avatar"></el-avatar>
|
||||
<div class="content">
|
||||
<div class="title">
|
||||
<span class="name">{{ item.fromUser.nickName }}</span>
|
||||
说:{{ item.message }}
|
||||
</div>
|
||||
<div class="content-box-time">{{ formatTime(item.chatTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="chatList.length <= 0" :image-size="60" description="暂无私信"></el-empty>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="foot-box">
|
||||
<div @click="onGoToGiteeClick" v-if="noticeList.length > 0">前往通知中心</div>
|
||||
</div>
|
||||
<div class="foot-box" @click="onGoToGiteeClick" v-if="noticeList.length > 0">前往通知中心</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-dialog draggable v-model="show" append-to-body>
|
||||
<template #header> {{ info.title }} </template>
|
||||
<template v-if="info">
|
||||
<template v-if="noticeType == 0">
|
||||
<div v-html="info.item.noticeContent"></div>
|
||||
|
||||
<div class="n_right">{{ info.item.create_by }}</div>
|
||||
<div class="n_right">{{ dayjs(info.item.create_time).format('YYYY-MM-DD HH:mm') }}</div>
|
||||
</template>
|
||||
<msgList v-if="noticeType == 1" v-model="info.userId"> </msgList>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="noticeIndex">
|
||||
import msgList from '@/views/components/msgList.vue'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { dayjs } from 'element-plus'
|
||||
import { formatTime } from '@/utils/index'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const noticeType = ref('0')
|
||||
// 小红点
|
||||
const newsDot = ref(false)
|
||||
|
||||
const newsDot = computed(() => {
|
||||
return useSocketStore().newNotice
|
||||
})
|
||||
const show = ref(false)
|
||||
const noticeList = computed(() => {
|
||||
return useSocketStore().noticeList
|
||||
})
|
||||
const noticeDot = computed(() => {
|
||||
return useSocketStore().noticeDot
|
||||
const allDotNum = computed(() => {
|
||||
return useSocketStore().getAllDotNum()
|
||||
})
|
||||
const chatList = computed(() => {
|
||||
return useSocketStore().getSessionList(useUserStore().userId)
|
||||
})
|
||||
const chatDotNum = computed(() => {
|
||||
return useSocketStore().newChat
|
||||
})
|
||||
const info = ref({})
|
||||
function handleDetails(item, type) {
|
||||
show.value = true
|
||||
if (type == 0) {
|
||||
info.value = { type, item, title: item.noticeTitle }
|
||||
} else if (type == 1) {
|
||||
info.value = { type, title: item.fromUser.nickName, userId: item.userId }
|
||||
}
|
||||
}
|
||||
// 全部已读点击
|
||||
function onAllReadClick() {
|
||||
newsDot.value = false
|
||||
proxy.$modal.msg('请自行实现!!!')
|
||||
useSocketStore().readAll(noticeType.value)
|
||||
}
|
||||
// 前往通知中心点击
|
||||
function onGoToGiteeClick() {
|
||||
@ -57,71 +111,78 @@ function onGoToGiteeClick() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.head-box {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
box-sizing: border-box;
|
||||
color: #333333;
|
||||
justify-content: space-between;
|
||||
height: 35px;
|
||||
align-items: center;
|
||||
.head-box-btn {
|
||||
color: #1890ff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.content-box {
|
||||
font-size: 13px;
|
||||
min-height: 160px;
|
||||
max-height: 230px;
|
||||
overflow: auto;
|
||||
|
||||
.content-box-item {
|
||||
padding-top: 12px;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.content-box-msg {
|
||||
color: #999999;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
.content {
|
||||
margin-left: 8px;
|
||||
|
||||
.name {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 2px 10px 0 0;
|
||||
}
|
||||
|
||||
.content-box-time {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
.content-box-empty {
|
||||
height: 260px;
|
||||
display: flex;
|
||||
.content-box-empty-margin {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
i {
|
||||
font-size: 60px;
|
||||
}
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.foot-box {
|
||||
height: 35px;
|
||||
color: #1890ff;
|
||||
color: var(--el-color-primary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-around;
|
||||
border-top: 1px solid #ebeef5;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
:deep(.el-empty__description p) {
|
||||
font-size: 13px;
|
||||
|
||||
.layout-navbars-breadcrumb-user-news {
|
||||
position: relative;
|
||||
.read {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 0;
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.head-box-title {
|
||||
color: var(--base-color-white);
|
||||
.n_right {
|
||||
text-align: right;
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.new-item {
|
||||
.is-fixed {
|
||||
right: -3px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -23,43 +23,43 @@ export default {
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50, 100]
|
||||
},
|
||||
}
|
||||
},
|
||||
// 移动端页码按钮的数量端默认值5
|
||||
pagerCount: {
|
||||
type: Number,
|
||||
default: document.body.clientWidth < 992 ? 5 : 7,
|
||||
default: document.body.clientWidth < 992 ? 5 : 7
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper',
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props, { ctx, emit }) {
|
||||
const currentPage = computed({
|
||||
@ -68,7 +68,7 @@ export default {
|
||||
},
|
||||
set(val) {
|
||||
emit('update:page', val)
|
||||
},
|
||||
}
|
||||
})
|
||||
const pageSize = computed({
|
||||
get() {
|
||||
@ -76,7 +76,7 @@ export default {
|
||||
},
|
||||
set(val) {
|
||||
emit('update:limit', val)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function handleSizeChange(val) {
|
||||
@ -96,15 +96,17 @@ export default {
|
||||
currentPage,
|
||||
pageSize,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleCurrentChange
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
/* background: #fff; */
|
||||
padding: 32px 16px;
|
||||
padding: 20px 16px 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :name="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
|
||||
<svg-icon v-if="isFullscreen" name="exit-fullscreen" @click="toggle" />
|
||||
<svg-icon v-else name="ele-FullScreen" @click="toggle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -9,14 +10,3 @@ import { useFullscreen } from '@vueuse/core'
|
||||
|
||||
const { isFullscreen, enter, exit, toggle } = useFullscreen()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -7,17 +7,17 @@ export default defineComponent({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
default: ''
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ''
|
||||
},
|
||||
// svg 颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
if (props.name?.startsWith('ele')) {
|
||||
@ -25,9 +25,9 @@ export default defineComponent({
|
||||
h(
|
||||
'i',
|
||||
{
|
||||
class: 'el-icon',
|
||||
class: 'el-icon'
|
||||
},
|
||||
[h(resolveComponent(props.name.replace('ele-', '')))],
|
||||
[h(resolveComponent(props.name.replace('ele-', '')))]
|
||||
)
|
||||
} else if (props.name != undefined && props.name != '') {
|
||||
return () =>
|
||||
@ -38,16 +38,17 @@ export default defineComponent({
|
||||
'aria-hidden': true,
|
||||
style: `color: ${props.color}`,
|
||||
class: `svg-icon ${props.className}`,
|
||||
'shape-rendering': 'geometricPrecision'
|
||||
},
|
||||
h('use', {
|
||||
'xlink:href': `#icon-${props.name}`,
|
||||
fill: `${props.color}`,
|
||||
}),
|
||||
fill: `${props.color}`
|
||||
})
|
||||
)
|
||||
} else {
|
||||
return () => h('i')
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -126,7 +126,13 @@ function handleSelect(key, keyPath) {
|
||||
window.open(key, '_blank')
|
||||
} else if (!route || !route.children) {
|
||||
// 没有子路由路径内部打开
|
||||
const routeMenu = childrenMenus.value.find((item) => item.path === key)
|
||||
if (routeMenu && routeMenu.query) {
|
||||
let query = JSON.parse(routeMenu.query)
|
||||
router.push({ path: key, query: query })
|
||||
} else {
|
||||
router.push({ path: key })
|
||||
}
|
||||
appStore.toggleSideBarHide(true)
|
||||
} else {
|
||||
// 显示左侧联动菜单
|
||||
|
||||
@ -3,7 +3,6 @@ import hasPermi from './permission/hasPermi'
|
||||
import clipboard from './module/clipboard'
|
||||
import drag from './module/drag'
|
||||
import waves from './module/waves'
|
||||
// import copyText from './module/copyText'
|
||||
|
||||
export default function directive(app) {
|
||||
app.directive('hasRole', hasRole)
|
||||
@ -11,5 +10,4 @@ export default function directive(app) {
|
||||
app.directive('clipboard', clipboard)
|
||||
app.directive('drag', drag)
|
||||
app.directive('waves', waves)
|
||||
// app.directive('copyText', copyText)
|
||||
}
|
||||
@ -1,77 +1,67 @@
|
||||
/**
|
||||
* v-clipboard 文字复制剪贴
|
||||
*
|
||||
作者: CodePlayer
|
||||
链接: https: //juejin.cn/post/7052968352007847972
|
||||
来源: 稀土掘金
|
||||
著作权归作者所有。 商业转载请联系作者获得授权, 非商业转载请注明出处。
|
||||
*/
|
||||
|
||||
// import Clipboard from 'clipboard'
|
||||
import useClipboard from "vue-clipboard3";
|
||||
const { toClipboard } = useClipboard();
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
const { toClipboard } = useClipboard()
|
||||
export default {
|
||||
// 挂载
|
||||
mounted(el, binding) {
|
||||
// binding.arg 为动态指令参数
|
||||
// 由于 指令是支持响应式的 因此我们指令需要有一个“全局”对象,这里我们为了不借助其他对象浪费资源,就直接使用el自身了
|
||||
// 将copy的值 成功回调 失败回调 及 click事件都绑定到el上 这样在更新和卸载时方便操作
|
||||
switch (binding.arg) {
|
||||
case "copy":
|
||||
const { value, arg } = binding
|
||||
switch (arg) {
|
||||
case 'copy':
|
||||
// copy值
|
||||
el.clipValue = binding.value;
|
||||
el.clipValue = value
|
||||
// click事件
|
||||
el.clipCopy = function () {
|
||||
toClipboard(el.clipValue)
|
||||
.then(result => {
|
||||
el.clipSuccess && el.clipSuccess(result);
|
||||
.then((result) => {
|
||||
el.clipSuccess && el.clipSuccess(result)
|
||||
})
|
||||
.catch(err => {
|
||||
el.clipError && el.clipError(err);
|
||||
});
|
||||
};
|
||||
.catch((err) => {
|
||||
el.clipError && el.clipError(err)
|
||||
})
|
||||
}
|
||||
// 绑定click事件
|
||||
el.addEventListener("click", el.clipCopy);
|
||||
break;
|
||||
case "success":
|
||||
el.addEventListener('click', el.clipCopy)
|
||||
break
|
||||
case 'success':
|
||||
// 成功回调
|
||||
el.clipSuccess = binding.value;
|
||||
break;
|
||||
case "error":
|
||||
el.clipSuccess = binding.value
|
||||
break
|
||||
case 'error':
|
||||
// 失败回调
|
||||
el.clipError = binding.value;
|
||||
break;
|
||||
el.clipError = binding.value
|
||||
break
|
||||
}
|
||||
},
|
||||
// 相应修改 这里比较简单 重置相应的值即可
|
||||
updated(el, binding) {
|
||||
switch (binding.arg) {
|
||||
case "copy":
|
||||
el.clipValue = binding.value;
|
||||
break;
|
||||
case "success":
|
||||
el.clipSuccess = binding.value;
|
||||
break;
|
||||
case "error":
|
||||
el.clipError = binding.value;
|
||||
break;
|
||||
case 'copy':
|
||||
el.clipValue = binding.value
|
||||
break
|
||||
case 'success':
|
||||
el.clipSuccess = binding.value
|
||||
break
|
||||
case 'error':
|
||||
el.clipError = binding.value
|
||||
break
|
||||
}
|
||||
},
|
||||
// 卸载 删除click事件 删除对应的自定义属性
|
||||
unmounted(el, binding) {
|
||||
switch (binding.arg) {
|
||||
case "copy":
|
||||
el.removeEventListener("click", el.clipCopy);
|
||||
delete el.clipValue;
|
||||
delete el.clipCopy;
|
||||
break;
|
||||
case "success":
|
||||
delete el.clipSuccess;
|
||||
break;
|
||||
case "error":
|
||||
delete el.clipError;
|
||||
break;
|
||||
case 'copy':
|
||||
el.removeEventListener('click', el.clipCopy)
|
||||
delete el.clipValue
|
||||
delete el.clipCopy
|
||||
break
|
||||
case 'success':
|
||||
delete el.clipSuccess
|
||||
break
|
||||
case 'error':
|
||||
delete el.clipError
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
/**
|
||||
* v-copyText 复制文本内容
|
||||
* Copyright (c) 2022 ruoyi
|
||||
*/
|
||||
|
||||
export default {
|
||||
beforeMount(el, { value, arg }) {
|
||||
if (arg === "callback") {
|
||||
el.$copyCallback = value;
|
||||
} else {
|
||||
el.$copyValue = value;
|
||||
const handler = () => {
|
||||
copyTextToClipboard(el.$copyValue);
|
||||
if (el.$copyCallback) {
|
||||
el.$copyCallback(el.$copyValue);
|
||||
}
|
||||
};
|
||||
el.addEventListener("click", handler);
|
||||
el.$destroyCopy = () => el.removeEventListener("click", handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyTextToClipboard(input, { target = document.body } = {}) {
|
||||
const element = document.createElement('textarea');
|
||||
const previouslyFocusedElement = document.activeElement;
|
||||
|
||||
element.value = input;
|
||||
|
||||
// Prevent keyboard from showing on mobile
|
||||
element.setAttribute('readonly', '');
|
||||
|
||||
element.style.contain = 'strict';
|
||||
element.style.position = 'absolute';
|
||||
element.style.left = '-9999px';
|
||||
element.style.fontSize = '12pt'; // Prevent zooming on iOS
|
||||
|
||||
const selection = document.getSelection();
|
||||
const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
|
||||
|
||||
target.append(element);
|
||||
element.select();
|
||||
|
||||
// Explicit selection workaround for iOS
|
||||
element.selectionStart = 0;
|
||||
element.selectionEnd = input.length;
|
||||
|
||||
let isSuccess = false;
|
||||
try {
|
||||
isSuccess = document.execCommand('copy');
|
||||
} catch { }
|
||||
|
||||
element.remove();
|
||||
|
||||
if (originalRange) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(originalRange);
|
||||
}
|
||||
|
||||
// Get the focus back on the previously focused element, if any
|
||||
if (previouslyFocusedElement) {
|
||||
previouslyFocusedElement.focus();
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
@ -5,30 +5,31 @@
|
||||
*/
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
el.classList.add('waves-effect');
|
||||
binding.value && el.classList.add(`waves-${binding.value}`);
|
||||
el.classList.add('waves-effect')
|
||||
binding.value && el.classList.add(`waves-${binding.value}`)
|
||||
|
||||
function setConvertStyle(obj) {
|
||||
let style = '';
|
||||
let style = ''
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
|
||||
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`
|
||||
}
|
||||
return style;
|
||||
return style
|
||||
}
|
||||
|
||||
function onCurrentClick(e) {
|
||||
let elDiv = document.createElement('div');
|
||||
elDiv.classList.add('waves-ripple');
|
||||
el.appendChild(elDiv);
|
||||
let elDiv = document.createElement('div')
|
||||
elDiv.classList.add('waves-ripple')
|
||||
el.appendChild(elDiv)
|
||||
let styles = {
|
||||
left: `${e.layerX}px`,
|
||||
top: `${e.layerY}px`,
|
||||
opacity: 1,
|
||||
transform: `scale(${(el.clientWidth / 100) * 10})`,
|
||||
'transition-duration': `750ms`,
|
||||
'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`,
|
||||
};
|
||||
elDiv.setAttribute('style', setConvertStyle(styles));
|
||||
'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`
|
||||
}
|
||||
|
||||
elDiv.setAttribute('style', setConvertStyle(styles))
|
||||
setTimeout(() => {
|
||||
elDiv.setAttribute(
|
||||
'style',
|
||||
@ -36,17 +37,17 @@ export default {
|
||||
opacity: 0,
|
||||
transform: styles.transform,
|
||||
left: styles.left,
|
||||
top: styles.top,
|
||||
top: styles.top
|
||||
})
|
||||
);
|
||||
)
|
||||
setTimeout(() => {
|
||||
elDiv && el.removeChild(elDiv);
|
||||
}, 750);
|
||||
}, 450);
|
||||
elDiv && el.removeChild(elDiv)
|
||||
}, 750)
|
||||
}, 450)
|
||||
}
|
||||
el.addEventListener('mousedown', onCurrentClick, false);
|
||||
el.addEventListener('mousedown', onCurrentClick, false)
|
||||
},
|
||||
unmounted(el) {
|
||||
el.addEventListener('mousedown', () => {});
|
||||
},
|
||||
el.addEventListener('mousedown', () => {})
|
||||
}
|
||||
}
|
||||
@ -87,9 +87,13 @@
|
||||
"bottomBar": "Footer",
|
||||
"identity": "Identity",
|
||||
"content1": "The code is completely free and open source, easy to read and understand, and the interface is simple and beautiful, giving you one more choice and reference for your project.",
|
||||
"topNav": "Top nav",
|
||||
"topNav": "top nav",
|
||||
"commonFuncs": "Common Functions",
|
||||
"openWatermark": "Open Watermark"
|
||||
"openWatermark": "Open Watermark",
|
||||
"workTime": "Today's working times(min)",
|
||||
"onlineClientNum": "Number of online devices",
|
||||
"tagsView": "tabs",
|
||||
"tagsPersist": "tabs Persist"
|
||||
},
|
||||
"common": {
|
||||
"ok": "Ok",
|
||||
@ -114,7 +118,15 @@
|
||||
"female": "Female",
|
||||
"male": "male",
|
||||
"sex": "gender",
|
||||
"systemTips": "system hint"
|
||||
"systemTips": "system hint",
|
||||
"default": "default",
|
||||
"hidden": "hide",
|
||||
"show": "show",
|
||||
"system": "system",
|
||||
"abnormal": "abnormal",
|
||||
"unknow": "unknown",
|
||||
"normal": "normal",
|
||||
"disable": "deactivate"
|
||||
},
|
||||
"btn": {
|
||||
"add": "Add",
|
||||
|
||||
@ -89,7 +89,11 @@
|
||||
"content1": "代码完全免费开源,易读易懂、界面简洁美观,给你的项目多一种选择与参考。",
|
||||
"topNav": "顶部导航",
|
||||
"commonFuncs": "常用功能",
|
||||
"openWatermark": "开启水印"
|
||||
"openWatermark": "开启水印",
|
||||
"workTime": "今日工作时长",
|
||||
"onlineClientNum": "在线设备数",
|
||||
"tagsView": "标签页",
|
||||
"tagsPersist": "标签持久化"
|
||||
},
|
||||
"common": {
|
||||
"ok": "确定",
|
||||
@ -114,7 +118,15 @@
|
||||
"sex": "性别",
|
||||
"male": "男",
|
||||
"female": "女",
|
||||
"systemTips": "系统提示"
|
||||
"unknow": "未知",
|
||||
"systemTips": "系统提示",
|
||||
"show": "显示",
|
||||
"hidden": "隐藏",
|
||||
"default": "默认",
|
||||
"system": "系统",
|
||||
"abnormal": "异常",
|
||||
"normal": "正常",
|
||||
"disable": "停用"
|
||||
},
|
||||
"btn": {
|
||||
"add": "新增",
|
||||
|
||||
@ -89,7 +89,11 @@
|
||||
"content1": "代碼完全免費開源,易讀易懂、界面簡潔美觀,給你的項目多一種選擇與參考。",
|
||||
"topNav": "頂部導航",
|
||||
"commonFuncs": "常用功能",
|
||||
"openWatermark": "開啟水印"
|
||||
"openWatermark": "開啟水印",
|
||||
"workTime": "今日工作時長(分)",
|
||||
"onlineClientNum": "在線設備數",
|
||||
"tagsView": "標簽頁",
|
||||
"tagsPersist": "標簽持久化"
|
||||
},
|
||||
"common": {
|
||||
"ok": "確定",
|
||||
@ -114,7 +118,15 @@
|
||||
"female": "女",
|
||||
"male": "男",
|
||||
"sex": "性別",
|
||||
"systemTips": "系統提示"
|
||||
"systemTips": "系統提示",
|
||||
"abnormal": "異常",
|
||||
"normal": "正常",
|
||||
"disable": "停用",
|
||||
"hidden": "隱藏",
|
||||
"show": "顯示",
|
||||
"system": "系統",
|
||||
"default": "默認",
|
||||
"unknow": "未知"
|
||||
},
|
||||
"btn": {
|
||||
"add": "新增",
|
||||
|
||||
@ -10,6 +10,14 @@
|
||||
"reLogin": "re-register",
|
||||
"invalidSession": "Invalid session, or session has expired, please log in again.",
|
||||
"otherLoginWay": "Other",
|
||||
"register": "Sign up now"
|
||||
"register": "Sign up now",
|
||||
"forgotPwd": "forget",
|
||||
"loginway1": "account",
|
||||
"loginway2": "Phone",
|
||||
"loginway3": "Scan code",
|
||||
"phoneCode": "Please enter SMS verification code",
|
||||
"sendPhoneCode": "Send",
|
||||
"input_phoneNum": "Please enter phone number",
|
||||
"tip_scan_code": "Please use the mobile app to scan the code to log in"
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,14 @@
|
||||
"reLogin": "重新登录",
|
||||
"invalidSession": "无效的会话,或者会话已过期,请重新登录。",
|
||||
"otherLoginWay": "其他登录方式",
|
||||
"register": "注册"
|
||||
"register": "立即注册",
|
||||
"forgotPwd": "忘记密码",
|
||||
"phoneCode": "请输入短信验证码",
|
||||
"sendPhoneCode": "发送验证码",
|
||||
"loginway1": "账号密码",
|
||||
"loginway2": "手机号",
|
||||
"loginway3": "扫码登录",
|
||||
"input_phoneNum": "请输入手机号",
|
||||
"tip_scan_code": "请使用移动端app扫码登录"
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,14 @@
|
||||
"reLogin": "重新登錄",
|
||||
"invalidSession": "無效的會話,或者會話已過期,請重新登錄。",
|
||||
"otherLoginWay": "其他登錄方式",
|
||||
"register": "註冊"
|
||||
"register": "註冊",
|
||||
"forgotPwd": "忘記密碼",
|
||||
"loginway1": "賬號密碼",
|
||||
"loginway2": "手機號",
|
||||
"loginway3": "掃碼登錄",
|
||||
"phoneCode": "請輸入短信驗證碼",
|
||||
"sendPhoneCode": "發送驗證碼",
|
||||
"input_phoneNum": "請輸入手機號",
|
||||
"tip_scan_code": "請使用移動端app掃碼登錄"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ export default {
|
||||
icon: '图标',
|
||||
menuid: '菜单id',
|
||||
menuType: '菜单类型',
|
||||
sort: '排序',
|
||||
sort: '菜单排序',
|
||||
authorityID: '权限标识',
|
||||
componentPath: '组件路径',
|
||||
isShow: '是否显示',
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
:key="item.path"
|
||||
:iframeId="'iframe' + index"
|
||||
v-show="route.path === item.path"
|
||||
:src="item.meta.link"></inner-link>
|
||||
:src="iframeUrl(item.meta.link, item.query)">
|
||||
</inner-link>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
@ -15,4 +16,14 @@ import useTagsViewStore from '@/store/modules/tagsView'
|
||||
|
||||
const route = useRoute()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
function iframeUrl(url, query) {
|
||||
if (Object.keys(query).length > 0) {
|
||||
let params = Object.keys(query)
|
||||
.map((key) => key + '=' + query[key])
|
||||
.join('&')
|
||||
return url + '?' + params
|
||||
}
|
||||
return url
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
<div class="right-menu">
|
||||
<header-search id="header-search" class="right-menu-item" />
|
||||
<Notice title="通知" class="right-menu-item" />
|
||||
<template v-if="appStore.device == 'desktop'">
|
||||
<zr-git title="源码地址" class="right-menu-item" />
|
||||
<zr-doc title="文档地址" class="right-menu-item" />
|
||||
@ -15,7 +16,6 @@
|
||||
</template>
|
||||
<size-select title="布局大小" class="right-menu-item" />
|
||||
<LangSelect title="语言设置" class="right-menu-item" />
|
||||
<Notice title="通知" class="right-menu-item" />
|
||||
|
||||
<el-dropdown @command="handleCommand" class="right-menu-item avatar-container" trigger="hover">
|
||||
<span class="avatar-wrapper">
|
||||
|
||||
@ -21,19 +21,10 @@
|
||||
<el-radio-group v-model="mode" size="small">
|
||||
<el-radio label="dark">{{ $t('layout.darkMode') }}</el-radio>
|
||||
<el-radio label="light">{{ $t('layout.lightMode') }}</el-radio>
|
||||
<el-radio label="cafe">cafe</el-radio>
|
||||
<!-- <el-radio label="contrast">contrast</el-radio> -->
|
||||
<!-- <el-radio label="cafe">cafe</el-radio>
|
||||
<el-radio label="contrast">contrast</el-radio> -->
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<!-- <div class="drawer-item">
|
||||
<span>暗黑模式</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="isDark" class="mt-2" inline-prompt />
|
||||
</span>
|
||||
</div> -->
|
||||
<!-- <h3 class="drawer-title">
|
||||
{{ $t('layout.themeColor') }}
|
||||
</h3> -->
|
||||
<div class="drawer-item">
|
||||
<span>{{ $t('layout.themeColor') }}</span>
|
||||
<span class="comp-style quick-color-wrap">
|
||||
@ -52,7 +43,7 @@
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>{{ $t('layout.open') }} Tags-Views</span>
|
||||
<span>{{ $t('layout.open') }} {{ $t('layout.tagsView') }}</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="tagsView" class="drawer-switch" />
|
||||
</span>
|
||||
@ -90,6 +81,13 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>{{ $t('layout.tagsPersist') }}</span>
|
||||
<span class="comp-style">
|
||||
<el-switch v-model="tabsPersist" class="drawer-switch" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">{{ $t('layout.saveConfig') }}</el-button>
|
||||
@ -100,7 +98,7 @@
|
||||
<script setup>
|
||||
import 'element-plus/theme-chalk/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import { useDark, useCycleList, useColorMode } from '@vueuse/core'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||
import { getLightColor } from '@/utils/index'
|
||||
import { getmark } from '@/utils/wartermark'
|
||||
@ -123,12 +121,10 @@ const mode = useColorMode({
|
||||
modes: {
|
||||
// custom colors
|
||||
contrast: 'dark contrast',
|
||||
cafe: 'cafe'
|
||||
cafe: 'cafe',
|
||||
auto: 'auto'
|
||||
}
|
||||
})
|
||||
const { next } = useCycleList(['light', 'dark', 'cafe', 'contrast'], { initialValue: mode })
|
||||
// const isDark= useDark()
|
||||
|
||||
/** 是否需要topnav */
|
||||
const topNav = computed({
|
||||
get: () => storeSettings.value.topNav,
|
||||
@ -185,6 +181,14 @@ const showWatermark = computed({
|
||||
changeWatermark()
|
||||
}
|
||||
})
|
||||
|
||||
/**标签持久化 */
|
||||
const tabsPersist = computed({
|
||||
get: () => storeSettings.value.tagsViewPersist,
|
||||
set: (val) => {
|
||||
settingsStore.changeSetting({ key: 'tagsViewPersist', value: val })
|
||||
}
|
||||
})
|
||||
const changeWatermark = () => {
|
||||
storeSettings.value.showWatermark ? setWatermark(useUserStore().userInfo.userName) : removeWatermark()
|
||||
}
|
||||
@ -254,7 +258,8 @@ function saveSetting() {
|
||||
sideTheme: storeSettings.value.sideTheme,
|
||||
theme: storeSettings.value.theme,
|
||||
showFooter: storeSettings.value.showFooter,
|
||||
showWatermark: storeSettings.value.showWatermark
|
||||
showWatermark: storeSettings.value.showWatermark,
|
||||
tagsViewPersist: storeSettings.value.tagsViewPersist
|
||||
}
|
||||
localStorage.setItem('layout-setting', JSON.stringify(layoutSetting))
|
||||
setTimeout(proxy.$modal.closeLoading(), 100)
|
||||
@ -302,13 +307,6 @@ defineExpose({
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.custom-img {
|
||||
width: 48px;
|
||||
height: 38px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 2px #898484;
|
||||
}
|
||||
|
||||
.setting-drawer-block-checbox-selectIcon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@ -5,11 +5,15 @@
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.path)">
|
||||
<svg-icon :name="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
|
||||
|
||||
<template v-if="onlyOneChild.meta.titleKey" #title>
|
||||
{{ $t(onlyOneChild.meta.titleKey) }}
|
||||
</template>
|
||||
<template v-else-if="onlyOneChild.meta.title" #title>
|
||||
{{ onlyOneChild.meta.title }}
|
||||
<span v-if="props.isCollapse && !onlyOneChild.meta.icon">{{ hasTitle2(onlyOneChild.meta.title) }}</span>
|
||||
<template #title>
|
||||
<span v-if="onlyOneChild.meta.titleKey">{{ $t(onlyOneChild.meta.titleKey) }}</span>
|
||||
<span v-else-if="onlyOneChild.meta.title">{{ onlyOneChild.meta.title }}</span>
|
||||
<svg-icon
|
||||
name="new"
|
||||
color="#fff"
|
||||
style="width: 50px; height: 25px"
|
||||
v-if="onlyOneChild.meta.title && onlyOneChild.meta.isNew == 1 && defaultSettings.menuShowNew" />
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</app-link>
|
||||
@ -20,6 +24,11 @@
|
||||
<svg-icon :name="item.meta && item.meta.icon" />
|
||||
<span v-if="item.meta && item.meta.titleKey">{{ $t(item.meta.titleKey) }}</span>
|
||||
<span v-else-if="item.meta && item.meta.title">{{ item.meta.title }}</span>
|
||||
<svg-icon
|
||||
name="new"
|
||||
color="#fff"
|
||||
style="width: 50px; height: 25px"
|
||||
v-if="item.meta.title && item.meta.isNew == 1 && defaultSettings.menuShowNew" />
|
||||
</template>
|
||||
|
||||
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" />
|
||||
@ -31,7 +40,7 @@
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import AppLink from './Link'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
|
||||
import defaultSettings from '@/settings'
|
||||
const props = defineProps({
|
||||
// route object
|
||||
item: {
|
||||
@ -45,6 +54,10 @@ const props = defineProps({
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isCollapse: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
@ -102,4 +115,12 @@ function hasTitle(title) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function hasTitle2(title) {
|
||||
if (title.length >= 1) {
|
||||
return title.charAt(0) + '...'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -11,7 +11,12 @@
|
||||
:collapse-transition="false"
|
||||
background-color="transparent"
|
||||
mode="vertical">
|
||||
<sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
|
||||
<sidebar-item
|
||||
v-for="(route, index) in sidebarRouters"
|
||||
:key="route.path + index"
|
||||
:item="route"
|
||||
:base-path="route.path"
|
||||
:isCollapse="isCollapse" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
|
||||
@ -38,7 +38,6 @@
|
||||
import ScrollPane from './ScrollPane'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
import useTagsViewStore from '@/store/modules/tagsView'
|
||||
// import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import { isHttp } from '@/utils/validate'
|
||||
const visible = ref(false)
|
||||
@ -54,7 +53,6 @@ const router = useRouter()
|
||||
|
||||
const visitedViews = computed(() => useTagsViewStore().visitedViews)
|
||||
const routes = computed(() => usePermissionStore().routes)
|
||||
// const theme = computed(() => useSettingsStore().theme);
|
||||
|
||||
watch(route, () => {
|
||||
addTags()
|
||||
@ -77,10 +75,6 @@ function isActive(r) {
|
||||
}
|
||||
function activeStyle(tag) {
|
||||
if (!isActive(tag)) return {}
|
||||
// return {
|
||||
// 'background-color': theme.value,
|
||||
// 'border-color': theme.value,
|
||||
// }
|
||||
}
|
||||
function isAffix(tag) {
|
||||
return tag.meta && tag.meta.affix
|
||||
|
||||
@ -20,11 +20,14 @@
|
||||
</el-header>
|
||||
<el-main class="app-main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<transition name="fade-transform" mode="out-in" v-if="!dev">
|
||||
<keep-alive :include="cachedViews">
|
||||
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
<keep-alive :include="cachedViews" v-else>
|
||||
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
<iframe-toggle />
|
||||
</el-main>
|
||||
@ -46,6 +49,7 @@ import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useTagsViewStore from '@/store/modules/tagsView'
|
||||
|
||||
const dev = import.meta.env.DEV
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme)
|
||||
const sidebar = computed(() => useAppStore().sidebar)
|
||||
|
||||
@ -8,10 +8,11 @@ import '@/assets/styles/index.scss' // global css
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
import directive from './directive' // directive
|
||||
import vxetb from './vxe-tb'
|
||||
// 注册指令
|
||||
import plugins from './plugins' // plugins
|
||||
import { downFile } from '@/utils/request'
|
||||
import signalR from '@/utils/signalR'
|
||||
import signalR from '@/signalr/signalr'
|
||||
import vueI18n from './i18n/index'
|
||||
import pinia from '@/store/index'
|
||||
|
||||
@ -41,6 +42,8 @@ import ImagePreview from '@/components/ImagePreview'
|
||||
import DictTag from '@/components/DictTag'
|
||||
// el-date-picker 快捷选项
|
||||
import dateOptions from '@/utils/dateOptions'
|
||||
// Dialog组件
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
const app = createApp(App)
|
||||
signalR.init(import.meta.env.VITE_APP_SOCKET_API)
|
||||
@ -65,7 +68,8 @@ app.component('UploadImage', ImageUpload)
|
||||
app.component('ImagePreview', ImagePreview)
|
||||
app.component('RightToolbar', RightToolbar)
|
||||
app.component('svg-icon', SvgIcon)
|
||||
app.component('ZrDialog', Dialog)
|
||||
|
||||
directive(app)
|
||||
|
||||
vxetb(app)
|
||||
app.use(pinia).use(router).use(plugins).use(ElementPlus, {}).use(elementIcons).use(vueI18n).mount('#app')
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import router from './router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
// import { ElMessage } from 'element-plus'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import { getToken } from '@/utils/auth'
|
||||
@ -7,9 +7,9 @@ import { isHttp } from '@/utils/validate'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
NProgress.configure({ showSpinner: false });
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/socialLogin'];
|
||||
const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/socialLogin', '/error']
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
@ -22,19 +22,26 @@ router.beforeEach((to, from, next) => {
|
||||
} else {
|
||||
if (useUserStore().roles.length === 0) {
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
useUserStore().getInfo().then(() => {
|
||||
usePermissionStore().generateRoutes().then(accessRoutes => {
|
||||
useUserStore()
|
||||
.getInfo()
|
||||
.then(() => {
|
||||
usePermissionStore()
|
||||
.generateRoutes()
|
||||
.then((accessRoutes) => {
|
||||
// 根据roles权限生成可访问的路由表
|
||||
accessRoutes.forEach(route => {
|
||||
accessRoutes.forEach((route) => {
|
||||
if (!isHttp(route.path)) {
|
||||
router.addRoute(route) // 动态添加可访问路由表
|
||||
}
|
||||
})
|
||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
|
||||
})
|
||||
}).catch(err => {
|
||||
useUserStore().logOut().then(() => {
|
||||
ElMessage.error(err != undefined ? err : '登录失败')
|
||||
})
|
||||
.catch((err) => {
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
// ElMessage.error(err != undefined ? err : '登录失败')
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
@ -48,7 +55,8 @@ router.beforeEach((to, from, next) => {
|
||||
// 在免登录白名单,直接进入
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
|
||||
console.log('to login')
|
||||
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
|
||||
NProgress.done()
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,11 @@ export const constantRoutes = [
|
||||
component: () => import('@/views/error/401'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: () => import('@/views/error/Error'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
@ -68,7 +73,7 @@ export const constantRoutes = [
|
||||
path: '/index',
|
||||
component: () => import('@/views/index'),
|
||||
name: 'Index',
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true, titleKey: 'menu.home' }
|
||||
meta: { title: '首页', icon: 'index', affix: true, titleKey: 'menu.home' }
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -85,22 +90,6 @@ export const constantRoutes = [
|
||||
meta: { title: '个人中心', icon: 'user', titleKey: 'menu.personalCenter' }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 不用可删掉
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
hidden: false,
|
||||
meta: { title: '组件示例', icon: 'icon', noCache: 'fasle' },
|
||||
children: [
|
||||
{
|
||||
path: 'icon',
|
||||
component: () => import('@/views/components/icons/index'),
|
||||
//component: () => import('@/views/business/GenDemo'),
|
||||
name: 'icon',
|
||||
meta: { title: '图标icon', icon: 'icon1', noCache: 'fasle', titleKey: 'menu.icon' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ export default {
|
||||
/**
|
||||
* 框架版本号
|
||||
*/
|
||||
version: '3.8.2',
|
||||
version: '20230920',
|
||||
/**
|
||||
* 网页标题
|
||||
*/
|
||||
@ -14,7 +14,7 @@ export default {
|
||||
/**
|
||||
* 框架主题颜色值
|
||||
*/
|
||||
theme: '#FF8C00',
|
||||
theme: '#409EFF',
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
*/
|
||||
@ -71,7 +71,7 @@ export default {
|
||||
/**
|
||||
* 是否显示其他登录
|
||||
*/
|
||||
showOtherLogin: false,
|
||||
showOtherLogin: true,
|
||||
/**
|
||||
* 默认大小
|
||||
*/
|
||||
@ -79,5 +79,21 @@ export default {
|
||||
/**
|
||||
* 默认语言
|
||||
*/
|
||||
defaultLang: 'zh-cn'
|
||||
defaultLang: 'zh-cn',
|
||||
/**
|
||||
* 左侧菜单是否显示New标记
|
||||
*/
|
||||
menuShowNew: false,
|
||||
/**
|
||||
* 是否显示QR登录
|
||||
*/
|
||||
showQrLogin: true,
|
||||
/**
|
||||
* 是否显示手机号登录
|
||||
*/
|
||||
showPhoneLogin: true,
|
||||
/**
|
||||
* 标签页持久化
|
||||
*/
|
||||
tagsViewPersist: false
|
||||
}
|
||||
|
||||
95
src/signalr/analysis.js
Normal file
@ -0,0 +1,95 @@
|
||||
import { ElNotification, ElMessageBox } from 'element-plus'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { webNotify } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
onMessage(connection) {
|
||||
connection.on(MsgType.M001, (data) => {
|
||||
useSocketStore().setOnlineUsers(data)
|
||||
})
|
||||
|
||||
connection.on(MsgType.M002, (data) => {
|
||||
// useUserStore().saveConnId(data)
|
||||
})
|
||||
// 接收后台手动推送消息
|
||||
connection.on('receiveNotice', (title, data) => {
|
||||
ElNotification({
|
||||
type: 'info',
|
||||
title: title,
|
||||
message: data,
|
||||
dangerouslyUseHTMLString: true,
|
||||
duration: 0
|
||||
})
|
||||
webNotify({ title: title, body: data })
|
||||
})
|
||||
// 接收系统通知/公告
|
||||
connection.on('moreNotice', (data) => {
|
||||
if (data.code == 200) {
|
||||
useSocketStore().setNoticeList(data.data)
|
||||
}
|
||||
})
|
||||
|
||||
// 接收在线用户
|
||||
// connection.on('onlineUser', (data) => {
|
||||
// useSocketStore().setOnlineUsers(data)
|
||||
// })
|
||||
|
||||
// 接收强退通知
|
||||
connection.on('forceUser', (data) => {
|
||||
// connection.stop().then(() => {
|
||||
// console.log('Connection stoped')
|
||||
// })
|
||||
// ElMessageBox.alert(`你的账号已被强退,原因:${data.reason || '无'}`, '提示', {
|
||||
// confirmButtonText: '确定',
|
||||
// callback: () => {
|
||||
|
||||
// }
|
||||
// })
|
||||
useSocketStore().setGlobalError({ code: 0, msg: `你的账号已被强退,原因:${data.reason || '无'}` })
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'error'
|
||||
})
|
||||
})
|
||||
// 接收聊天数据
|
||||
connection.on('receiveChat', (data) => {
|
||||
const { fromUser, message } = data
|
||||
|
||||
useSocketStore().setChat(data)
|
||||
|
||||
if (data.userid != useUserStore().userId) {
|
||||
ElNotification({
|
||||
title: fromUser.nickName,
|
||||
message: message,
|
||||
type: 'success',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
webNotify({ title: fromUser.nickName, body: message })
|
||||
})
|
||||
|
||||
connection.on('onlineInfo', (data) => {
|
||||
useSocketStore().getOnlineInfo(data)
|
||||
})
|
||||
|
||||
connection.on(MsgType.LogOut, () => {
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
ElMessageBox.alert(`你的账号已在其他设备登录,如果不是你的操作请尽快修改密码`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
callback: () => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
const MsgType = {
|
||||
M001: 'onlineNum',
|
||||
M002: 'connId',
|
||||
LogOut: 'logOut'
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
// 官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
|
||||
import * as signalR from '@microsoft/signalr'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { ElNotification, ElMessage } from 'element-plus'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
import { webNotify } from './index'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import cache from '@/plugins/cache'
|
||||
import analysis from '@/signalr/analysis'
|
||||
|
||||
export default {
|
||||
// signalR对象
|
||||
SR: {},
|
||||
// 失败连接重试次数
|
||||
failNum: 4,
|
||||
baseUrl: '',
|
||||
init(url) {
|
||||
var socketUrl = window.location.origin + url
|
||||
var socketUrl = window.location.origin + url + '?clientId=' + cache.local.get('clientId')
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(socketUrl, { accessTokenFactory: () => getToken() })
|
||||
.withAutomaticReconnect() //自动重新连接
|
||||
@ -19,20 +19,20 @@ export default {
|
||||
.build()
|
||||
this.SR = connection
|
||||
// 断线重连
|
||||
connection.onclose(async () => {
|
||||
console.log('断开连接了')
|
||||
connection.onclose(async (error) => {
|
||||
console.error('断开连接了' + error)
|
||||
console.assert(connection.state === signalR.HubConnectionState.Disconnected)
|
||||
// 建议用户重新刷新浏览器
|
||||
await this.start()
|
||||
})
|
||||
|
||||
connection.onreconnected(() => {
|
||||
connection.onreconnected((connectionId) => {
|
||||
ElMessage({
|
||||
message: '与服务器通讯已连接成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
console.log('断线重新连接成功')
|
||||
console.log('断线重新连接成功' + connectionId)
|
||||
})
|
||||
|
||||
connection.onreconnecting(async () => {
|
||||
@ -40,8 +40,7 @@ export default {
|
||||
|
||||
await this.start()
|
||||
})
|
||||
|
||||
this.receiveMsg(connection)
|
||||
analysis.onMessage(connection)
|
||||
// 启动
|
||||
// this.start();
|
||||
},
|
||||
@ -70,50 +69,5 @@ export default {
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 接收消息处理
|
||||
receiveMsg(connection) {
|
||||
connection.on('onlineNum', (data) => {
|
||||
useSocketStore().setOnlineUserNum(data)
|
||||
})
|
||||
// 接收欢迎语
|
||||
connection.on('welcome', (data) => {
|
||||
ElNotification.info(data)
|
||||
})
|
||||
// 接收后台手动推送消息
|
||||
connection.on('receiveNotice', (title, data) => {
|
||||
ElNotification({
|
||||
type: 'info',
|
||||
title: title,
|
||||
message: data,
|
||||
dangerouslyUseHTMLString: true,
|
||||
duration: 0
|
||||
})
|
||||
webNotify({ title: title, body: data })
|
||||
})
|
||||
// 接收系统通知/公告
|
||||
connection.on('moreNotice', (data) => {
|
||||
if (data.code == 200) {
|
||||
useSocketStore().setNoticeList(data.data)
|
||||
}
|
||||
})
|
||||
|
||||
// 接收在线用户
|
||||
// connection.on('onlineUser', (data) => {
|
||||
// useSocketStore().setOnlineUsers(data)
|
||||
// })
|
||||
|
||||
// 接收聊天数据
|
||||
connection.on('receiveChat', (data) => {
|
||||
const title = `来自${data.userName}的消息通知`
|
||||
ElNotification({
|
||||
title: title,
|
||||
message: data.message,
|
||||
type: 'success',
|
||||
duration: 0
|
||||
})
|
||||
|
||||
webNotify({ title: title, body: data.message })
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
const store = createPinia()
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const store = createPinia()
|
||||
store.use(piniaPluginPersistedstate)
|
||||
export default store
|
||||
@ -1,8 +1,20 @@
|
||||
import defaultSettings from '@/settings'
|
||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||
|
||||
const { sideTheme, theme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, showFooter, showWatermark, watermarkText } =
|
||||
defaultSettings
|
||||
const {
|
||||
sideTheme,
|
||||
theme,
|
||||
showSettings,
|
||||
topNav,
|
||||
tagsView,
|
||||
fixedHeader,
|
||||
sidebarLogo,
|
||||
dynamicTitle,
|
||||
showFooter,
|
||||
showWatermark,
|
||||
watermarkText,
|
||||
tagsViewPersist
|
||||
} = defaultSettings
|
||||
|
||||
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
|
||||
const useSettingsStore = defineStore('settings', {
|
||||
@ -18,13 +30,13 @@ const useSettingsStore = defineStore('settings', {
|
||||
dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
|
||||
showFooter: storageSetting.showFooter === undefined ? showFooter : storageSetting.showFooter,
|
||||
showWatermark: storageSetting.showWatermark === undefined ? showWatermark : storageSetting.showWatermark,
|
||||
watermarkText: storageSetting.watermarkText === undefined ? watermarkText : storageSetting.watermarkText
|
||||
watermarkText: storageSetting.watermarkText === undefined ? watermarkText : storageSetting.watermarkText,
|
||||
tagsViewPersist: storageSetting.tagsViewPersist === undefined ? tagsViewPersist : storageSetting.tagsViewPersist
|
||||
}),
|
||||
actions: {
|
||||
// 修改布局设置
|
||||
changeSetting(data) {
|
||||
const { key, value } = data
|
||||
// if (this.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.$state, key)) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
@ -1,10 +1,41 @@
|
||||
import useUserStore from './user'
|
||||
import signalR from '@/signalr/signalr'
|
||||
const useSocketStore = defineStore('socket', {
|
||||
persist: {
|
||||
paths: ['chatMessage', 'chatList', 'sessionList', 'newChat', 'noticeIdArr', 'newNotice', 'globalErrorMsg'] //存储指定key
|
||||
},
|
||||
state: () => ({
|
||||
onlineNum: 0,
|
||||
onlineUsers: [],
|
||||
noticeList: [],
|
||||
noticeDot: false
|
||||
//在线用户信息
|
||||
onlineInfo: {},
|
||||
// 聊天数据
|
||||
chatList: {},
|
||||
leaveUser: {},
|
||||
sessionList: {},
|
||||
newChat: 0,
|
||||
newNotice: 0,
|
||||
noticeIdArr: [],
|
||||
// 全局错误提醒
|
||||
globalErrorMsg: {}
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
* 返回当前会话的消息
|
||||
* @param {*} state
|
||||
* @returns
|
||||
*/
|
||||
getMessageList(state) {
|
||||
return (conversationId) => state.chatList[conversationId]
|
||||
},
|
||||
getSessionList(state) {
|
||||
return (userid) => state.sessionList[userid] || []
|
||||
},
|
||||
getAllDotNum(state) {
|
||||
return () => state.newChat + state.newNotice
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
//更新在线人数
|
||||
setOnlineUserNum(num) {
|
||||
@ -13,16 +44,80 @@ const useSocketStore = defineStore('socket', {
|
||||
// 更新系统通知
|
||||
setNoticeList(data) {
|
||||
this.noticeList = data
|
||||
this.noticeDot = data.length > 0
|
||||
|
||||
const idArr = []
|
||||
data.forEach((ele) => {
|
||||
idArr.push(ele.noticeId)
|
||||
})
|
||||
|
||||
var diffArr = idArr.filter((v) => !this.noticeIdArr.some((item) => item == v))
|
||||
|
||||
if (diffArr.length > 0) {
|
||||
this.newNotice = diffArr.length
|
||||
this.noticeIdArr = idArr
|
||||
}
|
||||
},
|
||||
setOnlineUsers(data) {
|
||||
const { onlineClients, num, leaveUser } = data
|
||||
this.onlineUsers = onlineClients
|
||||
this.onlineNum = num
|
||||
if (leaveUser != null) {
|
||||
this.leaveUser = leaveUser
|
||||
}
|
||||
},
|
||||
getOnlineInfo(data) {
|
||||
this.onlineInfo = data
|
||||
},
|
||||
setChat(data) {
|
||||
const userStore = useUserStore()
|
||||
var selfUserId = userStore.userId
|
||||
|
||||
var sessionId = data.toUserId
|
||||
if (data.userId != selfUserId) {
|
||||
sessionId = data.userId
|
||||
}
|
||||
var obj = this.chatList[sessionId]
|
||||
if (obj && obj.length >= 50) {
|
||||
this.chatList[sessionId].shift()
|
||||
}
|
||||
if (obj == null || obj == undefined) {
|
||||
this.chatList[sessionId] = []
|
||||
}
|
||||
// 判断消息是否是自己发的
|
||||
data.self = data.userId == selfUserId
|
||||
this.chatList[sessionId].push(data)
|
||||
if (selfUserId == data.userId) return
|
||||
this.newChat++
|
||||
|
||||
if (this.sessionList[selfUserId] == undefined) {
|
||||
this.sessionList[selfUserId] = []
|
||||
}
|
||||
var index = this.getSessionList(selfUserId).findIndex((x) => x.userId == data.userId)
|
||||
this.getSessionList(selfUserId).splice(index, 1)
|
||||
this.getSessionList(selfUserId).push(data)
|
||||
},
|
||||
// setOnlineUsers(data) {
|
||||
// const { onlineNum, users } = data
|
||||
// this.onlineUsers = users
|
||||
// this.onlineNum = onlineNum
|
||||
// },
|
||||
sendChat(data) {
|
||||
const { proxy } = getCurrentInstance()
|
||||
console.log(data)
|
||||
// console.log(JSON.stringify(data))
|
||||
return new Promise((resolve, reject) => {
|
||||
signalR.SR.invoke('sendMessage', data.toUserId, data.message)
|
||||
.then(() => {
|
||||
resolve(true)
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(false)
|
||||
console.error(err.toString())
|
||||
})
|
||||
})
|
||||
},
|
||||
readAll(type) {
|
||||
if (type == 0) {
|
||||
this.newNotice = 0
|
||||
} else if (type == 1) {
|
||||
this.newChat = 0
|
||||
}
|
||||
},
|
||||
setGlobalError(data) {
|
||||
this.globalErrorMsg = data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
|
||||
|
||||
const useTagsViewStore = defineStore('tagsView', {
|
||||
persist: {
|
||||
paths: [storageSetting.tagsViewPersist ? 'visitedViews' : ''] //存储指定key
|
||||
},
|
||||
state: () => ({
|
||||
visitedViews: [],
|
||||
cachedViews: [],
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { login, logout, getInfo, oauthCallback } from '@/api/system/login'
|
||||
import { login, logout, getInfo, oauthCallback, phoneLogin } from '@/api/system/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import useTagsViewStore from './tagsView'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
import md5 from 'js-md5'
|
||||
import cache from '@/plugins/cache'
|
||||
import md5 from 'crypto-js/md5'
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
userInfo: '',
|
||||
@ -12,7 +15,8 @@ const useUserStore = defineStore('user', {
|
||||
permissions: [],
|
||||
userId: 0,
|
||||
authSource: '',
|
||||
userName: ''
|
||||
userName: '',
|
||||
clientId: cache.local.get('clientId')
|
||||
}),
|
||||
actions: {
|
||||
setAuthSource(source) {
|
||||
@ -21,11 +25,13 @@ const useUserStore = defineStore('user', {
|
||||
// 登录
|
||||
login(userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = md5(userInfo.password)
|
||||
const password = md5(userInfo.password).toString()
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
const clientId = this.clientId
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid)
|
||||
login(username, password, code, uuid, clientId)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
setToken(res.data)
|
||||
@ -69,6 +75,34 @@ const useUserStore = defineStore('user', {
|
||||
})
|
||||
})
|
||||
},
|
||||
// 扫码登录
|
||||
scanLogin(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setToken(data.token)
|
||||
this.token = data.token
|
||||
|
||||
resolve(data.token) //then处理
|
||||
})
|
||||
},
|
||||
// 手机号登录
|
||||
phoneNumLogin(userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
phoneLogin(userInfo)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
setToken(res.data)
|
||||
this.token = res.data
|
||||
resolve() //then处理
|
||||
} else {
|
||||
console.log('login error ', res)
|
||||
reject(res) //catch处理
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取用户信息
|
||||
getInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -93,7 +127,7 @@ const useUserStore = defineStore('user', {
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
console.warn(error)
|
||||
reject('获取用户信息失败')
|
||||
})
|
||||
})
|
||||
@ -107,6 +141,7 @@ const useUserStore = defineStore('user', {
|
||||
this.roles = []
|
||||
this.permissions = []
|
||||
removeToken()
|
||||
useTagsViewStore().visitedViews = []
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -121,6 +156,14 @@ const useUserStore = defineStore('user', {
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
setClientId(clientId) {
|
||||
this.clientId = clientId
|
||||
cache.local.set('clientId', clientId)
|
||||
},
|
||||
refreshToken(token) {
|
||||
setToken(token)
|
||||
this.token = token
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -30,17 +30,7 @@ export function formatTime(time, option) {
|
||||
if (option) {
|
||||
return parseTime(time, option)
|
||||
} else {
|
||||
return (
|
||||
d.getMonth() +
|
||||
1 +
|
||||
'月' +
|
||||
d.getDate() +
|
||||
'日' +
|
||||
d.getHours() +
|
||||
'时' +
|
||||
d.getMinutes() +
|
||||
'分'
|
||||
)
|
||||
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +75,7 @@ export function cleanArray(actual) {
|
||||
export function param(json) {
|
||||
if (!json) return ''
|
||||
return cleanArray(
|
||||
Object.keys(json).map(key => {
|
||||
Object.keys(json).map((key) => {
|
||||
if (json[key] === undefined) return ''
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
|
||||
})
|
||||
@ -103,7 +93,7 @@ export function param2Obj(url) {
|
||||
}
|
||||
const obj = {}
|
||||
const searchArr = search.split('&')
|
||||
searchArr.forEach(v => {
|
||||
searchArr.forEach((v) => {
|
||||
const index = v.indexOf('=')
|
||||
if (index !== -1) {
|
||||
const name = v.substring(0, index)
|
||||
@ -137,7 +127,7 @@ export function objectMerge(target, source) {
|
||||
if (Array.isArray(source)) {
|
||||
return source.slice()
|
||||
}
|
||||
Object.keys(source).forEach(property => {
|
||||
Object.keys(source).forEach((property) => {
|
||||
const sourceProperty = source[property]
|
||||
if (typeof sourceProperty === 'object') {
|
||||
target[property] = objectMerge(target[property], sourceProperty)
|
||||
@ -161,9 +151,7 @@ export function toggleClass(element, className) {
|
||||
if (nameIndex === -1) {
|
||||
classString += '' + className
|
||||
} else {
|
||||
classString =
|
||||
classString.substr(0, nameIndex) +
|
||||
classString.substr(nameIndex + className.length)
|
||||
classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length)
|
||||
}
|
||||
element.className = classString
|
||||
}
|
||||
@ -233,7 +221,7 @@ export function deepClone(source) {
|
||||
throw new Error('error arguments', 'deepClone')
|
||||
}
|
||||
const targetObj = source.constructor === Array ? [] : {}
|
||||
Object.keys(source).forEach(keys => {
|
||||
Object.keys(source).forEach((keys) => {
|
||||
if (source[keys] && typeof source[keys] === 'object') {
|
||||
targetObj[keys] = deepClone(source[keys])
|
||||
} else {
|
||||
@ -297,19 +285,17 @@ export function makeMap(str, expectsLowerCase) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
map[list[i]] = true
|
||||
}
|
||||
return expectsLowerCase ?
|
||||
val => map[val.toLowerCase()] :
|
||||
val => map[val]
|
||||
return expectsLowerCase ? (val) => map[val.toLowerCase()] : (val) => map[val]
|
||||
}
|
||||
|
||||
// 首字母大小
|
||||
export function titleCase(str) {
|
||||
return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
|
||||
return str.replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
|
||||
}
|
||||
|
||||
// 下划转驼峰
|
||||
export function camelCase(str) {
|
||||
return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())
|
||||
return str.replace(/_[a-z]/g, (str1) => str1.substr(-1).toUpperCase())
|
||||
}
|
||||
|
||||
// 是否数字
|
||||
@ -324,11 +310,11 @@ export function isNumberStr(str) {
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function getLightColor(color, level) {
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(color)) return color;
|
||||
let rgb = hexToRgb(color);
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
|
||||
return rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/
|
||||
if (!reg.test(color)) return color
|
||||
let rgb = hexToRgb(color)
|
||||
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
|
||||
return rgbToHex(rgb[0], rgb[1], rgb[2])
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,13 +323,13 @@ export function getLightColor(color, level) {
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function hexToRgb(str) {
|
||||
let hexs = '';
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/;
|
||||
if (!reg.test(str)) return str;
|
||||
str = str.replace('#', '');
|
||||
hexs = str.match(/../g);
|
||||
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
|
||||
return hexs;
|
||||
let hexs = ''
|
||||
let reg = /^\#?[0-9A-Fa-f]{6}$/
|
||||
if (!reg.test(str)) return str
|
||||
str = str.replace('#', '')
|
||||
hexs = str.match(/../g)
|
||||
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16)
|
||||
return hexs
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,12 +340,11 @@ export function hexToRgb(str) {
|
||||
* @returns 返回处理后的颜色值
|
||||
*/
|
||||
export function rgbToHex(r, g, b) {
|
||||
let reg = /^\d{1,3}$/;
|
||||
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return "";
|
||||
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
|
||||
for (let i = 0; i < 3; i++)
|
||||
if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
|
||||
return `#${hexs.join('')}`;
|
||||
let reg = /^\d{1,3}$/
|
||||
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ''
|
||||
let hexs = [r.toString(16), g.toString(16), b.toString(16)]
|
||||
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`
|
||||
return `#${hexs.join('')}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,7 +3,7 @@ import { ElMessageBox, ElMessage, ElLoading } from 'element-plus'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import errorCode from '@/utils/errorCode'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { blobValidate } from '@/utils/ruoyi'
|
||||
import { blobValidate, delEmptyQueryNodes } from '@/utils/ruoyi'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
let downloadLoadingInstance
|
||||
@ -26,7 +26,13 @@ service.interceptors.request.use(
|
||||
//将token放到请求头发送给服务器,将tokenkey放在请求头中
|
||||
config.headers['Authorization'] = 'Bearer ' + getToken()
|
||||
config.headers['userid'] = useUserStore().userId
|
||||
config.headers['userName'] = useUserStore().userName
|
||||
config.headers['userName'] = encodeURIComponent(useUserStore().userName)
|
||||
}
|
||||
const method = config?.method || 'get'
|
||||
const header = config?.headers['Content-Type'] ?? ''
|
||||
|
||||
if ((method.toLowerCase() === 'post' || method.toLowerCase() === 'put') && header != 'multipart/form-data') {
|
||||
config.data = delEmptyQueryNodes(config.data)
|
||||
}
|
||||
return config
|
||||
},
|
||||
@ -49,6 +55,10 @@ service.interceptors.response.use(
|
||||
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
||||
return res
|
||||
}
|
||||
var token = res.headers['x-refresh-token']
|
||||
if (token) {
|
||||
useUserStore().refreshToken(token)
|
||||
}
|
||||
if (code == 401) {
|
||||
ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
|
||||
confirmButtonText: '重新登陆',
|
||||
@ -58,7 +68,10 @@ service.interceptors.response.use(
|
||||
useUserStore()
|
||||
.logOut()
|
||||
.then(() => {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index'
|
||||
var redirectUrl = window.location.pathname
|
||||
if (location.pathname.indexOf('/login') != 0) {
|
||||
location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + 'index?redirect=' + redirectUrl
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -75,24 +88,34 @@ service.interceptors.response.use(
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log('axios err', error)
|
||||
let { message } = error
|
||||
if (message == 'Network Error') {
|
||||
console.error('axios err', error)
|
||||
var duration = 3000
|
||||
let { message, response } = error
|
||||
|
||||
if (response.status == 403) {
|
||||
window.location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + '401'
|
||||
} else if (message == 'Network Error') {
|
||||
message = '后端接口连接异常'
|
||||
} else if (message.includes('timeout')) {
|
||||
message = '系统接口请求超时'
|
||||
} else if (message.includes('Request failed with status code 429')) {
|
||||
} else if (message.includes('code 429')) {
|
||||
message = '请求过于频繁,请稍后再试'
|
||||
} else if (message.includes('Request failed with status code')) {
|
||||
message = '系统接口' + message.substr(message.length - 3) + '异常,请联系管理员'
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
message = 'Oops,后端出错了,你不会连错误日志都不会看吧'
|
||||
duration = 0
|
||||
}
|
||||
}
|
||||
ElMessage({
|
||||
message: message,
|
||||
type: 'error',
|
||||
duration: 3 * 1000,
|
||||
duration: duration,
|
||||
showClose: true,
|
||||
grouping: true
|
||||
})
|
||||
return Promise.reject(error)
|
||||
return Promise.reject()
|
||||
}
|
||||
)
|
||||
|
||||
@ -189,13 +212,14 @@ export async function downFile(url, params, config) {
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
downloadLoadingInstance.close()
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: '下载文件出现错误,请联系管理员!',
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
downloadLoadingInstance.close()
|
||||
})
|
||||
}
|
||||
|
||||
@ -180,6 +180,29 @@ export function handleTree(data, id, parentId, children) {
|
||||
}
|
||||
return tree
|
||||
}
|
||||
/**
|
||||
* 将自定义数据转换成字典
|
||||
* @param {*} data 数据源
|
||||
* @param {*} dictLabel dictLabel
|
||||
* @param {*} dictValue dictValue
|
||||
*/
|
||||
export function toDict(data, dictLabel, dictValue) {
|
||||
let config = {
|
||||
label: dictLabel || 'dictLabel',
|
||||
value: dictValue || 'dictValue'
|
||||
}
|
||||
|
||||
var tree = []
|
||||
|
||||
for (let d of data) {
|
||||
let label = d[config.label]
|
||||
let value = d[config.value]
|
||||
|
||||
tree.push({ dictLabel: label, dictValue: value })
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
@ -293,3 +316,21 @@ export function getWeek(num = 0) {
|
||||
var week = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return '星期' + week[datas]
|
||||
}
|
||||
|
||||
// 移除空字符串,null, undefined
|
||||
export const delEmptyQueryNodes = (obj = {}) => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj
|
||||
}
|
||||
const params = Object.keys(obj)
|
||||
.filter((key) => obj[key] !== null && obj[key] !== undefined)
|
||||
.reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: obj[key]
|
||||
}),
|
||||
{}
|
||||
)
|
||||
// console.log('过滤后参数=', params)
|
||||
return params
|
||||
}
|
||||
|
||||
43
src/views/components/Login/oauthLogin.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="other-login" v-if="defaultSettings.showOtherLogin">
|
||||
<div class="other-tip">{{ $t('login.otherLoginWay') }}</div>
|
||||
|
||||
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
|
||||
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import defaultSettings from '@/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
const { proxy } = getCurrentInstance()
|
||||
function onAuth(type) {
|
||||
userStore.setAuthSource(type)
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
// window.location.href = import.meta.env.VITE_APP_BASE_API + '/auth/Authorization?authSource=' + type
|
||||
proxy.$modal.msg('请看文档怎么接入')
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.other-login {
|
||||
padding: 0px 20px 10px;
|
||||
|
||||
.other-tip {
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
font-size: 13px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.login-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
134
src/views/components/Login/phoneLogin.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form" v-show="loginType == 1">
|
||||
<el-form-item prop="phoneNum">
|
||||
<el-input v-model="loginForm.phoneNum" type="phone" :maxlength="11" auto-complete="off" :placeholder="$t('login.input_phoneNum')">
|
||||
<template #prefix>
|
||||
<svg-icon name="phone" class="input-icon" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" v-if="captchaOnOff != 'off'" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
|
||||
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="$t('login.captcha')" style="width: 63%" @keyup.enter="handleLogin">
|
||||
<template #prefix>
|
||||
<svg-icon name="validCode" class="input-icon" />
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="phoneCode">
|
||||
<el-input v-model="loginForm.phoneCode" type="number" auto-complete="off" :placeholder="$t('login.phoneCode')" @keyup.enter="handleLogin">
|
||||
<template #prefix>
|
||||
<svg-icon name="validCode" class="input-icon" />
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button @click="handleSendCode" v-if="!showCounddown">{{ $t('login.sendPhoneCode') }}</el-button>
|
||||
<el-countdown :value="countdownValue" format="mm:ss" @finish="handleFinish" v-else />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
|
||||
<el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin">
|
||||
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup name="phonelogin">
|
||||
import { getCodeImg, checkMobile } from '@/api/system/login'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const loginForm = ref({
|
||||
code: '',
|
||||
uuid: '',
|
||||
phoneCode: '',
|
||||
phoneNum: ''
|
||||
})
|
||||
|
||||
const loginRules = {
|
||||
phoneNum: [{ required: true, trigger: 'blur', message: '请输入手机号码', pattern: /^1\d{10}$/ }],
|
||||
phoneCode: [{ required: true, trigger: 'blur', message: '请输入短信验证码' }],
|
||||
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
|
||||
}
|
||||
const loginType = ref(1)
|
||||
const codeUrl = ref('')
|
||||
const loading = ref(false)
|
||||
// 验证码开关
|
||||
const captchaOnOff = ref('')
|
||||
const redirect = ref()
|
||||
redirect.value = route.query.redirect
|
||||
|
||||
function getCode() {
|
||||
getCodeImg().then((res) => {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.data.img
|
||||
loginForm.value.uuid = res.data.uuid
|
||||
captchaOnOff.value = res.data.captchaOff
|
||||
})
|
||||
}
|
||||
const showCounddown = ref(false)
|
||||
const countdownValue = ref(0)
|
||||
function handleSendCode() {
|
||||
const updateArr = ['phoneNum', 'code']
|
||||
|
||||
proxy.$refs.loginRef.validateField(updateArr, async (valid) => {
|
||||
// 返回值为空时,验证通过;返回值非空时,验证失败
|
||||
if (!valid) {
|
||||
// proxy.$modal.msg('必填字段均通过校验,允许进入下一步!')
|
||||
return
|
||||
} else {
|
||||
checkMobile(loginForm.value)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
showCounddown.value = true
|
||||
countdownValue.value = Date.now() + 1000 * 60
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
// proxy.$modal.msgError(err.msg)
|
||||
// 重新获取验证码
|
||||
if (captchaOnOff.value) {
|
||||
getCode()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
proxy.$refs.loginRef.validate((valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
|
||||
userStore
|
||||
.phoneNumLogin(loginForm.value)
|
||||
.then(() => {
|
||||
proxy.$modal.msgSuccess(proxy.$t('login.loginSuccess'))
|
||||
router.push({ path: redirect.value || '/' })
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
// proxy.$modal.msgError(error.msg)
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleFinish() {
|
||||
showCounddown.value = false
|
||||
}
|
||||
getCode()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/styles/login.scss';
|
||||
</style>
|
||||
@ -32,6 +32,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="翻译键值" align="center" prop="langKey" />
|
||||
<el-table-column label="字典键值" align="center" prop="dictValue" sortable />
|
||||
<el-table-column label="字典排序" align="center" prop="dictSort" sortable />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
@ -42,27 +43,42 @@
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="130px">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.dictCode > 0">
|
||||
<el-button text size="small" icon="edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">编辑</el-button>
|
||||
<el-button text size="small" icon="delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">删除 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 添加或修改参数配置对话框 -->
|
||||
<el-dialog :title="title" v-model="open" draggable width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="字典类型">
|
||||
<el-input v-model="form.dictType" :disabled="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="数据标签" prop="dictLabel">
|
||||
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="翻译键值" prop="langKey">
|
||||
<el-input v-model="form.langKey" placeholder="请输入翻译键值" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="数据键值" prop="dictValue">
|
||||
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="样式属性" prop="cssClass">
|
||||
<!-- <el-input v-model="form.cssClass" placeholder="请输入样式属性" /> -->
|
||||
<el-select v-model="form.cssClass" allow-create filterable clearable="">
|
||||
<el-option v-for="dict in cssClassOptions" :class="dict.value" :key="dict.value" :label="dict.label" :value="dict.value">
|
||||
<span style="float: left" :class="dict.value">{{ dict.label }}</span>
|
||||
@ -70,23 +86,34 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示排序" prop="dictSort">
|
||||
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="回显样式" prop="listClass">
|
||||
<el-select v-model="form.listClass">
|
||||
<el-option v-for="item in listClassOptions" :key="item.value" :label="item.label + '(' + item.value + ')'" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="显示排序" prop="dictSort">
|
||||
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in statusOptions" :key="dict.dictValue" :label="dict.dictValue">{{ dict.dictLabel }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@ -225,7 +252,8 @@ const state = reactive({
|
||||
rules: {
|
||||
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
||||
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
||||
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
||||
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }],
|
||||
langKey: [{ pattern: /^[A-Za-z].+$/, message: '输入格式不正确,格式:login.ok', trigger: 'blur' }]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<template #content> {{ generateIconCode(item) }} </template>
|
||||
<div class="icon-item">
|
||||
<svg-icon :name="item" style="height: 40px; width: 40px" />
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -17,6 +18,7 @@
|
||||
<template #content> {{ generateElementIconCode(item) }} </template>
|
||||
<div class="icon-item">
|
||||
<el-icon><component :is="item" /></el-icon>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -54,7 +56,7 @@ function generateElementIconCode(symbol) {
|
||||
margin: 20px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
width: 77px;
|
||||
float: left;
|
||||
font-size: 30px;
|
||||
color: #24292e;
|
||||
|
||||
179
src/views/components/msgList.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div style="height: 400px">
|
||||
<div class="message_content">
|
||||
<el-scrollbar height="100%" ref="scrollContainer">
|
||||
<template v-for="item in conversionMsgList">
|
||||
<div class="talk_item talk_primary" v-if="item.self">
|
||||
<div class="head">
|
||||
<el-avatar shape="square"> 我 </el-avatar>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="bubble">{{ item.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="talk_item talk_other" v-else>
|
||||
<div class="head">
|
||||
<el-avatar :src="item.fromUser.avatar" shape="square" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="bubble">{{ item.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="talk_bottom">
|
||||
<div class="talk_area">
|
||||
<textarea class="textarea" placeholder="请输入聊天内容" @keyup.enter="handleSend" v-model="content"></textarea>
|
||||
|
||||
<div class="talk_btn">
|
||||
<el-button type="success" size="default" @click="handleSend">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="msglist">
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
const socketStore = useSocketStore()
|
||||
const { proxy } = getCurrentInstance()
|
||||
const props = defineProps({
|
||||
modelValue: {}
|
||||
})
|
||||
const conversionMsgList = computed(() => {
|
||||
return socketStore.getMessageList(props.modelValue)
|
||||
})
|
||||
const content = ref('')
|
||||
function handleSend() {
|
||||
if (content.value.trim().length <= 0) {
|
||||
proxy.$modal.msgError('请输入聊天内容')
|
||||
return
|
||||
}
|
||||
var obj = {
|
||||
toUserId: props.modelValue,
|
||||
message: content.value
|
||||
}
|
||||
socketStore
|
||||
.sendChat(obj)
|
||||
.then(() => {
|
||||
content.value = ''
|
||||
scrollBottom()
|
||||
})
|
||||
.catch(() => {
|
||||
proxy.$modal.msgError('发送失败')
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 滚动聊天记录
|
||||
* @param {*} type
|
||||
*/
|
||||
function scrollBottom(type) {
|
||||
setTimeout(() => {
|
||||
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef)
|
||||
const height = scrollWrapper.value.scrollHeight
|
||||
|
||||
if (type == 1) {
|
||||
scrollWrapper.value.scrollTop = height
|
||||
} else {
|
||||
scrollWrapper.value.scrollTo({ top: height + 120, behavior: 'smooth' })
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
scrollBottom(1)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.message_content {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.talk_item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.head {
|
||||
display: block;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
img {
|
||||
border-radius: 0.3rem;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin-right: 0.4rem;
|
||||
max-width: 70%;
|
||||
|
||||
.bubble {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 0.4rem;
|
||||
background: var(--el-color-success);
|
||||
color: #fff;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.talk_primary {
|
||||
flex-direction: row-reverse;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.talk_item:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.talk_bottom {
|
||||
border-top: 1px solid #ddd;
|
||||
|
||||
.talk_btn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
padding: 0 10px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.talk_area {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
|
||||
.textarea {
|
||||
padding: 10px 0.2rem;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
resize: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
}
|
||||
.textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
src/views/error/Error.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="errPage-container">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h1 class="text-jumbo text-ginormous">提示!</h1>
|
||||
<h3 class="text-danger">{{ msgObj.msg }}</h3>
|
||||
<!-- <h6>{{ msgObj }}</h6> -->
|
||||
<div class="list-unstyled">
|
||||
<router-link to="/"> 回首页 </router-link>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream." />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import errImage from '@/assets/401_images/401.gif'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
let { proxy } = getCurrentInstance()
|
||||
const socketStore = useSocketStore()
|
||||
|
||||
const msgObj = computed(() => {
|
||||
return socketStore.globalErrorMsg
|
||||
})
|
||||
const errGif = ref(errImage + '?' + +new Date())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.errPage-container {
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
margin: 100px auto;
|
||||
.pan-back-btn {
|
||||
background: #008489;
|
||||
color: #fff;
|
||||
border: none !important;
|
||||
}
|
||||
.pan-gif {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
.pan-img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.text-jumbo {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
color: #484848;
|
||||
}
|
||||
.list-unstyled {
|
||||
font-size: 14px;
|
||||
li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
a {
|
||||
color: #008489;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -39,7 +39,7 @@
|
||||
<h2>ZRAdmin.NET {{ $t('layout.backstageManagement') }}</h2>
|
||||
<p>
|
||||
ZRAdmin.NET借鉴了很多开源项目的优点,让你开发Web管理系统更简单,所以我也把它给开源了(前端
|
||||
<code>vue页面</code>主要参考若依,在此表示感谢.)
|
||||
<code>vue页面</code>主要使用了若依,后端参考Ruoyi SpringBoot版本,在此表示感谢.)
|
||||
</p>
|
||||
<p>{{ $t('layout.content1') }}</p>
|
||||
<p>
|
||||
@ -111,7 +111,34 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :sm="24" :lg="8">
|
||||
<el-col :sm="24" :lg="10">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>
|
||||
移动端体验
|
||||
<span style="color: red">如有需要联系作者</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div class="body">
|
||||
<table style="width: 100%; text-align: center">
|
||||
<tr>
|
||||
<td>微信小程序</td>
|
||||
<td>H5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="@/assets/images/qrcode.jpg" alt="donate" style="width: 160px" />
|
||||
</td>
|
||||
<td>
|
||||
<img src="@/assets/images/qrcodeH5.png" alt="donate" style="width: 160px" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :sm="24" :lg="6">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>{{ $t('layout.contactUs') }}</span>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="home">
|
||||
<!-- 用户信息 -->
|
||||
<el-row :gutter="15">
|
||||
<el-col :md="24" :lg="18" :xl="24" class="mb10">
|
||||
<el-col :md="24" :lg="16" :xl="24" class="mb10">
|
||||
<el-card shadow="hover">
|
||||
<div class="user-item">
|
||||
<div class="user-item-left">
|
||||
@ -12,7 +12,10 @@
|
||||
<div class="user-item-right">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="24" class="right-title mb20 one-text-overflow">
|
||||
{{ userInfo.welcomeMessage }} <strong>{{ userInfo.nickName }}</strong> {{ userInfo.welcomeContent }}
|
||||
<div class="mb10">
|
||||
{{ userInfo.welcomeMessage }} <strong>{{ userInfo.nickName }}</strong>
|
||||
<span>({{ userInfo.welcomeContent }})</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
@ -24,10 +27,13 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :lg="6" class="mb10">
|
||||
<el-col :lg="8" class="mb10">
|
||||
<el-card style="height: 100%">
|
||||
<div class="text-warning mb10">{{ currentTime }} {{ weekName }}</div>
|
||||
<div>上次登录时间:{{ userInfo.loginDate }}</div>
|
||||
<div class="work-wrap">
|
||||
<el-statistic :title="$t('layout.workTime')" :formatter="workTimeFormatter" :value="onlineInfo.todayOnlineTime" />
|
||||
<el-statistic :title="$t('layout.onlineClientNum')" :value="onlineInfo.clientNum" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -41,7 +47,7 @@
|
||||
<el-button text @click="handleAdd()">{{ $t('btn.add') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="info">
|
||||
<div>
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper"> <CommonMenu v-model="showEdit"></CommonMenu></el-scrollbar>
|
||||
</div>
|
||||
</el-card>
|
||||
@ -92,8 +98,13 @@ import PieChart from './dashboard/PieChart'
|
||||
import BarChart from './dashboard/BarChart'
|
||||
// import WordCloudChat from './dashboard/WordCloud.vue'
|
||||
import CommonMenu from './components/CommonMenu'
|
||||
import dayjs from 'dayjs'
|
||||
// 时间插件
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
dayjs.extend(duration)
|
||||
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
import { getWeek } from '@/utils/ruoyi'
|
||||
const showEdit = ref(false)
|
||||
const data = {
|
||||
@ -118,6 +129,9 @@ const { proxy } = getCurrentInstance()
|
||||
const userInfo = computed(() => {
|
||||
return useUserStore().userInfo
|
||||
})
|
||||
const onlineInfo = computed(() => {
|
||||
return useSocketStore().onlineInfo
|
||||
})
|
||||
const currentTime = computed(() => {
|
||||
return proxy.parseTime(new Date(), 'YYYY-MM-DD')
|
||||
})
|
||||
@ -134,6 +148,9 @@ handleSetLineChartData('newVisitis')
|
||||
function handleAdd() {
|
||||
proxy.$modal.msg('请通过搜索添加')
|
||||
}
|
||||
function workTimeFormatter(val) {
|
||||
return dayjs.duration(val * 60, 'second').format('HH时mm分')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -165,6 +182,19 @@ function handleAdd() {
|
||||
height: 200px;
|
||||
// overflow-y: scroll;
|
||||
}
|
||||
|
||||
.work-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
|
||||
.item {
|
||||
text-align: center;
|
||||
|
||||
.name {
|
||||
color: #606666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chart-wrapper {
|
||||
background: var(--base-bg-main);
|
||||
|
||||
@ -1,10 +1,20 @@
|
||||
<template>
|
||||
<starBackground></starBackground>
|
||||
<div class="login-wrap">
|
||||
<div class="login">
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{ defaultSettings.title }}</h3>
|
||||
|
||||
<LangSelect title="多语言设置" class="langSet" />
|
||||
|
||||
<div style="padding: 0 25px 5px 25px">
|
||||
<el-tabs v-model="loginType" @tab-click="handleLoginType">
|
||||
<el-tab-pane :label="$t('login.loginway1')" :name="1"></el-tab-pane>
|
||||
<el-tab-pane :label="$t('login.loginway2')" :name="2" v-if="defaultSettings.showPhoneLogin"></el-tab-pane>
|
||||
<el-tab-pane :label="$t('login.loginway3')" :name="3" v-if="defaultSettings.showQrLogin"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form" v-show="loginType == 1">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="loginForm.username" type="text" auto-complete="off" :placeholder="$t('login.account')">
|
||||
<template #prefix>
|
||||
@ -26,33 +36,36 @@
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<el-checkbox v-model="loginForm.rememberMe">{{ $t('login.rememberMe') }}</el-checkbox>
|
||||
<span style="font-size: 12px">
|
||||
<span @click="handleForgetPwd()" class="forget-pwd">忘记密码</span>
|
||||
<router-link class="link-type" :to="'/register'">{{ $t('login.register') }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-form-item style="width: 100%">
|
||||
<el-button :loading="loading" size="default" type="primary" style="width: 100%" @click.prevent="handleLogin">
|
||||
<el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
|
||||
<el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin">
|
||||
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="other-login" v-if="defaultSettings.showOtherLogin">
|
||||
<el-divider>{{ $t('login.otherLoginWay') }}</el-divider>
|
||||
|
||||
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
|
||||
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<el-checkbox v-model="loginForm.rememberMe">{{ $t('login.rememberMe') }}</el-checkbox>
|
||||
<span style="font-size: 12px">
|
||||
<router-link class="link-type" :to="'/register'">{{ $t('login.register') }}</router-link>
|
||||
<span @click="handleForgetPwd()" class="forget-pwd">{{ $t('login.forgotPwd') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-form>
|
||||
<div class="qr-wrap login-form" v-show="loginType == 3">
|
||||
<div class="login-scan-container">
|
||||
<div ref="imgContainerRef" id="imgContainer" class="qrCode"></div>
|
||||
<div class="mt10 text-muted">{{ $t('login.tip_scan_code') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<phoneLogin v-show="loginType == 2"></phoneLogin>
|
||||
<oauthLogin v-show="defaultSettings.showOtherLogin"></oauthLogin>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<div v-html="defaultSettings.copyright"></div>
|
||||
</div>
|
||||
@ -67,6 +80,13 @@ import defaultSettings from '@/settings'
|
||||
import starBackground from '@/views/components/starBackground.vue'
|
||||
import LangSelect from '@/components/LangSelect/index.vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import QRCode from 'qrcodejs2-fixes'
|
||||
import { verifyScan, generateQrcode } from '@/api/system/login'
|
||||
import oauthLogin from './components/Login/oauthLogin.vue'
|
||||
import phoneLogin from './components/Login/phoneLogin.vue'
|
||||
|
||||
var visitorId = ''
|
||||
const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3').then((FingerprintJS) => FingerprintJS.load())
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
@ -86,7 +106,7 @@ const loginRules = {
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
|
||||
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
|
||||
}
|
||||
|
||||
const loginType = ref(1)
|
||||
const codeUrl = ref('')
|
||||
const loading = ref(false)
|
||||
// 验证码开关
|
||||
@ -95,7 +115,21 @@ const captchaOnOff = ref('')
|
||||
const register = ref(false)
|
||||
const redirect = ref()
|
||||
redirect.value = route.query.redirect
|
||||
|
||||
// Get the visitor identifier when you need it.
|
||||
fpPromise
|
||||
.then((fp) => fp.get())
|
||||
.then((result) => {
|
||||
// This is the visitor identifier:
|
||||
visitorId = result.visitorId
|
||||
userStore.setClientId(visitorId)
|
||||
})
|
||||
watch(
|
||||
route,
|
||||
(newRoute) => {
|
||||
redirect.value = newRoute.query && newRoute.query.redirect
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
function handleLogin() {
|
||||
proxy.$refs.loginRef.validate((valid) => {
|
||||
if (valid) {
|
||||
@ -116,7 +150,14 @@ function handleLogin() {
|
||||
.login(loginForm.value)
|
||||
.then(() => {
|
||||
proxy.$modal.msgSuccess(proxy.$t('login.loginSuccess'))
|
||||
router.push({ path: redirect.value || '/' })
|
||||
const query = route.query
|
||||
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
||||
if (cur !== 'redirect') {
|
||||
acc[cur] = query[cur]
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
router.push({ path: redirect.value || '/', query: otherQueryParams })
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
@ -149,18 +190,80 @@ function getCookie() {
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
}
|
||||
}
|
||||
function onAuth(type) {
|
||||
userStore.setAuthSource(type)
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
window.location.href = import.meta.env.VITE_APP_BASE_API + '/auth/Authorization?authSource=' + type
|
||||
break
|
||||
}
|
||||
}
|
||||
function handleForgetPwd() {
|
||||
proxy.$modal.msg('请联系管理员')
|
||||
}
|
||||
|
||||
const interval = ref(null)
|
||||
function handleShowQrLogin() {
|
||||
nextTick(() => {
|
||||
generateCode()
|
||||
})
|
||||
}
|
||||
// 生成二维码
|
||||
function generateCode() {
|
||||
clearQr()
|
||||
var uuid = getUuid()
|
||||
|
||||
document.getElementById('imgContainer').innerHTML = '正在生成中...'
|
||||
generateQrcode({ uuid, deviceId: visitorId }).then((res) => {
|
||||
const { code, data } = res
|
||||
document.getElementById('imgContainer').innerHTML = ''
|
||||
|
||||
if (code == 200) {
|
||||
new QRCode(document.getElementById('imgContainer'), {
|
||||
// text: 'https://qm.qq.com/cgi-bin/qm/qr?k=kgt4HsckdljU0VM-0kxND6d_igmfuPlL&authKey=r55YUbruiKQ5iwC/folG7KLCmZ++Y4rQVgNlvLbUniUMkbk24Y9+zNuOmOnjAjRc&noverify=0',
|
||||
text: JSON.stringify(data.codeContent),
|
||||
width: 160,
|
||||
height: 160
|
||||
})
|
||||
}
|
||||
})
|
||||
interval.value = setInterval(() => {
|
||||
verifyScan({ uuid: uuid, deviceId: userStore.clientId })
|
||||
.then((res) => {
|
||||
const { code, data } = res
|
||||
if (data.status == -1) {
|
||||
clearQr()
|
||||
document.getElementById('imgContainer').innerHTML = '二维码已过期'
|
||||
} else if (data.status == 2) {
|
||||
userStore
|
||||
.scanLogin(data)
|
||||
.then(() => {
|
||||
proxy.$modal.msgSuccess(proxy.$t('login.loginSuccess'))
|
||||
router.push({ path: redirect.value || '/' })
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
proxy.$modal.msgError(error.msg)
|
||||
})
|
||||
clearQr()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
clearQr()
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
function clearQr() {
|
||||
clearInterval(interval.value)
|
||||
interval.value = null
|
||||
}
|
||||
function getUuid() {
|
||||
var temp_url = URL.createObjectURL(new Blob())
|
||||
var uuid = temp_url.toString().replace('-', '') // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3
|
||||
URL.revokeObjectURL(temp_url)
|
||||
return uuid.substr(uuid.lastIndexOf('/') + 1)
|
||||
}
|
||||
function handleLoginType(t) {
|
||||
const val = t.paneName
|
||||
|
||||
if (val == 3) {
|
||||
handleShowQrLogin()
|
||||
} else {
|
||||
clearQr()
|
||||
}
|
||||
}
|
||||
getCode()
|
||||
getCookie()
|
||||
</script>
|
||||
@ -169,16 +272,14 @@ getCookie()
|
||||
@import '@/assets/styles/login.scss';
|
||||
.forget-pwd {
|
||||
color: #ccc;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
border-left: 1px solid;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.login-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.other-login {
|
||||
padding: 0px 10px 5px;
|
||||
.qrCode {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
line-height: 160px;
|
||||
}
|
||||
</style>
|
||||
|
||||
204
src/views/monitor/SmsLog.vue
Normal file
@ -0,0 +1,204 @@
|
||||
<!--
|
||||
* @Descripttion: (短信验证码记录/smsCode_log)
|
||||
* @Author: (zz)
|
||||
* @Date: (2023-11-19)
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
|
||||
<el-form-item label="用户id" prop="userid">
|
||||
<el-input v-model.number="queryParams.userid" placeholder="请输入用户id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phoneNum">
|
||||
<el-input v-model.number="queryParams.phoneNum" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发送时间">
|
||||
<el-date-picker
|
||||
v-model="dateRangeAddTime"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="defaultTime"
|
||||
:shortcuts="dateOptions">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送类型" prop="sendType">
|
||||
<el-select clearable v-model="queryParams.sendType" placeholder="请选择发送类型">
|
||||
<el-option v-for="item in options.sendTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
|
||||
<span class="fl">{{ item.dictLabel }}</span>
|
||||
<span class="fr" style="color: var(--el-text-color-secondary)">{{ item.dictValue }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 工具区域 -->
|
||||
<el-row :gutter="15" class="mb10">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['smscodelog:export']">
|
||||
{{ $t('btn.export') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
border
|
||||
header-cell-class-name="el-table-header-cell"
|
||||
highlight-current-row
|
||||
@sort-change="sortChange">
|
||||
<el-table-column prop="id" label="Id" align="center" width="170" v-if="columns.showColumn('id')" />
|
||||
<el-table-column prop="userid" label="用户id" align="center" v-if="columns.showColumn('userid')" />
|
||||
<el-table-column prop="userIP" label="用户IP" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userIP')" />
|
||||
<el-table-column prop="location" label="位置" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userIP')" />
|
||||
<el-table-column prop="phoneNum" label="手机号" align="center" v-if="columns.showColumn('phoneNum')" />
|
||||
<el-table-column prop="smsCode" label="短信验证码" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('smsCode')" />
|
||||
<el-table-column prop="smsContent" label="短信内容" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('smsContent')" />
|
||||
<el-table-column prop="addTime" label="发送时间" :show-overflow-tooltip="true" v-if="columns.showColumn('addTime')" />
|
||||
<el-table-column prop="sendType" label="发送类型" align="center" v-if="columns.showColumn('sendType')">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="options.sendTypeOptions" :value="scope.row.sendType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="60">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="delete"
|
||||
title="删除"
|
||||
v-hasPermi="['smscodelog:delete']"
|
||||
@click="handleDelete(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="smscodelog">
|
||||
import { listSmscodeLog, delSmscodeLog } from '@/api/system/smscodelog.js'
|
||||
const { proxy } = getCurrentInstance()
|
||||
const ids = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sort: 'Id',
|
||||
sortType: 'desc',
|
||||
userid: undefined,
|
||||
phoneNum: undefined,
|
||||
addTime: undefined,
|
||||
sendType: undefined
|
||||
})
|
||||
const columns = ref([
|
||||
{ visible: true, prop: 'id', label: 'Id' },
|
||||
{ visible: true, prop: 'smsCode', label: '短信验证码' },
|
||||
{ visible: true, prop: 'userid', label: '用户id' },
|
||||
{ visible: true, prop: 'phoneNum', label: '手机号' },
|
||||
{ visible: true, prop: 'smsContent', label: '短信内容' },
|
||||
{ visible: true, prop: 'addTime', label: '发送时间' },
|
||||
{ visible: true, prop: 'userIP', label: '用户IP' },
|
||||
{ visible: true, prop: 'sendType', label: '发送类型' }
|
||||
])
|
||||
const total = ref(0)
|
||||
const dataList = ref([])
|
||||
const queryRef = ref()
|
||||
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
|
||||
|
||||
// 添加时间时间范围
|
||||
const dateRangeAddTime = ref([])
|
||||
|
||||
var dictParams = []
|
||||
|
||||
function getList() {
|
||||
proxy.addDateRange(queryParams, dateRangeAddTime.value, 'AddTime')
|
||||
loading.value = true
|
||||
listSmscodeLog(queryParams).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
dataList.value = data.result
|
||||
total.value = data.totalNum
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询操作
|
||||
function resetQuery() {
|
||||
// 添加时间时间范围
|
||||
dateRangeAddTime.value = []
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
// 自定义排序
|
||||
function sortChange(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/*************** form操作 ***************/
|
||||
|
||||
const state = reactive({
|
||||
single: true,
|
||||
multiple: true,
|
||||
form: {},
|
||||
options: {
|
||||
// 发送类型 选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'}
|
||||
sendTypeOptions: [{ dictLabel: '登录', dictValue: '1' }]
|
||||
}
|
||||
})
|
||||
|
||||
const { form, options } = toRefs(state)
|
||||
|
||||
// 删除按钮操作
|
||||
function handleDelete(row) {
|
||||
const Ids = row.id || ids.value
|
||||
|
||||
proxy
|
||||
.$confirm('是否确认删除参数编号为"' + Ids + '"的数据项?')
|
||||
.then(function () {
|
||||
return delSmscodeLog(Ids)
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
}
|
||||
// 导出按钮操作
|
||||
function handleExport() {
|
||||
proxy
|
||||
.$confirm('是否确认导出短信验证码记录数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await proxy.downFile('/system/SmscodeLog/export', { ...queryParams })
|
||||
})
|
||||
}
|
||||
|
||||
handleQuery()
|
||||
</script>
|
||||
335
src/views/monitor/SqlDiffLog.vue
Normal file
@ -0,0 +1,335 @@
|
||||
<!--
|
||||
* @Descripttion: (数据差异日志/SqlDiffLog)
|
||||
* @Author: (zz)
|
||||
* @Date: (2023-08-17)
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input v-model="queryParams.tableName" placeholder="请输入表名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="差异类型" prop="diffType">
|
||||
<el-select clearable v-model="queryParams.diffType" placeholder="请选择差异类型">
|
||||
<el-option v-for="item in options.diffTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
|
||||
<span class="fl">{{ item.dictLabel }}</span>
|
||||
<span class="fr" style="color: var(--el-text-color-secondary)">{{ item.dictValue }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作用户名" prop="userName">
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入操作用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间">
|
||||
<el-date-picker
|
||||
v-model="dateRangeAddTime"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:default-time="defaultTime"
|
||||
:shortcuts="dateOptions">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 工具区域 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['sqldifflog:export']">
|
||||
{{ $t('btn.export') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
border
|
||||
header-cell-class-name="el-table-header-cell"
|
||||
highlight-current-row
|
||||
@sort-change="sortChange">
|
||||
<el-table-column prop="pId" label="主键" align="center" v-if="columns.showColumn('pId')" width="150" />
|
||||
<el-table-column prop="tableName" label="表名" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('tableName')" />
|
||||
<el-table-column prop="diffType" label="操作类型" align="center" v-if="columns.showColumn('diffType')">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="options.diffTypeOptions" :value="scope.row.diffType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="businessData"
|
||||
label="业务数据内容"
|
||||
align="center"
|
||||
:show-overflow-tooltip="true"
|
||||
v-if="columns.showColumn('businessData')" />
|
||||
<el-table-column prop="sql" label="执行sql语句" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('sql')" />
|
||||
<el-table-column prop="beforeData" label="变更前数据" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('beforeData')" />
|
||||
<el-table-column prop="afterData" label="变更后数据" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('afterData')" />
|
||||
<el-table-column prop="userName" label="操作用户名" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userName')" />
|
||||
<el-table-column prop="addTime" label="操作时间" :show-overflow-tooltip="true" v-if="columns.showColumn('addTime')" />
|
||||
<el-table-column prop="configId" label="数据库配置id" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('configId')" />
|
||||
<el-table-column label="操作" width="130">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="view" title="详情" @click="handlePreview(scope.row)">详细</el-button>
|
||||
<el-button v-hasPermi="['sqldifflog:delete']" type="danger" icon="delete" title="删除" text @click="handleDelete(scope.row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 添加或修改数据差异日志对话框 -->
|
||||
<zr-dialog :title="title" :lock-scroll="false" v-model="open" @close="cancel">
|
||||
<el-form ref="formRef" :model="form" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="主键" prop="pId">
|
||||
<el-input v-model.number="form.pId" placeholder="请输入主键" :disabled="opertype != 1" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="表名" prop="tableName">
|
||||
<el-input v-model="form.tableName" disabled placeholder="请输入表名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="业务数据内容" prop="businessData">
|
||||
<el-input type="textarea" v-model="form.businessData" placeholder="请输入业务数据内容" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="操作类型" prop="diffType">
|
||||
<dict-tag :options="options.diffTypeOptions" :value="form.diffType"></dict-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="执行sql语句" prop="sql">
|
||||
<code class="hljs" v-html="highlightedCode(form.sql)"></code>
|
||||
<!-- <el-input type="textarea" v-model="form.sql" placeholder="请输入执行sql语句" /> -->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="变更前数据" prop="beforeData">
|
||||
<code class="hljs" v-html="highlightedCode(form.beforeData)"></code>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="变更后数据">
|
||||
<code class="hljs" v-html="highlightedCode(form.afterData)"></code>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24">
|
||||
<code-diff :old-string="form.beforeData" :new-string="form.afterData" language="json" output-format="side-by-side" />
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="操作用户名" prop="userName">
|
||||
<el-input v-model="form.userName" placeholder="请输入操作用户名" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="记录时间" prop="addTime">
|
||||
<el-date-picker v-model="form.addTime" disabled type="datetime" :teleported="false" placeholder="选择日期时间"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="数据库配置id" prop="configId">
|
||||
<el-input v-model="form.configId" disabled placeholder="请输入数据库配置id" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer v-if="opertype != 3">
|
||||
<el-button text @click="cancel">{{ $t('btn.cancel') }}</el-button>
|
||||
</template>
|
||||
</zr-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="sqldifflog">
|
||||
import { listSqlDiffLog, delSqlDiffLog } from '@/api/monitor/sqldifflog.js'
|
||||
import { CodeDiff } from 'v-code-diff'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/default.css' // 这里有多个样式,自己可以根据需要切换
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const ids = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sort: 'PId',
|
||||
sortType: 'desc',
|
||||
tableName: undefined,
|
||||
diffType: undefined,
|
||||
userName: undefined,
|
||||
addTime: undefined
|
||||
})
|
||||
const columns = ref([
|
||||
{ visible: true, prop: 'pId', label: '主键' },
|
||||
{ visible: true, prop: 'tableName', label: '表名' },
|
||||
{ visible: true, prop: 'businessData', label: '业务数据内容' },
|
||||
{ visible: true, prop: 'diffType', label: '差异类型' },
|
||||
{ visible: true, prop: 'sql', label: '执行sql语句' },
|
||||
{ visible: true, prop: 'beforeData', label: '变更前数据' },
|
||||
{ visible: true, prop: 'afterData', label: '变更后数据' },
|
||||
{ visible: true, prop: 'userName', label: '操作用户名' },
|
||||
{ visible: false, prop: 'addTime', label: '操作时间' },
|
||||
{ visible: false, prop: 'configId', label: '数据库配置id' }
|
||||
])
|
||||
const total = ref(0)
|
||||
const dataList = ref([])
|
||||
const queryRef = ref()
|
||||
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
|
||||
|
||||
// AddTime时间范围
|
||||
const dateRangeAddTime = ref([])
|
||||
|
||||
var dictParams = []
|
||||
|
||||
function getList() {
|
||||
proxy.addDateRange(queryParams, dateRangeAddTime.value, 'AddTime')
|
||||
loading.value = true
|
||||
listSqlDiffLog(queryParams).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
dataList.value = data.result
|
||||
total.value = data.totalNum
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询操作
|
||||
function resetQuery() {
|
||||
// AddTime时间范围
|
||||
dateRangeAddTime.value = []
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
// 自定义排序
|
||||
function sortChange(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/*************** form操作 ***************/
|
||||
const formRef = ref()
|
||||
const title = ref('')
|
||||
// 操作类型 1、add 2、edit 3、view
|
||||
const opertype = ref(0)
|
||||
const open = ref(false)
|
||||
const state = reactive({
|
||||
single: true,
|
||||
multiple: true,
|
||||
form: {},
|
||||
options: {
|
||||
// 差异类型 选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'}
|
||||
diffTypeOptions: [
|
||||
{ dictLabel: 'insert', dictValue: 'insert' },
|
||||
{ dictLabel: 'update', dictValue: 'update', listClass: 'success' },
|
||||
{ dictLabel: 'delete', dictValue: 'delete', listClass: 'danger' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const { form, options } = toRefs(state)
|
||||
|
||||
// 关闭dialog
|
||||
function cancel() {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function reset() {
|
||||
form.value = {
|
||||
pId: null,
|
||||
tableName: null,
|
||||
businessData: null,
|
||||
diffType: null,
|
||||
sql: null,
|
||||
beforeData: null,
|
||||
afterData: null,
|
||||
userName: null,
|
||||
addTime: null,
|
||||
configId: null
|
||||
}
|
||||
proxy.resetForm('formRef')
|
||||
}
|
||||
|
||||
// 删除按钮操作
|
||||
function handleDelete(row) {
|
||||
const Ids = row.pId || ids.value
|
||||
|
||||
proxy
|
||||
.$confirm('是否确认删除参数编号为"' + Ids + '"的数据项?')
|
||||
.then(function () {
|
||||
return delSqlDiffLog(Ids)
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @param {*} row
|
||||
*/
|
||||
function handlePreview(row) {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '查看'
|
||||
opertype.value = 3
|
||||
form.value = { ...row }
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
function handleExport() {
|
||||
proxy
|
||||
.$confirm('是否确认导出数据差异日志数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await proxy.downFile('/monitor/SqlDiffLog/export', { ...queryParams })
|
||||
})
|
||||
}
|
||||
function highlightedCode(code) {
|
||||
const result = hljs.highlightAuto(code)
|
||||
return result.value || ' '
|
||||
}
|
||||
handleQuery()
|
||||
</script>
|
||||
@ -7,13 +7,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="queryText">
|
||||
<el-input
|
||||
v-model="queryParams.queryText"
|
||||
placeholder="请输入计划任务名称"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
@keyup.enter="handleQuery"
|
||||
@clear="handleQuery" />
|
||||
<el-input v-model="queryParams.queryText" placeholder="请输入计划任务名称" clearable @keyup.enter="handleQuery" @clear="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="viewSwitch">
|
||||
<el-radio-button label="1">表格</el-radio-button>
|
||||
<el-radio-button label="2">卡片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
@ -39,8 +39,7 @@
|
||||
</el-col>
|
||||
<right-toolbar :showSearch="searchToggle" :columns="columns" @queryTable="handleQuery"></right-toolbar>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-table ref="tasks" v-loading="loading" :data="dataTasks" border="" row-key="id" @sort-change="handleSortable">
|
||||
<el-table v-if="viewSwitch == 1" ref="tasks" v-loading="loading" :data="dataTasks" border row-key="id" @sort-change="handleSortable">
|
||||
<!-- <el-table-column type="index" :index="handleIndexCalc" label="#" align="center" /> -->
|
||||
<el-table-column prop="id" label="id" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('id')" />
|
||||
<el-table-column prop="name" label="任务名称" width="100" />
|
||||
@ -56,7 +55,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column sortable prop="isStart" align="center" label="任务状态" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :value="scope.row.isStart" :options="isStartOptions"></dict-tag>
|
||||
<dict-tag :value="scope.row.isStart" :options="options.isStartOptions"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -138,8 +137,51 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-model:total="total" v-model:page="queryParams.PageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<el-row :gutter="20" v-if="viewSwitch == 2">
|
||||
<el-col v-for="item in dataTasks" :lg="8" :span="24">
|
||||
<el-card :body-style="{ padding: '15px 15px 0' }">
|
||||
<el-descriptions :column="1" :title="item.name" size="small" border>
|
||||
<el-descriptions-item label="任务类型">
|
||||
<dict-tag :options="options.taskTypeOptions" :value="item.taskType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="触发器类型" width="90px">
|
||||
<dict-tag :options="options.triggerTypeOptions" :value="item.triggerType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务状态" width="90px">
|
||||
<dict-tag :options="options.isStartOptions" :value="item.isStart"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务分组" width="90px">
|
||||
{{ item.jobGroup }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="程序集" width="90px">
|
||||
{{ item.assemblyName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后运行时间" width="90px">
|
||||
{{ item.lastRunTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行表达式" width="90px">
|
||||
{{ item.cron }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运行次数" width="90px">
|
||||
{{ item.runTimes }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="apiUrl" width="90px">
|
||||
{{ item.apiUrl }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div>
|
||||
<el-button text icon="view" v-hasPermi="['monitor:job:query']" @click="handleDetails(item)">
|
||||
{{ $t('btn.details') }}
|
||||
</el-button>
|
||||
<el-button text icon="view" v-hasPermi="['monitor:job:query']" @click="handleJobLog(item)">
|
||||
{{ $t('btn.log') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<pagination v-model:total="total" v-model:page="queryParams.PageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<el-dialog :title="title" v-model="open" width="600px" draggable append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
@ -168,7 +210,11 @@
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="任务分组" maxlength="200" prop="jobGroup">
|
||||
<el-select v-model="form.jobGroup" placeholder="请选择任务分组">
|
||||
<el-option v-for="dict in jobGroupOptions" :key="dict.dictValue" :label="dict.dictLabel" :value="dict.dictValue"></el-option>
|
||||
<el-option
|
||||
v-for="dict in options.jobGroupOptions"
|
||||
:key="dict.dictValue"
|
||||
:label="dict.dictLabel"
|
||||
:value="dict.dictValue"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -368,15 +414,7 @@ const jobLogList = ref([])
|
||||
const logTitle = ref('')
|
||||
const formRef = ref(null)
|
||||
const queryRef = ref(null)
|
||||
|
||||
// 任务状态字典
|
||||
const isStartOptions = ref([
|
||||
{ dictLabel: '运行中', dictValue: '1', listClass: 'success' },
|
||||
{ dictLabel: '已停止', dictValue: '0', listClass: 'danger' }
|
||||
])
|
||||
// 任务组名字典
|
||||
const jobGroupOptions = ref([])
|
||||
|
||||
const viewSwitch = ref(1)
|
||||
const state = reactive({
|
||||
form: {},
|
||||
// 表单校验
|
||||
@ -404,7 +442,14 @@ const state = reactive({
|
||||
{ dictLabel: '程序集', dictValue: '1' },
|
||||
{ dictLabel: 'api请求', dictValue: '2', listClass: 'danger' },
|
||||
{ dictLabel: 'sql脚本', dictValue: '3', listClass: 'info' }
|
||||
]
|
||||
],
|
||||
// 任务状态字典
|
||||
isStartOptions: [
|
||||
{ dictLabel: '运行中', dictValue: '1', listClass: 'success' },
|
||||
{ dictLabel: '已停止', dictValue: '0', listClass: 'danger' }
|
||||
],
|
||||
// 任务组名字典
|
||||
jobGroupOptions: []
|
||||
}
|
||||
})
|
||||
// 按钮是否可见
|
||||
@ -540,10 +585,17 @@ function submitForm() {
|
||||
})
|
||||
}
|
||||
// 排序操作
|
||||
function handleSortable(val) {
|
||||
queryParams.orderby = val.prop
|
||||
queryParams.sort = val.order
|
||||
getList()
|
||||
function handleSortable(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
// 表单重置
|
||||
function reset() {
|
||||
@ -588,8 +640,9 @@ function handleExport() {
|
||||
|
||||
getList()
|
||||
proxy.getDicts('sys_job_group').then((response) => {
|
||||
jobGroupOptions.value = response.data
|
||||
state.options.jobGroupOptions = response.data
|
||||
})
|
||||
|
||||
watch(
|
||||
() => form.value.triggerType,
|
||||
(val) => {
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
<el-form-item label="登录时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
@ -42,12 +41,17 @@
|
||||
<right-toolbar :showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" :data="list" border @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="访问编号" align="center" prop="infoId" width="80"/>
|
||||
<el-table-column label="访问编号" align="center" prop="infoId" width="80" />
|
||||
<el-table-column label="用户名称" align="center" prop="userName" />
|
||||
<el-table-column label="登录地址" align="center" prop="ipaddr" width="130" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地址" align="center" prop="ipaddr" width="130">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.loginLocation }}</div>
|
||||
<div>{{ row.ipaddr }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="登录地点" align="center" prop="loginLocation" /> -->
|
||||
<el-table-column label="浏览器" align="center" prop="browser" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" />
|
||||
<el-table-column label="操作状态" align="center" prop="status">
|
||||
@ -61,6 +65,11 @@
|
||||
<span>{{ scope.row.loginTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" text plain icon="delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
@ -92,7 +101,7 @@ const queryParams = reactive({
|
||||
pageSize: 10,
|
||||
ipaddr: undefined,
|
||||
userName: undefined,
|
||||
status: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
@ -142,7 +151,7 @@ function handleDelete(row) {
|
||||
.$confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(function () {
|
||||
return delLogininfor(infoIds)
|
||||
@ -158,7 +167,7 @@ function handleClean() {
|
||||
.$confirm('是否确认清空所有登录日志数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(function () {
|
||||
return cleanLogininfor()
|
||||
@ -174,7 +183,7 @@ function handleExport() {
|
||||
.$confirm('是否确认导出所有操作日志数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(function () {
|
||||
return exportLogininfor(queryParams)
|
||||
|
||||
@ -1,62 +1,84 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item>
|
||||
<el-button plain type="primary" @click="onLockAll()" icon="lock" v-hasPermi="['monitor:online:forceLogout']">全部强退</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="viewSwitch">
|
||||
<el-radio-button label="1">表格</el-radio-button>
|
||||
<el-radio-button label="2">卡片</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">刷新</el-button>
|
||||
<!-- <el-button icon="Refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button> -->
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="onlineUsers" ref="tableRef" border highlight-current-row>
|
||||
<!-- <el-table-column prop="connnectionId" label="连接id"></el-table-column> -->
|
||||
<el-table :data="onlineUsers" v-loading="loading" ref="tableRef" border highlight-current-row v-if="viewSwitch == 1">
|
||||
<el-table-column label="No" type="index" width="50" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="用户名" align="center" />
|
||||
<el-table-column prop="userIP" label="用户IP" align="center" />
|
||||
<el-table-column prop="location" label="登录地点" align="center" />
|
||||
<el-table-column prop="browser" label="登录浏览器"></el-table-column>
|
||||
<el-table-column prop="loginTime" label="登录时间">
|
||||
<el-table-column label="登录地点" prop="location" align="center"> </el-table-column>
|
||||
<el-table-column label="登录IP" prop="userIP" align="center"></el-table-column>
|
||||
<el-table-column prop="browser" label="登录浏览器" width="210"></el-table-column>
|
||||
<el-table-column prop="platform" label="登录平台" align="center"></el-table-column>
|
||||
<el-table-column prop="loginTime" label="登录时间" witdh="280px">
|
||||
<template #default="scope">
|
||||
{{ dayjs(scope.row.loginTime).format('MM/DD日HH:mm:ss') }}
|
||||
<div>在线时长:{{ scope.row.onlineTime }}分钟</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="140">
|
||||
<el-table-column label="操作" align="center" width="160">
|
||||
<template #default="scope">
|
||||
<el-button text @click="onChat(scope.row)" icon="bell" v-hasRole="['admin']">通知</el-button>
|
||||
<el-button text @click="onChat(scope.row)" icon="ChatDotRound" v-hasRole="['admin']">私信</el-button>
|
||||
<el-button text @click="onLock(scope.row)" icon="lock" v-hasPermi="['monitor:online:forceLogout']">强退</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination
|
||||
class="mt10"
|
||||
background
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
|
||||
<el-row :gutter="20" v-if="viewSwitch == 2">
|
||||
<el-col v-for="item in onlineUsers" :lg="4" :span="24">
|
||||
<el-card :body-style="{ padding: '15px 15px 0' }">
|
||||
<el-descriptions :column="1" :title="item.name">
|
||||
<el-descriptions-item label="登录平台">{{ item.platform }}</el-descriptions-item>
|
||||
<el-descriptions-item label="登录地点">{{ item.location }}</el-descriptions-item>
|
||||
<el-descriptions-item label="在线时长" :span="2">
|
||||
<el-tag type="success">{{ item.onlineTime }}分钟</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-text truncated>{{ item.browser }}</el-text>
|
||||
<div>
|
||||
<el-button text @click="onChat(item)" size="small" icon="ChatDotRound" title="私信" v-hasRole="['admin']">私信</el-button>
|
||||
<el-button text @click="onLock(item)" size="small" icon="lock" title="强退" v-hasPermi="['monitor:online:forceLogout']">强退</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-empty v-show="total == 0" description="no data" />
|
||||
</el-row>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="onlineuser">
|
||||
import { listOnline } from '@/api/monitor/online'
|
||||
import { listOnline, forceLogout, forceLogoutAll } from '@/api/monitor/online'
|
||||
import dayjs from 'dayjs'
|
||||
import useSocketStore from '@/store/modules/socket'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const queryRef = ref(null)
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// const total = computed(() => {
|
||||
// return useSocketStore().onlineNum
|
||||
// })
|
||||
// const onlineUsers = computed(() => {
|
||||
// return useSocketStore().onlineUsers
|
||||
// })
|
||||
const onlineNum = computed(() => {
|
||||
return useSocketStore().onlineNum
|
||||
})
|
||||
const viewSwitch = ref(1)
|
||||
const loading = ref(false)
|
||||
const onlineUsers = ref([])
|
||||
const total = ref(0)
|
||||
function handleQuery() {
|
||||
@ -64,13 +86,14 @@ function handleQuery() {
|
||||
getList()
|
||||
}
|
||||
function getList() {
|
||||
// proxy.signalr.SR.invoke('GetOnlineUsers', queryParams.pageNum, queryParams.pageSize).catch(function (err) {
|
||||
// console.error(err.toString())
|
||||
// })
|
||||
loading.value = true
|
||||
listOnline(queryParams).then((res) => {
|
||||
if (res.code == 200) {
|
||||
total.value = res.data.totalNum
|
||||
onlineUsers.value = res.data.result
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
}, 200)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -78,18 +101,57 @@ getList()
|
||||
|
||||
function onChat(item) {
|
||||
proxy
|
||||
.$prompt('请输入通知内容', '', {
|
||||
.$prompt('请输入消息内容', '', {
|
||||
confirmButtonText: '发送',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '消息内容不能为空'
|
||||
})
|
||||
.then(({ value }) => {
|
||||
proxy.signalr.SR.invoke('SendMessage', item.connnectionId, item.name, value).catch(function (err) {
|
||||
proxy.signalr.SR.invoke('sendMessage', item.userid, value).catch(function (err) {
|
||||
console.error(err.toString())
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
function resetQuery() {}
|
||||
function onLock(row) {
|
||||
proxy
|
||||
.$prompt('请输入强退原因', '', {
|
||||
confirmButtonText: '发送',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
.then((val) => {
|
||||
forceLogout({ ...row, time: 10, reason: val.value, clientId: row.clientId }).then(() => {
|
||||
proxy.$modal.msgSuccess('强退成功')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量强退
|
||||
function onLockAll() {
|
||||
proxy
|
||||
.$prompt('请输入强退原因', '', {
|
||||
confirmButtonText: '发送',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
.then((val) => {
|
||||
forceLogoutAll({ time: 10, reason: val.value }).then((res) => {
|
||||
proxy.$modal.msgSuccess('强退成功')
|
||||
})
|
||||
})
|
||||
}
|
||||
watch(
|
||||
onlineNum,
|
||||
() => {
|
||||
handleQuery()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.el-col {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="defaultTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:shortcuts="dateOptions"></el-date-picker>
|
||||
</el-form-item>
|
||||
@ -62,13 +63,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="请求方法" align="center" prop="requestMethod" v-if="columns.showColumn('requestMethod')" />
|
||||
<el-table-column label="操作人员" align="center" prop="operName" v-if="columns.showColumn('operName')" />
|
||||
<el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" v-if="columns.showColumn('operIP')" />
|
||||
<el-table-column
|
||||
label="操作地点"
|
||||
align="center"
|
||||
prop="operLocation"
|
||||
:show-overflow-tooltip="true"
|
||||
v-if="columns.showColumn('operLocation')" />
|
||||
|
||||
<el-table-column label="操作地址" align="center" prop="operIp" width="120">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.operLocation }}</div>
|
||||
<div>{{ row.operIp }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作状态" align="center" prop="status" v-if="columns.showColumn('status')">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="options.statusOptions" :value="row.status"></dict-tag>
|
||||
@ -91,11 +92,12 @@
|
||||
<el-table-column prop="method" label="操作方法" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('method')" />
|
||||
<el-table-column prop="operParam" label="请求参数" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('operParam')" />
|
||||
<el-table-column prop="jsonResult" label="返回结果" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('jsonResult')" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text icon="view" @click="handleView(scope.row, scope.index)" v-hasPermi="['monitor:operlog:query']">
|
||||
详细
|
||||
</el-button>
|
||||
<el-button size="small" text icon="delete" @click="handleDelete(scope.row)" v-hasPermi="['monitor:operlog:remove']"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -132,18 +134,20 @@
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24">
|
||||
<el-col :lg="24" v-if="form.operParam">
|
||||
<el-form-item label="请求参数:">
|
||||
<el-input type="textarea" rows="5" v-model="form.operParam"> </el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24">
|
||||
<el-col :lg="24" v-if="form.jsonResult">
|
||||
<el-form-item label="返回结果:">
|
||||
<el-input type="textarea" rows="10" v-model="form.jsonResult"> </el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="24">
|
||||
<el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
|
||||
<el-form-item label="异常信息:" v-if="form.status === 1">
|
||||
<div class="text-danger">{{ form.errorMsg }}</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@ -155,8 +159,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="operlog">
|
||||
import { list as listOperLog, delOperlog, cleanOperlog, exportOperlog } from '@/api/monitor/operlog'
|
||||
|
||||
import { list as listOperLog, delOperlog, cleanOperlog } from '@/api/monitor/operlog'
|
||||
import dayjs from 'dayjs'
|
||||
const { proxy } = getCurrentInstance()
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
@ -177,7 +181,8 @@ const statusOptions = ref([])
|
||||
// 业务类型(0其它 1新增 2修改 3删除)选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'}
|
||||
const businessTypeOptions = ref([])
|
||||
// 日期范围
|
||||
const dateRange = ref([])
|
||||
const dateRange = ref([dayjs().format('YYYY-MM-DD 00:00:00'), dayjs().format('YYYY-MM-DD 23:59:59')])
|
||||
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
|
||||
|
||||
const state = reactive({
|
||||
form: {},
|
||||
@ -205,9 +210,9 @@ const columns = ref([
|
||||
{ visible: true, prop: 'operName', label: '操作人员' },
|
||||
// { visible: true, prop: 'deptName', label: '部门' },
|
||||
// { visible: true, prop: 'operUrl', label: '请求地址' },
|
||||
{ visible: true, prop: 'operIP', label: '请求IP' },
|
||||
// { visible: true, prop: 'operIP', label: '请求IP' },
|
||||
{ visible: true, prop: 'status', label: '操作状态' },
|
||||
{ visible: true, prop: 'operLocation', label: '操作人地址' },
|
||||
// { visible: true, prop: 'operLocation', label: '操作人地址' },
|
||||
{ visible: true, prop: 'operTime', label: '操作时间' },
|
||||
{ visible: false, prop: 'method', label: '操作方法' },
|
||||
{ visible: false, prop: 'operParam', label: '请求参数' },
|
||||
|
||||