mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 07:50:25 +08:00
refactor: 引入dayjs、新增refreshToken无感刷新、团队新增有效期、数据库等问题修复
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"element-plus": "^2.7.2",
|
"element-plus": "^2.7.2",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const config = {
|
|||||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||||
|
|
||||||
// 系统版本
|
// 系统版本
|
||||||
version: 'v1.8.3',
|
version: 'v1.8.4',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import request from './request';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
login: (param: any) => request.post('/auth/accounts/login', param),
|
login: (param: any) => request.post('/auth/accounts/login', param),
|
||||||
|
refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
|
||||||
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
|
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
|
||||||
getPublicKey: () => request.get('/common/public-key'),
|
getPublicKey: () => request.get('/common/public-key'),
|
||||||
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
|
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export enum ResultEnum {
|
|||||||
PARAM_ERROR = 405,
|
PARAM_ERROR = 405,
|
||||||
SERVER_ERROR = 500,
|
SERVER_ERROR = 500,
|
||||||
NO_PERMISSION = 501,
|
NO_PERMISSION = 501,
|
||||||
|
ACCESS_TOKEN_INVALID = 502, // accessToken失效
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseUrl: string = config.baseApiUrl;
|
export const baseUrl: string = config.baseApiUrl;
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
export function dateFormat2(fmt: string, date: Date) {
|
|
||||||
let ret;
|
|
||||||
const opt = {
|
|
||||||
'y+': date.getFullYear().toString(), // 年
|
|
||||||
'M+': (date.getMonth() + 1).toString(), // 月
|
|
||||||
'd+': date.getDate().toString(), // 日
|
|
||||||
'H+': date.getHours().toString(), // 时
|
|
||||||
'm+': date.getMinutes().toString(), // 分
|
|
||||||
's+': date.getSeconds().toString(), // 秒
|
|
||||||
'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
|
|
||||||
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|
||||||
};
|
|
||||||
for (const k in opt) {
|
|
||||||
ret = new RegExp('(' + k + ')').exec(fmt);
|
|
||||||
if (ret) {
|
|
||||||
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dateStrFormat(fmt: string, dateStr: string) {
|
|
||||||
return dateFormat2(fmt, new Date(dateStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dateFormat(dateStr: string) {
|
|
||||||
if (!dateStr) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,18 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
* @param date 日期 字符串 Date 时间戳等
|
||||||
|
* @param format 格式化格式 默认 YYYY-MM-DD HH:mm:ss
|
||||||
|
* @returns 格式化后内容
|
||||||
|
*/
|
||||||
|
export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
|
if (!date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return dayjs(date).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化字节单位
|
* 格式化字节单位
|
||||||
* @param size byte size
|
* @param size byte size
|
||||||
@@ -46,110 +61,6 @@ export function convertToBytes(sizeStr: string) {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 年(Y) 可用1-4个占位符
|
|
||||||
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
|
|
||||||
* 星期(W) 可用1-3个占位符
|
|
||||||
* 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
|
|
||||||
*
|
|
||||||
* let date = new Date()
|
|
||||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
|
|
||||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
|
|
||||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
|
|
||||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
|
|
||||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
|
|
||||||
*/
|
|
||||||
export function formatDate(date: Date, format: string) {
|
|
||||||
let we = date.getDay(); // 星期
|
|
||||||
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
|
|
||||||
const opt: any = {
|
|
||||||
'Y+': date.getFullYear().toString(), // 年
|
|
||||||
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
|
|
||||||
'd+': date.getDate().toString(), // 日
|
|
||||||
'H+': date.getHours().toString(), // 时
|
|
||||||
'M+': date.getMinutes().toString(), // 分
|
|
||||||
'S+': date.getSeconds().toString(), // 秒
|
|
||||||
'q+': qut, // 季度
|
|
||||||
};
|
|
||||||
// 中文数字 (星期)
|
|
||||||
const week: any = {
|
|
||||||
'0': '日',
|
|
||||||
'1': '一',
|
|
||||||
'2': '二',
|
|
||||||
'3': '三',
|
|
||||||
'4': '四',
|
|
||||||
'5': '五',
|
|
||||||
'6': '六',
|
|
||||||
};
|
|
||||||
// 中文数字(季度)
|
|
||||||
const quarter: any = {
|
|
||||||
'1': '一',
|
|
||||||
'2': '二',
|
|
||||||
'3': '三',
|
|
||||||
'4': '四',
|
|
||||||
};
|
|
||||||
if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
|
|
||||||
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
|
|
||||||
for (let k in opt) {
|
|
||||||
let r = new RegExp('(' + k + ')').exec(format);
|
|
||||||
// 若输入的长度不为1,则前面补零
|
|
||||||
if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
|
|
||||||
}
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 10秒: 10 * 1000
|
|
||||||
* 1分: 60 * 1000
|
|
||||||
* 1小时: 60 * 60 * 1000
|
|
||||||
* 24小时:60 * 60 * 24 * 1000
|
|
||||||
* 3天: 60 * 60* 24 * 1000 * 3
|
|
||||||
*
|
|
||||||
* let data = new Date()
|
|
||||||
* formatPast(data) // 刚刚
|
|
||||||
* formatPast(data - 11 * 1000) // 11秒前
|
|
||||||
* formatPast(data - 2 * 60 * 1000) // 2分钟前
|
|
||||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
|
||||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
|
||||||
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
|
|
||||||
* formatPast("2020-06-01") // 2020-06-01
|
|
||||||
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
|
|
||||||
*/
|
|
||||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
|
|
||||||
// 传入格式处理、存储转换值
|
|
||||||
let t: any, s: any;
|
|
||||||
// 获取js 时间戳
|
|
||||||
let time: any = new Date().getTime();
|
|
||||||
// 是否是对象
|
|
||||||
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
|
|
||||||
// 当前时间戳 - 传入时间戳
|
|
||||||
time = Number.parseInt(`${time - t}`);
|
|
||||||
if (time < 10000) {
|
|
||||||
// 10秒内
|
|
||||||
return '刚刚';
|
|
||||||
} else if (time < 60000 && time >= 10000) {
|
|
||||||
// 超过10秒少于1分钟内
|
|
||||||
s = Math.floor(time / 1000);
|
|
||||||
return `${s}秒前`;
|
|
||||||
} else if (time < 3600000 && time >= 60000) {
|
|
||||||
// 超过1分钟少于1小时
|
|
||||||
s = Math.floor(time / 60000);
|
|
||||||
return `${s}分钟前`;
|
|
||||||
} else if (time < 86400000 && time >= 3600000) {
|
|
||||||
// 超过1小时少于24小时
|
|
||||||
s = Math.floor(time / 3600000);
|
|
||||||
return `${s}小时前`;
|
|
||||||
} else if (time < 259200000 && time >= 86400000) {
|
|
||||||
// 超过1天少于3天内
|
|
||||||
s = Math.floor(time / 86400000);
|
|
||||||
return `${s}天前`;
|
|
||||||
} else {
|
|
||||||
// 超过3天
|
|
||||||
let date = typeof param === 'string' || 'object' ? new Date(param) : param;
|
|
||||||
return formatDate(date, format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
|
* 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { randomUuid } from './string';
|
import { randomUuid } from './string';
|
||||||
|
|
||||||
const TokenKey = 'm-token';
|
const TokenKey = 'm-token';
|
||||||
|
const RefreshTokenKey = 'm-refresh-token';
|
||||||
const UserKey = 'm-user';
|
const UserKey = 'm-user';
|
||||||
const TagViewsKey = 'm-tagViews';
|
const TagViewsKey = 'm-tagViews';
|
||||||
const ClientIdKey = 'm-clientId';
|
const ClientIdKey = 'm-clientId';
|
||||||
@@ -15,6 +16,14 @@ export function saveToken(token: string) {
|
|||||||
setLocal(TokenKey, token);
|
setLocal(TokenKey, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRefreshToken(): string {
|
||||||
|
return getLocal(RefreshTokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveRefreshToken(refreshToken: string) {
|
||||||
|
return setLocal(RefreshTokenKey, refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
// 获取登录用户基础信息
|
// 获取登录用户基础信息
|
||||||
export function getUser() {
|
export function getUser() {
|
||||||
return getLocal(UserKey);
|
return getLocal(UserKey);
|
||||||
@@ -39,6 +48,7 @@ export function getThemeConfig() {
|
|||||||
export function clearUser() {
|
export function clearUser() {
|
||||||
removeLocal(TokenKey);
|
removeLocal(TokenKey);
|
||||||
removeLocal(UserKey);
|
removeLocal(UserKey);
|
||||||
|
removeLocal(RefreshTokenKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTagViews() {
|
export function getTagViews() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import EnumValue from '@/common/Enum';
|
import EnumValue from '@/common/Enum';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { getValueByPath } from '@/common/utils/object';
|
import { getValueByPath } from '@/common/utils/object';
|
||||||
import { getTextWidth } from '@/common/utils/string';
|
import { getTextWidth } from '@/common/utils/string';
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ export class TableColumn {
|
|||||||
*/
|
*/
|
||||||
isTime(): TableColumn {
|
isTime(): TableColumn {
|
||||||
this.setFormatFunc((data: any, prop: string) => {
|
this.setFormatFunc((data: any, prop: string) => {
|
||||||
return dateFormat(getValueByPath(data, prop));
|
return formatDate(getValueByPath(data, prop));
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,33 @@
|
|||||||
<SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
|
<SvgIcon name="Refresh" @click="connect(0, 0)" :size="20" class="pointer-icon mr10" title="重新连接" />
|
||||||
</div>
|
</div>
|
||||||
<clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
|
<clipboard-dialog ref="clipboardRef" v-model:visible="state.clipboardDialog.visible" @close="closePaste" @submit="onsubmitClipboard" />
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-if="!state.fullscreen"
|
||||||
|
destroy-on-close
|
||||||
|
:title="state.filesystemDialog.title"
|
||||||
|
v-model="state.filesystemDialog.visible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
|
<machine-file
|
||||||
|
:machine-id="state.filesystemDialog.machineId"
|
||||||
|
:auth-cert-name="state.filesystemDialog.authCertName"
|
||||||
|
:protocol="state.filesystemDialog.protocol"
|
||||||
|
:file-id="state.filesystemDialog.fileId"
|
||||||
|
:path="state.filesystemDialog.path"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog destroy-on-close :title="state.filesystemDialog.title" v-model="state.filesystemDialog.visible" :close-on-click-modal="false" width="70%">
|
|
||||||
|
<el-dialog
|
||||||
|
v-if="!state.fullscreen"
|
||||||
|
destroy-on-close
|
||||||
|
:title="state.filesystemDialog.title"
|
||||||
|
v-model="state.filesystemDialog.visible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
<machine-file
|
<machine-file
|
||||||
:machine-id="state.filesystemDialog.machineId"
|
:machine-id="state.filesystemDialog.machineId"
|
||||||
:auth-cert-name="state.filesystemDialog.authCertName"
|
:auth-cert-name="state.filesystemDialog.authCertName"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { getClientId, getToken } from '@/common/utils/storage';
|
import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
|
||||||
import { templateResolve } from '@/common/utils/string';
|
import { templateResolve } from '@/common/utils/string';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { createFetch } from '@vueuse/core';
|
import { createFetch } from '@vueuse/core';
|
||||||
@@ -8,6 +8,7 @@ import { Result, ResultEnum } from '@/common/request';
|
|||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { unref } from 'vue';
|
import { unref } from 'vue';
|
||||||
import { URL_401 } from '@/router/staticRouter';
|
import { URL_401 } from '@/router/staticRouter';
|
||||||
|
import openApi from '@/common/openApi';
|
||||||
|
|
||||||
const baseUrl: string = config.baseApiUrl;
|
const baseUrl: string = config.baseApiUrl;
|
||||||
|
|
||||||
@@ -88,61 +89,104 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
execute: async function () {
|
execute: async function () {
|
||||||
try {
|
return execUaf(uaf);
|
||||||
await uaf.execute(true);
|
|
||||||
} catch (e: any) {
|
|
||||||
const rejectPromise = Promise.reject(e);
|
|
||||||
|
|
||||||
if (e?.name == 'AbortError') {
|
|
||||||
console.log('请求已取消');
|
|
||||||
return rejectPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const respStatus = uaf.response.value?.status;
|
|
||||||
if (respStatus == 404) {
|
|
||||||
ElMessage.error('请求接口不存在');
|
|
||||||
return rejectPromise;
|
|
||||||
}
|
|
||||||
if (respStatus == 500) {
|
|
||||||
ElMessage.error('服务器响应异常');
|
|
||||||
return rejectPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(e);
|
|
||||||
ElMessage.error('网络请求错误');
|
|
||||||
return rejectPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: Result = uaf.data.value as any;
|
|
||||||
if (!result) {
|
|
||||||
ElMessage.error('网络请求失败');
|
|
||||||
return Promise.reject(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果返回为成功结果,则将结果的data赋值给响应式data
|
|
||||||
if (result.code === ResultEnum.SUCCESS) {
|
|
||||||
uaf.data.value = result.data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果提示没有权限,则跳转至无权限页面
|
|
||||||
if (result.code === ResultEnum.NO_PERMISSION) {
|
|
||||||
router.push({
|
|
||||||
path: URL_401,
|
|
||||||
});
|
|
||||||
return Promise.reject(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
|
||||||
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
|
|
||||||
ElMessage.error(result.msg);
|
|
||||||
uaf.error.value = new Error(result.msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(result);
|
|
||||||
},
|
},
|
||||||
isFetching: uaf.isFetching,
|
isFetching: uaf.isFetching,
|
||||||
data: uaf.data,
|
data: uaf.data,
|
||||||
abort: uaf.abort,
|
abort: uaf.abort,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let refreshingToken = false;
|
||||||
|
let queue: any[] = [];
|
||||||
|
|
||||||
|
async function execUaf(uaf: any) {
|
||||||
|
try {
|
||||||
|
await uaf.execute(true);
|
||||||
|
} catch (e: any) {
|
||||||
|
const rejectPromise = Promise.reject(e);
|
||||||
|
|
||||||
|
if (e?.name == 'AbortError') {
|
||||||
|
console.log('请求已取消');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const respStatus = uaf.response.value?.status;
|
||||||
|
if (respStatus == 404) {
|
||||||
|
ElMessage.error('请求接口不存在');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
if (respStatus == 500) {
|
||||||
|
ElMessage.error('服务器响应异常');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(e);
|
||||||
|
ElMessage.error('网络请求错误');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Result = uaf.data.value as any;
|
||||||
|
if (!result) {
|
||||||
|
ElMessage.error('网络请求失败');
|
||||||
|
return Promise.reject(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultCode = result.code;
|
||||||
|
|
||||||
|
// 如果返回为成功结果,则将结果的data赋值给响应式data
|
||||||
|
if (resultCode === ResultEnum.SUCCESS) {
|
||||||
|
uaf.data.value = result.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是accessToken失效,则使用refreshToken刷新token
|
||||||
|
if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
|
||||||
|
if (refreshingToken) {
|
||||||
|
// 请求加入队列等待, 防止并发多次请求refreshToken
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
queue.push(() => {
|
||||||
|
resolve(execUaf(uaf));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
refreshingToken = true;
|
||||||
|
const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
|
||||||
|
saveToken(res.token);
|
||||||
|
saveRefreshToken(res.refresh_token);
|
||||||
|
// 重新缓存后端用户权限code
|
||||||
|
await openApi.getPermissions();
|
||||||
|
|
||||||
|
// 执行accessToken失效的请求
|
||||||
|
queue.forEach((resolve: any) => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
clearUser();
|
||||||
|
} finally {
|
||||||
|
refreshingToken = false;
|
||||||
|
queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await execUaf(uaf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提示没有权限,则跳转至无权限页面
|
||||||
|
if (resultCode === ResultEnum.NO_PERMISSION) {
|
||||||
|
router.push({
|
||||||
|
path: URL_401,
|
||||||
|
});
|
||||||
|
return Promise.reject(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
||||||
|
if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
|
||||||
|
ElMessage.error(result.msg);
|
||||||
|
uaf.error.value = new Error(result.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(result);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { dateFormat2 } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { getSysStyleConfig } from '@/common/sysconfig';
|
import { getSysStyleConfig } from '@/common/sysconfig';
|
||||||
import { getLocal, getThemeConfig } from '@/common/utils/storage';
|
import { getLocal, getThemeConfig } from '@/common/utils/storage';
|
||||||
@@ -191,7 +191,7 @@ export const useThemeConfig = defineStore('themeConfig', {
|
|||||||
},
|
},
|
||||||
// 设置水印时间为当前时间
|
// 设置水印时间为当前时间
|
||||||
setWatermarkNowTime() {
|
setWatermarkNowTime() {
|
||||||
this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
|
this.themeConfig.watermarkText[1] = formatDate(new Date());
|
||||||
},
|
},
|
||||||
// 切换暗黑模式
|
// 切换暗黑模式
|
||||||
switchDark(isDark: boolean) {
|
switchDark(isDark: boolean) {
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
|
<AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
|
||||||
<!-- {{ procinst.creator }} -->
|
<!-- {{ procinst.creator }} -->
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
|
<el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
|
||||||
|
|
||||||
<div v-if="procinst.duration">
|
<div v-if="procinst.duration">
|
||||||
<el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
|
<el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="结束时间">{{ dateFormat(procinst.endTime) }}</el-descriptions-item>
|
<el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-descriptions-item label="流程状态">
|
<el-descriptions-item label="流程状态">
|
||||||
@@ -86,11 +86,11 @@ import { procinstApi } from './api';
|
|||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||||
import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
|
import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
|
||||||
import ProcdefTasks from './components/ProcdefTasks.vue';
|
import ProcdefTasks from './components/ProcdefTasks.vue';
|
||||||
import { formatTime } from '@/common/utils/format';
|
import { formatTime } from '@/common/utils/format';
|
||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||||
import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
|
import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
|
||||||
|
import { formatDate } from '@/common/utils/format';
|
||||||
|
|
||||||
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
|
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
|
||||||
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
|
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
|
<el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
|
||||||
<template #description>
|
<template #description>
|
||||||
<div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
|
<div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
|
||||||
<div v-if="task.completeTime">{{ `${dateFormat(task.completeTime)}` }}</div>
|
<div v-if="task.completeTime">{{ `${formatDate(task.completeTime)}` }}</div>
|
||||||
<div v-if="task.remark">{{ task.remark }}</div>
|
<div v-if="task.remark">{{ task.remark }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-step>
|
</el-step>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
import { toRefs, reactive, watch, onMounted } from 'vue';
|
import { toRefs, reactive, watch, onMounted } from 'vue';
|
||||||
import { accountApi } from '../../system/api';
|
import { accountApi } from '../../system/api';
|
||||||
import { ProcinstTaskStatus } from '../enums';
|
import { ProcinstTaskStatus } from '../enums';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { ElSteps, ElStep } from 'element-plus';
|
import { ElSteps, ElStep } from 'element-plus';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
<el-col :xs="24" :sm="12" class="personal-item mb6">
|
||||||
<div class="personal-item-label">上次登录时间:</div>
|
<div class="personal-item-label">上次登录时间:</div>
|
||||||
<div class="personal-item-value">{{ dateFormat(userInfo.lastLoginTime) }}</div>
|
<div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
<el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
||||||
<el-table-column prop="createTime" show-overflow-tooltip width="135">
|
<el-table-column prop="createTime" show-overflow-tooltip width="135">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
|
<el-table-column prop="codePath" min-width="400" show-overflow-tooltip>
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
||||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
||||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
|
||||||
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
<el-table-column prop="codePath" min-width="380" show-overflow-tooltip>
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
<el-table-column property="msg" label="消息"></el-table-column>
|
<el-table-column property="msg" label="消息"></el-table-column>
|
||||||
<el-table-column property="createTime" label="时间" width="150">
|
<el-table-column property="createTime" label="时间" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -257,7 +257,7 @@ import { useRouter } from 'vue-router';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { personApi } from '../personal/api';
|
import { personApi } from '../personal/api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||||
import { resourceOpLogApi } from '../ops/tag/api';
|
import { resourceOpLogApi } from '../ops/tag/api';
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
|
|||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { initRouter } from '@/router/index';
|
import { initRouter } from '@/router/index';
|
||||||
import { saveToken, saveUser } from '@/common/utils/storage';
|
import { getRefreshToken, saveRefreshToken, saveToken, saveUser } from '@/common/utils/storage';
|
||||||
import { formatAxis } from '@/common/utils/format';
|
import { formatAxis } from '@/common/utils/format';
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
import { RsaEncrypt } from '@/common/rsa';
|
||||||
@@ -279,19 +279,20 @@ const login = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const otpVerify = async () => {
|
const otpVerify = async () => {
|
||||||
otpFormRef.value.validate(async (valid: boolean) => {
|
try {
|
||||||
if (!valid) {
|
await otpFormRef.value.validate();
|
||||||
return false;
|
} catch (e: any) {
|
||||||
}
|
return false;
|
||||||
try {
|
}
|
||||||
state.loading.otpConfirm = true;
|
|
||||||
const accessToken = await openApi.otpVerify(state.otpDialog.form);
|
try {
|
||||||
await signInSuccess(accessToken);
|
state.loading.otpConfirm = true;
|
||||||
state.otpDialog.visible = false;
|
const res = await openApi.otpVerify(state.otpDialog.form);
|
||||||
} finally {
|
await signInSuccess(res.token, res.refresh_token);
|
||||||
state.loading.otpConfirm = false;
|
state.otpDialog.visible = false;
|
||||||
}
|
} finally {
|
||||||
});
|
state.loading.otpConfirm = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
@@ -327,22 +328,23 @@ const onSignIn = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateUserInfo = async () => {
|
const updateUserInfo = async () => {
|
||||||
baseInfoFormRef.value.validate(async (valid: boolean) => {
|
try {
|
||||||
if (!valid) {
|
await baseInfoFormRef.value.validate();
|
||||||
return false;
|
} catch (e: any) {
|
||||||
}
|
return false;
|
||||||
try {
|
}
|
||||||
state.loading.updateUserConfirm = true;
|
|
||||||
const form = state.baseInfoDialog.form;
|
try {
|
||||||
await personApi.updateAccount.request(state.baseInfoDialog.form);
|
state.loading.updateUserConfirm = true;
|
||||||
state.baseInfoDialog.visible = false;
|
const form = state.baseInfoDialog.form;
|
||||||
useUserInfo().userInfo.username = form.username;
|
await personApi.updateAccount.request(state.baseInfoDialog.form);
|
||||||
useUserInfo().userInfo.name = form.name;
|
state.baseInfoDialog.visible = false;
|
||||||
await toIndex();
|
useUserInfo().userInfo.username = form.username;
|
||||||
} finally {
|
useUserInfo().userInfo.name = form.name;
|
||||||
state.loading.updateUserConfirm = false;
|
await toIndex();
|
||||||
}
|
} finally {
|
||||||
});
|
state.loading.updateUserConfirm = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginResDeal = (loginRes: any) => {
|
const loginResDeal = (loginRes: any) => {
|
||||||
@@ -366,7 +368,7 @@ const loginResDeal = (loginRes: any) => {
|
|||||||
const token = loginRes.token;
|
const token = loginRes.token;
|
||||||
// 如果不需要 otp校验,则该token即为accessToken,否则为otp校验token
|
// 如果不需要 otp校验,则该token即为accessToken,否则为otp校验token
|
||||||
if (loginRes.otp == -1) {
|
if (loginRes.otp == -1) {
|
||||||
signInSuccess(token);
|
signInSuccess(token, loginRes.refresh_token);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,12 +381,16 @@ const loginResDeal = (loginRes: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 登录成功后的跳转
|
// 登录成功后的跳转
|
||||||
const signInSuccess = async (accessToken: string = '') => {
|
const signInSuccess = async (accessToken: string = '', refreshToken = '') => {
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
accessToken = getToken();
|
accessToken = getToken();
|
||||||
}
|
}
|
||||||
|
if (!refreshToken) {
|
||||||
|
refreshToken = getRefreshToken();
|
||||||
|
}
|
||||||
// 存储 token 到浏览器缓存
|
// 存储 token 到浏览器缓存
|
||||||
saveToken(accessToken);
|
saveToken(accessToken);
|
||||||
|
saveRefreshToken(refreshToken);
|
||||||
|
|
||||||
// 初始化路由
|
// 初始化路由
|
||||||
await initRouter();
|
await initRouter();
|
||||||
@@ -415,26 +421,27 @@ const toIndex = async () => {
|
|||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const changePwd = () => {
|
const changePwd = async () => {
|
||||||
changePwdFormRef.value.validate(async (valid: boolean) => {
|
try {
|
||||||
if (!valid) {
|
await changePwdFormRef.value.validate();
|
||||||
return false;
|
} catch (e: any) {
|
||||||
}
|
return false;
|
||||||
try {
|
}
|
||||||
state.loading.changePwd = true;
|
|
||||||
const form = state.changePwdDialog.form;
|
try {
|
||||||
const changePwdReq: any = { ...form };
|
state.loading.changePwd = true;
|
||||||
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
|
const form = state.changePwdDialog.form;
|
||||||
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
|
const changePwdReq: any = { ...form };
|
||||||
await openApi.changePwd(changePwdReq);
|
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
|
||||||
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
|
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
|
||||||
state.loginForm.password = state.changePwdDialog.form.newPassword;
|
await openApi.changePwd(changePwdReq);
|
||||||
state.changePwdDialog.visible = false;
|
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
|
||||||
getCaptcha();
|
state.loginForm.password = state.changePwdDialog.form.newPassword;
|
||||||
} finally {
|
state.changePwdDialog.visible = false;
|
||||||
state.loading.changePwd = false;
|
getCaptcha();
|
||||||
}
|
} finally {
|
||||||
});
|
state.loading.changePwd = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelChangePwd = () => {
|
const cancelChangePwd = () => {
|
||||||
|
|||||||
@@ -187,10 +187,10 @@
|
|||||||
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
|
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
|
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data?.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data?.updateTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -205,7 +205,7 @@ import { dbApi } from './api';
|
|||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { joinClientParams } from '@/common/request';
|
import { joinClientParams } from '@/common/request';
|
||||||
import { isTrue } from '@/common/assert';
|
import { isTrue } from '@/common/assert';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { hasPerms } from '@/components/auth/auth';
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
|
|||||||
@@ -45,14 +45,14 @@
|
|||||||
<el-descriptions :column="1" border>
|
<el-descriptions :column="1" border>
|
||||||
<el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="数据库名称">{{ infoDialog.data.dbName }}</el-descriptions-item>
|
||||||
<el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
|
<el-descriptions-item v-if="infoDialog.data.pointInTime" :span="1" label="恢复时间点">{{
|
||||||
dateFormat(infoDialog.data.pointInTime)
|
formatDate(infoDialog.data.pointInTime)
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
|
<el-descriptions-item v-if="!infoDialog.data.pointInTime" :span="1" label="数据库备份">{{
|
||||||
infoDialog.data.dbBackupHistoryName
|
infoDialog.data.dbBackupHistoryName
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="开始时间">{{ formatDate(infoDialog.data.startTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="执行时间">{{ formatDate(infoDialog.data.lastTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -66,7 +66,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
|
|||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { SearchItem } from '@/components/SearchForm';
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
|
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
|
||||||
const pageTableRef: Ref<any> = ref(null);
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
|
|
||||||
|
|||||||
@@ -53,10 +53,10 @@
|
|||||||
|
|
||||||
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { hasPerms } from '@/components/auth/auth';
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
|
|||||||
@@ -296,44 +296,37 @@ const onRunSql = async (newTab = false) => {
|
|||||||
notBlank(sql && sql.trim(), '请选中需要执行的sql');
|
notBlank(sql && sql.trim(), '请选中需要执行的sql');
|
||||||
// 去除字符串前的空格、换行等
|
// 去除字符串前的空格、换行等
|
||||||
sql = sql.replace(/(^\s*)/g, '');
|
sql = sql.replace(/(^\s*)/g, '');
|
||||||
let execRemark = '';
|
|
||||||
let canRun = true;
|
|
||||||
|
|
||||||
// 简单截取前十个字符
|
// 简单截取前十个字符
|
||||||
const sqlPrefix = sql.slice(0, 10).toLowerCase();
|
const sqlPrefix = sql.slice(0, 10).toLowerCase();
|
||||||
if (
|
const nonQuery =
|
||||||
sqlPrefix.startsWith('update') ||
|
sqlPrefix.startsWith('update') ||
|
||||||
sqlPrefix.startsWith('insert') ||
|
sqlPrefix.startsWith('insert') ||
|
||||||
sqlPrefix.startsWith('delete') ||
|
sqlPrefix.startsWith('delete') ||
|
||||||
sqlPrefix.startsWith('alert') ||
|
sqlPrefix.startsWith('alert') ||
|
||||||
sqlPrefix.startsWith('drop') ||
|
sqlPrefix.startsWith('drop') ||
|
||||||
sqlPrefix.startsWith('create')
|
sqlPrefix.startsWith('create');
|
||||||
) {
|
|
||||||
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
// 启用工单审批
|
||||||
confirmButtonText: '确定',
|
if (nonQuery && getNowDbInst().flowProcdef) {
|
||||||
cancelButtonText: '取消',
|
try {
|
||||||
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
|
getNowDbInst().promptExeSql(props.dbName, sql, null, () => {
|
||||||
inputErrorMessage: '请输入执行该sql的备注信息',
|
ElMessage.success('工单提交成功');
|
||||||
});
|
});
|
||||||
execRemark = res.value;
|
} catch (e) {
|
||||||
if (!execRemark) {
|
ElMessage.success('工单提交失败');
|
||||||
canRun = false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!canRun) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启用工单审批
|
let execRemark;
|
||||||
if (execRemark && getNowDbInst().flowProcdef) {
|
if (nonQuery) {
|
||||||
try {
|
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
||||||
await getNowDbInst().runSql(props.dbName, sql, execRemark);
|
confirmButtonText: '确定',
|
||||||
ElMessage.success('工单提交成功');
|
cancelButtonText: '取消',
|
||||||
return;
|
inputErrorMessage: '输入执行该sql的备注信息',
|
||||||
} catch (e) {
|
});
|
||||||
ElMessage.success('工单提交失败');
|
execRemark = res.value;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let execRes: ExecResTab;
|
let execRes: ExecResTab;
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const runSql = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
|
runSuccess = true;
|
||||||
|
|
||||||
const res = await dbApi.sqlExec.request({
|
const res = await dbApi.sqlExec.request({
|
||||||
id: props.dbId,
|
id: props.dbId,
|
||||||
db: props.db,
|
db: props.db,
|
||||||
@@ -75,7 +77,6 @@ const runSql = async () => {
|
|||||||
|
|
||||||
// 存在流程审批
|
// 存在流程审批
|
||||||
if (props.flowProcdef) {
|
if (props.flowProcdef) {
|
||||||
runSuccess = false;
|
|
||||||
ElMessage.success('工单提交成功');
|
ElMessage.success('工单提交成功');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,6 @@ const runSql = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runSuccess = true;
|
|
||||||
ElMessage.success('执行成功');
|
ElMessage.success('执行成功');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
runSuccess = false;
|
runSuccess = false;
|
||||||
@@ -96,9 +96,9 @@ const runSql = async () => {
|
|||||||
if (props.runSuccessCallback) {
|
if (props.runSuccessCallback) {
|
||||||
props.runSuccessCallback();
|
props.runSuccessCallback();
|
||||||
}
|
}
|
||||||
|
cancel();
|
||||||
}
|
}
|
||||||
state.btnLoading = false;
|
state.btnLoading = false;
|
||||||
cancel();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ import { DbInst } from '@/views/ops/db/db';
|
|||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
import { exportCsv, exportFile } from '@/common/utils/export';
|
import { exportCsv, exportFile } from '@/common/utils/export';
|
||||||
import { dateStrFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { useIntervalFn, useStorage } from '@vueuse/core';
|
import { useIntervalFn, useStorage } from '@vueuse/core';
|
||||||
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
|
import { ColumnTypeSubscript, compatibleMysql, DataType, DbDialect, getDbDialect } from '../../dialect/index';
|
||||||
import ColumnFormItem from './ColumnFormItem.vue';
|
import ColumnFormItem from './ColumnFormItem.vue';
|
||||||
@@ -617,6 +617,10 @@ const onDeleteData = async () => {
|
|||||||
const db = state.db;
|
const db = state.db;
|
||||||
const dbInst = getNowDbInst();
|
const dbInst = getNowDbInst();
|
||||||
dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
|
dbInst.promptExeSql(db, await dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas as any), null, () => {
|
||||||
|
// 存在流程则恢复原值,需工单流程审批完后自动执行
|
||||||
|
if (dbInst.flowProcdef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
emits('dataDelete', deleteDatas);
|
emits('dataDelete', deleteDatas);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -628,7 +632,7 @@ const onEditRowData = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = selectionDatas[0];
|
const data = selectionDatas[0];
|
||||||
state.tableDataFormDialog.data = data;
|
state.tableDataFormDialog.data = { ...data };
|
||||||
state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
|
state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
|
||||||
state.tableDataFormDialog.visible = true;
|
state.tableDataFormDialog.visible = true;
|
||||||
};
|
};
|
||||||
@@ -674,13 +678,13 @@ const onExportCsv = () => {
|
|||||||
columnNames.push(column.columnName);
|
columnNames.push(column.columnName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, columnNames, dataList);
|
exportCsv(`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}`, columnNames, dataList);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onExportSql = async () => {
|
const onExportSql = async () => {
|
||||||
const selectionDatas = state.datas;
|
const selectionDatas = state.datas;
|
||||||
exportFile(
|
exportFile(
|
||||||
`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}.sql`,
|
`数据导出-${state.table}-${formatDate(new Date(), 'yyyyMMddHHmm')}.sql`,
|
||||||
await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
|
await getNowDbInst().genInsertSql(state.db, state.table, selectionDatas)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -763,7 +767,12 @@ const submitUpdateFields = async () => {
|
|||||||
res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
|
res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
|
||||||
}
|
}
|
||||||
|
|
||||||
dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
|
dbInst.promptExeSql(db, res, null, () => {
|
||||||
|
// 存在流程则恢复原值,需工单流程审批完后自动执行
|
||||||
|
if (dbInst.flowProcdef) {
|
||||||
|
cancelUpdateFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
triggerRefresh();
|
triggerRefresh();
|
||||||
cellUpdateMap.clear();
|
cellUpdateMap.clear();
|
||||||
changeUpdatedField();
|
changeUpdatedField();
|
||||||
@@ -810,11 +819,11 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
|
|||||||
|
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case DataType.Time:
|
case DataType.Time:
|
||||||
return dateStrFormat('HH:mm:ss', originValue);
|
return formatDate(originValue, 'HH:mm:ss');
|
||||||
case DataType.Date:
|
case DataType.Date:
|
||||||
return dateStrFormat('yyyy-MM-dd', originValue);
|
return formatDate(originValue, 'YYYY-MM-DD');
|
||||||
case DataType.DateTime:
|
case DataType.DateTime:
|
||||||
return dateStrFormat('yyyy-MM-dd HH:mm:ss', originValue);
|
return formatDate(originValue, 'YYYY-MM-DD HH:mm:ss');
|
||||||
default:
|
default:
|
||||||
return originValue;
|
return originValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,35 +85,35 @@ const closeDialog = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const confirm = async () => {
|
const confirm = async () => {
|
||||||
dataForm.value.validate(async (valid: boolean) => {
|
try {
|
||||||
if (!valid) {
|
await dataForm.value.validate();
|
||||||
ElMessage.error('请正确填写数据信息');
|
} catch (e: any) {
|
||||||
return false;
|
ElMessage.error('请正确填写数据信息');
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const dbInst = props.dbInst;
|
const dbInst = props.dbInst;
|
||||||
const data = modelValue.value;
|
const data = modelValue.value;
|
||||||
const db = props.dbName;
|
const db = props.dbName;
|
||||||
const tableName = props.tableName;
|
const tableName = props.tableName;
|
||||||
|
|
||||||
let sql = '';
|
let sql = '';
|
||||||
if (oldValue) {
|
if (oldValue) {
|
||||||
const updateColumnValue = {};
|
const updateColumnValue = {};
|
||||||
Object.keys(oldValue).forEach((key) => {
|
Object.keys(oldValue).forEach((key) => {
|
||||||
// 如果新旧值不相等,则为需要更新的字段
|
// 如果新旧值不相等,则为需要更新的字段
|
||||||
if (oldValue[key] !== modelValue.value[key]) {
|
if (oldValue[key] !== modelValue.value[key]) {
|
||||||
updateColumnValue[key] = modelValue.value[key];
|
updateColumnValue[key] = modelValue.value[key];
|
||||||
}
|
}
|
||||||
});
|
|
||||||
sql = await dbInst.genUpdateSql(db, tableName, updateColumnValue, oldValue);
|
|
||||||
} else {
|
|
||||||
sql = await dbInst.genInsertSql(db, tableName, [data], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
dbInst.promptExeSql(db, sql, null, () => {
|
|
||||||
closeDialog();
|
|
||||||
emit('submitSuccess');
|
|
||||||
});
|
});
|
||||||
|
sql = await dbInst.genUpdateSql(db, tableName, updateColumnValue, oldValue);
|
||||||
|
} else {
|
||||||
|
sql = await dbInst.genInsertSql(db, tableName, [data], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbInst.promptExeSql(db, sql, null, () => {
|
||||||
|
closeDialog();
|
||||||
|
emit('submitSuccess');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -50,17 +50,6 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
<el-tooltip :show-after="500" class="box-item" effect="dark" content="commit" placement="top">
|
|
||||||
<template #content>
|
|
||||||
1. 右击数据/表头可显示操作菜单 <br />
|
|
||||||
2. 按住Ctrl点击数据则为多选 <br />
|
|
||||||
3. 双击单元格可编辑数据 <br />
|
|
||||||
4. 鼠标悬停字段名或标签树的表名可提示相关备注
|
|
||||||
</template>
|
|
||||||
<el-link icon="QuestionFilled" :underline="false"> </el-link>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
|
||||||
|
|
||||||
<!-- 表数据展示配置 -->
|
<!-- 表数据展示配置 -->
|
||||||
<el-popover
|
<el-popover
|
||||||
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
|
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="db-table">
|
<div class="db-table">
|
||||||
<el-row class="mb5">
|
<el-row class="mb5">
|
||||||
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
|
<el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button class="ml5" type="success" size="small">导出</el-button>
|
<el-button class="ml5" type="success" size="small">导出</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,16 +13,15 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="导出表: ">
|
<el-form-item>
|
||||||
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tables">
|
<el-table :data="state.dumpInfo.tables" empty-text="请先选择要导出的表" max-height="300" size="small">
|
||||||
<el-table-column type="selection" width="45" />
|
|
||||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
|
||||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
|
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> </el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div style="text-align: right">
|
<div style="text-align: right">
|
||||||
<el-button @click="showDumpInfo = false" size="small">取消</el-button>
|
<el-button @click="state.dumpInfo.visible = false" size="small">取消</el-button>
|
||||||
<el-button @click="dump(db)" type="success" size="small">确定</el-button>
|
<el-button @click="dump(db)" type="success" size="small">确定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
@@ -30,7 +29,9 @@
|
|||||||
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" border stripe :data="filterTableInfos" size="small" :height="height">
|
<el-table v-loading="loading" @selection-change="handleDumpTableSelectionChange" border stripe :data="filterTableInfos" size="small" :height="height">
|
||||||
|
<el-table-column type="selection" width="30" />
|
||||||
|
|
||||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
<el-input v-model="tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
||||||
@@ -161,8 +162,8 @@ const state = reactive({
|
|||||||
tables: [],
|
tables: [],
|
||||||
tableNameSearch: '',
|
tableNameSearch: '',
|
||||||
tableCommentSearch: '',
|
tableCommentSearch: '',
|
||||||
showDumpInfo: false,
|
|
||||||
dumpInfo: {
|
dumpInfo: {
|
||||||
|
visible: false,
|
||||||
id: 0,
|
id: 0,
|
||||||
db: '',
|
db: '',
|
||||||
type: 3,
|
type: 3,
|
||||||
@@ -201,19 +202,7 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const { loading, tableNameSearch, tableCommentSearch, dumpInfo, chooseTableName, columnDialog, indexDialog, ddlDialog, tableCreateDialog } = toRefs(state);
|
||||||
loading,
|
|
||||||
tables,
|
|
||||||
tableNameSearch,
|
|
||||||
tableCommentSearch,
|
|
||||||
showDumpInfo,
|
|
||||||
dumpInfo,
|
|
||||||
chooseTableName,
|
|
||||||
columnDialog,
|
|
||||||
indexDialog,
|
|
||||||
ddlDialog,
|
|
||||||
tableCreateDialog,
|
|
||||||
} = toRefs(state);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getTables();
|
getTables();
|
||||||
@@ -259,21 +248,22 @@ const getTables = async () => {
|
|||||||
* 选择导出数据库表
|
* 选择导出数据库表
|
||||||
*/
|
*/
|
||||||
const handleDumpTableSelectionChange = (vals: any) => {
|
const handleDumpTableSelectionChange = (vals: any) => {
|
||||||
state.dumpInfo.tables = vals.map((x: any) => x.tableName);
|
state.dumpInfo.tables = vals;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据库信息导出
|
* 数据库信息导出
|
||||||
*/
|
*/
|
||||||
const dump = (db: string) => {
|
const dump = (db: string) => {
|
||||||
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
|
isTrue(state.dumpInfo.tables.length > 0, '请先选择要导出的表');
|
||||||
|
const tableNames = state.dumpInfo.tables.map((x: any) => x.tableName);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.setAttribute(
|
a.setAttribute(
|
||||||
'href',
|
'href',
|
||||||
`${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${state.dumpInfo.tables.join(',')}&${joinClientParams()}`
|
`${config.baseApiUrl}/dbs/${props.dbId}/dump?db=${db}&type=${state.dumpInfo.type}&tables=${tableNames.join(',')}&${joinClientParams()}`
|
||||||
);
|
);
|
||||||
a.click();
|
a.click();
|
||||||
state.showDumpInfo = false;
|
state.dumpInfo.visible = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showColumns = async (row: any) => {
|
const showColumns = async (row: any) => {
|
||||||
|
|||||||
@@ -352,7 +352,6 @@ export class DbInst {
|
|||||||
* 弹框提示是否执行sql
|
* 弹框提示是否执行sql
|
||||||
*/
|
*/
|
||||||
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
|
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
|
||||||
console.log(this);
|
|
||||||
SqlExecBox({
|
SqlExecBox({
|
||||||
sql,
|
sql,
|
||||||
dbId: this.id,
|
dbId: this.id,
|
||||||
|
|||||||
@@ -160,10 +160,10 @@
|
|||||||
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -236,12 +236,11 @@ import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue
|
|||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
|
||||||
import ResourceTags from '../component/ResourceTags.vue';
|
import ResourceTags from '../component/ResourceTags.vue';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { hasPerms } from '@/components/auth/auth';
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
import { formatByteSize } from '@/common/utils/format';
|
import { formatByteSize, formatDate } from '@/common/utils/format';
|
||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||||
import { SearchItem } from '@/components/SearchForm';
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
import { getTagPathSearchItem } from '../component/tag';
|
import { getTagPathSearchItem } from '../component/tag';
|
||||||
|
|||||||
@@ -103,10 +103,10 @@
|
|||||||
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(infoDialog.data.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(infoDialog.data.updateTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
import { getMachineTerminalSocketUrl, machineApi } from './api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { hasPerms } from '@/components/auth/auth';
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<el-table-column prop="cmd" label="命令" show-overflow-tooltip min-width="150px"> </el-table-column>
|
<el-table-column prop="cmd" label="命令" show-overflow-tooltip min-width="150px"> </el-table-column>
|
||||||
<el-table-column prop="time" label="执行时间" min-width="80" show-overflow-tooltip>
|
<el-table-column prop="time" label="执行时间" min-width="80" show-overflow-tooltip>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(new Date(scope.row.time * 1000).toString()) }}
|
{{ formatDate(new Date(scope.row.time * 1000).toString()) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -48,7 +48,7 @@ import * as AsciinemaPlayer from 'asciinema-player';
|
|||||||
import 'asciinema-player/dist/bundle/asciinema-player.css';
|
import 'asciinema-player/dist/bundle/asciinema-player.css';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: { type: Boolean },
|
visible: { type: Boolean },
|
||||||
@@ -122,8 +122,8 @@ const playRec = async (rec: any) => {
|
|||||||
idleTimeLimit: 2,
|
idleTimeLimit: 2,
|
||||||
// fit: false,
|
// fit: false,
|
||||||
// terminalFontSize: 'small',
|
// terminalFontSize: 'small',
|
||||||
cols: 144,
|
// cols: 144,
|
||||||
rows: 32,
|
// rows: 32,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -130,10 +130,10 @@
|
|||||||
<el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
|
<el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(detailDialog.data.createTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="创建者">{{ detailDialog.data.creator }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="创建者">{{ detailDialog.data.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(detailDialog.data.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(detailDialog.data.updateTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="修改者">{{ detailDialog.data.modifier }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="修改者">{{ detailDialog.data.modifier }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -153,7 +153,7 @@ import { redisApi } from './api';
|
|||||||
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
import { onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import RedisEdit from './RedisEdit.vue';
|
import RedisEdit from './RedisEdit.vue';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import ResourceTags from '../component/ResourceTags.vue';
|
import ResourceTags from '../component/ResourceTags.vue';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
|
|||||||
@@ -82,9 +82,9 @@
|
|||||||
<el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
|
<el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="创建者">{{ currentTag.creator }}</el-descriptions-item>
|
<el-descriptions-item label="创建者">{{ currentTag.creator }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间">{{ dateFormat(currentTag.createTime) }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatDate(currentTag.createTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="修改者">{{ currentTag.modifier }}</el-descriptions-item>
|
<el-descriptions-item label="修改者">{{ currentTag.modifier }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">{{ dateFormat(currentTag.updateTime) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ formatDate(currentTag.updateTime) }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
import { toRefs, ref, watch, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
|
import { toRefs, ref, watch, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { tagApi } from './api';
|
import { tagApi } from './api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
|
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { Splitpanes, Pane } from 'splitpanes';
|
import { Splitpanes, Pane } from 'splitpanes';
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
|
<TagCodePath :path="data.tags?.map((tag: any) => tag.codePath)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #validityDate="{ data }"> {{ data.validityStartDate }} ~ {{ data.validityEndDate }} </template>
|
||||||
|
|
||||||
<template #action="{ data }">
|
<template #action="{ data }">
|
||||||
<el-button @click.prevent="showMembers(data)" link type="primary">成员</el-button>
|
<el-button @click.prevent="showMembers(data)" link type="primary">成员</el-button>
|
||||||
|
|
||||||
@@ -40,12 +42,26 @@
|
|||||||
<el-form-item prop="name" label="团队名" required>
|
<el-form-item prop="name" label="团队名" required>
|
||||||
<el-input :disabled="addTeamDialog.form.id" v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
|
<el-input :disabled="addTeamDialog.form.id" v-model="addTeamDialog.form.name" auto-complete="off"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="validityDate" label="生效时间" required>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="addTeamDialog.form.validityDate"
|
||||||
|
type="datetimerange"
|
||||||
|
start-placeholder="生效开始时间"
|
||||||
|
end-placeholder="生效结束时间"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
date-format="YYYY-MM-DD"
|
||||||
|
time-format="HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
|
<el-input v-model="addTeamDialog.form.remark" auto-complete="off"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item prop="tag" label="标签">
|
<el-form-item prop="tag" label="标签">
|
||||||
<TagTreeCheck v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
|
<TagTreeCheck height="calc(100vh - 390px)" v-model="state.addTeamDialog.form.codePaths" :tag-type="0" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -101,6 +117,7 @@ import AccountSelectFormItem from '@/views/system/account/components/AccountSele
|
|||||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||||
import TagTreeCheck from '../component/TagTreeCheck.vue';
|
import TagTreeCheck from '../component/TagTreeCheck.vue';
|
||||||
import TagCodePath from '../component/TagCodePath.vue';
|
import TagCodePath from '../component/TagCodePath.vue';
|
||||||
|
import { formatDate } from '@/common/utils/format';
|
||||||
|
|
||||||
const teamForm: any = ref(null);
|
const teamForm: any = ref(null);
|
||||||
const pageTableRef: Ref<any> = ref(null);
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
@@ -114,13 +131,21 @@ const teamFormRules = {
|
|||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
validityDate: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择生效时间',
|
||||||
|
trigger: ['change', 'blur'],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchItems = [SearchItem.input('name', '团队名称')];
|
const searchItems = [SearchItem.input('name', '团队名称')];
|
||||||
const columns = [
|
const columns = [
|
||||||
TableColumn.new('name', '团队名称'),
|
TableColumn.new('name', '团队名称'),
|
||||||
TableColumn.new('remark', '备注'),
|
|
||||||
TableColumn.new('tags', '分配标签').isSlot().setAddWidth(40),
|
TableColumn.new('tags', '分配标签').isSlot().setAddWidth(40),
|
||||||
|
TableColumn.new('validityDate', '有效期').isSlot('validityDate').setMinWidth(310),
|
||||||
|
TableColumn.new('remark', '备注'),
|
||||||
TableColumn.new('creator', '创建者'),
|
TableColumn.new('creator', '创建者'),
|
||||||
TableColumn.new('createTime', '创建时间').isTime(),
|
TableColumn.new('createTime', '创建时间').isTime(),
|
||||||
TableColumn.new('modifier', '修改者'),
|
TableColumn.new('modifier', '修改者'),
|
||||||
@@ -132,7 +157,7 @@ const state = reactive({
|
|||||||
currentEditPermissions: false,
|
currentEditPermissions: false,
|
||||||
addTeamDialog: {
|
addTeamDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
form: { id: 0, name: '', remark: '', codePaths: [] },
|
form: { id: 0, name: '', validityDate: ['', ''], validityStartDate: '', validityEndDate: '', remark: '', codePaths: [] },
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
@@ -182,24 +207,33 @@ const showSaveTeamDialog = async (data: any) => {
|
|||||||
if (data) {
|
if (data) {
|
||||||
state.addTeamDialog.form.id = data.id;
|
state.addTeamDialog.form.id = data.id;
|
||||||
state.addTeamDialog.form.name = data.name;
|
state.addTeamDialog.form.name = data.name;
|
||||||
|
state.addTeamDialog.form.validityDate = [data.validityStartDate, data.validityEndDate];
|
||||||
state.addTeamDialog.form.remark = data.remark;
|
state.addTeamDialog.form.remark = data.remark;
|
||||||
state.addTeamDialog.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
|
state.addTeamDialog.form.codePaths = data.tags?.map((tag: any) => tag.codePath);
|
||||||
// state.addTeamDialog.form.tags = await tagApi.getRelateTagIds.request({ relateType: TagTreeRelateTypeEnum.Team.value, relateId: data.id });
|
} else {
|
||||||
|
let end = new Date();
|
||||||
|
end.setFullYear(end.getFullYear() + 10);
|
||||||
|
state.addTeamDialog.form.validityDate = [formatDate(new Date()), formatDate(end)];
|
||||||
}
|
}
|
||||||
|
|
||||||
state.addTeamDialog.visible = true;
|
state.addTeamDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveTeam = async () => {
|
const saveTeam = async () => {
|
||||||
teamForm.value.validate(async (valid: any) => {
|
try {
|
||||||
if (valid) {
|
await teamForm.value.validate();
|
||||||
const form = state.addTeamDialog.form;
|
} catch (e: any) {
|
||||||
await tagApi.saveTeam.request(form);
|
ElMessage.error('请正确填写信息');
|
||||||
ElMessage.success('保存成功');
|
return false;
|
||||||
search();
|
}
|
||||||
cancelSaveTeam();
|
|
||||||
}
|
const form = state.addTeamDialog.form;
|
||||||
});
|
form.validityStartDate = form.validityDate[0];
|
||||||
|
form.validityEndDate = form.validityDate[1];
|
||||||
|
await tagApi.saveTeam.request(form);
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
search();
|
||||||
|
cancelSaveTeam();
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelSaveTeam = () => {
|
const cancelSaveTeam = () => {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<el-table-column property="creator" label="分配账号" width="125"></el-table-column>
|
<el-table-column property="creator" label="分配账号" width="125"></el-table-column>
|
||||||
<el-table-column property="createTime" label="分配时间">
|
<el-table-column property="createTime" label="分配时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ dateFormat(scope.row.createTime) }}
|
{{ formatDate(scope.row.createTime) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -60,7 +60,7 @@ import AccountEdit from './AccountEdit.vue';
|
|||||||
import { AccountStatusEnum } from '../enums';
|
import { AccountStatusEnum } from '../enums';
|
||||||
import { accountApi } from '../api';
|
import { accountApi } from '../api';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { hasPerms } from '@/components/auth/auth';
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
|
|||||||
@@ -89,9 +89,9 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="创建者">{{ currentResource.creator }}</el-descriptions-item>
|
<el-descriptions-item label="创建者">{{ currentResource.creator }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间">{{ dateFormat(currentResource.createTime) }} </el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatDate(currentResource.createTime) }} </el-descriptions-item>
|
||||||
<el-descriptions-item label="修改者">{{ currentResource.modifier }}</el-descriptions-item>
|
<el-descriptions-item label="修改者">{{ currentResource.modifier }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">{{ dateFormat(currentResource.updateTime) }} </el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ formatDate(currentResource.updateTime) }} </el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
@@ -119,7 +119,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
|||||||
import ResourceEdit from './ResourceEdit.vue';
|
import ResourceEdit from './ResourceEdit.vue';
|
||||||
import { ResourceTypeEnum } from '../enums';
|
import { ResourceTypeEnum } from '../enums';
|
||||||
import { resourceApi } from '../api';
|
import { resourceApi } from '../api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||||
import { Splitpanes, Pane } from 'splitpanes';
|
import { Splitpanes, Pane } from 'splitpanes';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
{{ data.creator }}
|
{{ data.creator }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="分配时间">
|
<el-descriptions-item label="分配时间">
|
||||||
{{ dateFormat(data.createTime) }}
|
{{ formatDate(data.createTime) }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, watch } from 'vue';
|
import { toRefs, reactive, watch } from 'vue';
|
||||||
import { ResourceTypeEnum } from '../enums';
|
import { ResourceTypeEnum } from '../enums';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { formatDate } from '@/common/utils/format';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ server:
|
|||||||
cert-file: ./default.pem
|
cert-file: ./default.pem
|
||||||
jwt:
|
jwt:
|
||||||
# jwt key,不设置默认使用随机字符串
|
# jwt key,不设置默认使用随机字符串
|
||||||
key:
|
key: 333333000000
|
||||||
# 过期时间单位分钟
|
# accessToken过期时间单位分钟
|
||||||
expire-time: 1440
|
expire-time: 720
|
||||||
|
# refreshToken过期时间单位分钟
|
||||||
|
refresh-token-expire-time: 4320
|
||||||
# 资源密码aes加密key
|
# 资源密码aes加密key
|
||||||
aes:
|
aes:
|
||||||
key: 1111111111111111
|
key: 1111111111111111
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ require (
|
|||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/redis/go-redis/v9 v9.5.1
|
github.com/redis/go-redis/v9 v9.5.1
|
||||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||||
github.com/sijms/go-ora/v2 v2.8.16
|
github.com/sijms/go-ora/v2 v2.8.17
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/veops/go-ansiterm v0.0.5
|
github.com/veops/go-ansiterm v0.0.5
|
||||||
go.mongodb.org/mongo-driver v1.15.0 // mongo
|
go.mongodb.org/mongo-driver v1.15.0 // mongo
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"mayfly-go/internal/auth/api/form"
|
"mayfly-go/internal/auth/api/form"
|
||||||
"mayfly-go/internal/auth/config"
|
"mayfly-go/internal/auth/config"
|
||||||
@@ -71,11 +70,12 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OtpVerifyInfo struct {
|
type OtpVerifyInfo struct {
|
||||||
AccountId uint64
|
AccountId uint64
|
||||||
Username string
|
Username string
|
||||||
OptStatus int
|
OptStatus int
|
||||||
AccessToken string
|
AccessToken string
|
||||||
OtpSecret string
|
RefreshToken string
|
||||||
|
OtpSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OTP双因素校验
|
// OTP双因素校验
|
||||||
@@ -84,10 +84,9 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
|||||||
req.BindJsonAndValid(rc, otpVerify)
|
req.BindJsonAndValid(rc, otpVerify)
|
||||||
|
|
||||||
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
|
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
|
||||||
otpInfoJson := cache.GetStr(tokenKey)
|
|
||||||
biz.NotEmpty(otpInfoJson, "otpToken错误或失效, 请重新登陆获取")
|
|
||||||
otpInfo := new(OtpVerifyInfo)
|
otpInfo := new(OtpVerifyInfo)
|
||||||
json.Unmarshal([]byte(otpInfoJson), otpInfo)
|
ok := cache.Get(tokenKey, otpInfo)
|
||||||
|
biz.IsTrue(ok, "otpToken错误或失效, 请重新登陆获取")
|
||||||
|
|
||||||
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
|
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
|
||||||
failCount := cache.GetInt(failCountKey)
|
failCount := cache.GetInt(failCountKey)
|
||||||
@@ -116,7 +115,20 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
|||||||
go saveLogin(la, getIpAndRegion(rc))
|
go saveLogin(la, getIpAndRegion(rc))
|
||||||
|
|
||||||
cache.Del(tokenKey)
|
cache.Del(tokenKey)
|
||||||
rc.ResData = accessToken
|
rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
|
||||||
|
refreshToken := rc.Query("refresh_token")
|
||||||
|
biz.NotEmpty(refreshToken, "refresh_token不能为空")
|
||||||
|
|
||||||
|
accountId, username, err := req.ParseToken(refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
panic(errorx.PermissionErr)
|
||||||
|
}
|
||||||
|
token, refreshToken, err := req.CreateToken(accountId, username)
|
||||||
|
biz.ErrIsNil(err)
|
||||||
|
rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccountLogin) Logout(rc *req.Ctx) {
|
func (a *AccountLogin) Logout(rc *req.Ctx) {
|
||||||
|
|||||||
@@ -41,18 +41,19 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
|
|||||||
// 默认为不校验otp
|
// 默认为不校验otp
|
||||||
otpStatus := OtpStatusNone
|
otpStatus := OtpStatusNone
|
||||||
// 访问系统使用的token
|
// 访问系统使用的token
|
||||||
accessToken, err := req.CreateToken(account.Id, username)
|
accessToken, refreshToken, err := req.CreateToken(account.Id, username)
|
||||||
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
|
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
|
||||||
|
|
||||||
// 若系统配置中设置开启otp双因素校验,则进行otp校验
|
// 若系统配置中设置开启otp双因素校验,则进行otp校验
|
||||||
if accountLoginSecurity.UseOtp {
|
if accountLoginSecurity.UseOtp {
|
||||||
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken)
|
otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken, refreshToken)
|
||||||
otpStatus = otpInfo.OptStatus
|
otpStatus = otpInfo.OptStatus
|
||||||
if otpurl != "" {
|
if otpurl != "" {
|
||||||
res["otpUrl"] = otpurl
|
res["otpUrl"] = otpurl
|
||||||
}
|
}
|
||||||
accessToken = otpToken
|
accessToken = otpToken
|
||||||
} else {
|
} else {
|
||||||
|
res["refresh_token"] = refreshToken
|
||||||
// 不进行otp二次校验则直接返回accessToken
|
// 不进行otp二次校验则直接返回accessToken
|
||||||
// 保存登录消息
|
// 保存登录消息
|
||||||
go saveLogin(account, loginIp)
|
go saveLogin(account, loginIp)
|
||||||
@@ -64,7 +65,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVerifyInfo, string, string) {
|
func useOtp(account *sysentity.Account, otpIssuer, accessToken string, refreshToken string) (*OtpVerifyInfo, string, string) {
|
||||||
biz.ErrIsNil(account.OtpSecretDecrypt())
|
biz.ErrIsNil(account.OtpSecretDecrypt())
|
||||||
otpSecret := account.OtpSecret
|
otpSecret := account.OtpSecret
|
||||||
// 修改状态为已注册
|
// 修改状态为已注册
|
||||||
@@ -83,13 +84,14 @@ func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVeri
|
|||||||
otpUrl = key.URL()
|
otpUrl = key.URL()
|
||||||
otpSecret = key.Secret()
|
otpSecret = key.Secret()
|
||||||
}
|
}
|
||||||
// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
|
// 缓存otpInfo, 只有双因素校验通过才可返回真正的token
|
||||||
otpInfo := &OtpVerifyInfo{
|
otpInfo := &OtpVerifyInfo{
|
||||||
AccountId: account.Id,
|
AccountId: account.Id,
|
||||||
Username: account.Username,
|
Username: account.Username,
|
||||||
OptStatus: otpStatus,
|
OptStatus: otpStatus,
|
||||||
OtpSecret: otpSecret,
|
OtpSecret: otpSecret,
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
}
|
}
|
||||||
cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
|
cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
|
||||||
return otpInfo, otpUrl, token
|
return otpInfo, otpUrl, token
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ func Init(router *gin.RouterGroup) {
|
|||||||
// 用户账号密码登录
|
// 用户账号密码登录
|
||||||
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSave("用户登录")).DontNeedToken(),
|
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSave("用户登录")).DontNeedToken(),
|
||||||
|
|
||||||
|
req.NewGet("/accounts/refreshToken", accountLogin.RefreshToken).DontNeedToken(),
|
||||||
|
|
||||||
// 用户退出登录
|
// 用户退出登录
|
||||||
req.NewPost("/accounts/logout", accountLogin.Logout),
|
req.NewPost("/accounts/logout", accountLogin.Logout),
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ type Team struct {
|
|||||||
model.Model
|
model.Model
|
||||||
entity.RelateTags // 标签信息
|
entity.RelateTags // 标签信息
|
||||||
|
|
||||||
Name string `json:"name"` // 名称
|
Name string `json:"name"` // 名称
|
||||||
Remark string `json:"remark"` // 备注说明
|
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||||
|
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||||
|
Remark string `json:"remark"` // 备注说明
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) GetRelateId() uint64 {
|
func (t *Team) GetRelateId() uint64 {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import "mayfly-go/pkg/model"
|
||||||
|
|
||||||
type SaveTeam struct {
|
type SaveTeam struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
Name string `json:"name" binding:"required"` // 名称
|
Name string `json:"name" binding:"required"` // 名称
|
||||||
Remark string `json:"remark"` // 备注说明
|
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||||
|
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||||
|
Remark string `json:"remark"` // 备注说明
|
||||||
|
|
||||||
CodePaths []string `json:"codePaths"` // 关联标签信息
|
CodePaths []string `json:"codePaths"` // 关联标签信息
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,12 @@ func (p *teamAppImpl) GetPageList(condition *entity.TeamQuery, pageParam *model.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *teamAppImpl) SaveTeam(ctx context.Context, saveParam *dto.SaveTeam) error {
|
func (p *teamAppImpl) SaveTeam(ctx context.Context, saveParam *dto.SaveTeam) error {
|
||||||
team := &entity.Team{Name: saveParam.Name, Remark: saveParam.Remark}
|
team := &entity.Team{
|
||||||
|
Name: saveParam.Name,
|
||||||
|
ValidityStartDate: saveParam.ValidityStartDate,
|
||||||
|
ValidityEndDate: saveParam.ValidityEndDate,
|
||||||
|
Remark: saveParam.Remark,
|
||||||
|
}
|
||||||
team.Id = saveParam.Id
|
team.Id = saveParam.Id
|
||||||
|
|
||||||
if team.Id == 0 {
|
if team.Id == 0 {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import "mayfly-go/pkg/model"
|
import (
|
||||||
|
"mayfly-go/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
// 团队信息
|
// 团队信息
|
||||||
type Team struct {
|
type Team struct {
|
||||||
model.Model
|
model.Model
|
||||||
|
|
||||||
Name string `json:"name"` // 名称
|
Name string `json:"name"` // 名称
|
||||||
Remark string `json:"remark"` // 备注说明
|
ValidityStartDate *model.JsonTime `json:"validityStartDate"` // 生效开始时间
|
||||||
|
ValidityEndDate *model.JsonTime `json:"validityEndDate"` // 生效结束时间
|
||||||
|
Remark string `json:"remark"` // 备注说明
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
const AccountTagsKey = "mayfly:tag:account:%d"
|
const AccountTagsKey = "mayfly:tag:account:%d"
|
||||||
|
|
||||||
func SaveAccountTagPaths(accountId uint64, tags []string) error {
|
func SaveAccountTagPaths(accountId uint64, tags []string) error {
|
||||||
return global_cache.Set(fmt.Sprintf(AccountTagsKey, accountId), tags, 30*time.Minute)
|
return global_cache.Set(fmt.Sprintf(AccountTagsKey, accountId), tags, 2*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccountTagPaths(accountId uint64) ([]string, error) {
|
func GetAccountTagPaths(accountId uint64) ([]string, error) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"mayfly-go/internal/tag/domain/entity"
|
"mayfly-go/internal/tag/domain/entity"
|
||||||
"mayfly-go/internal/tag/domain/repository"
|
"mayfly-go/internal/tag/domain/repository"
|
||||||
"mayfly-go/pkg/base"
|
"mayfly-go/pkg/base"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tagTreeRelateRepoImpl struct {
|
type tagTreeRelateRepoImpl struct {
|
||||||
@@ -43,12 +44,10 @@ func (tr *tagTreeRelateRepoImpl) SelectTagPathsByAccountId(accountId uint64) []s
|
|||||||
sql := `
|
sql := `
|
||||||
SELECT
|
SELECT
|
||||||
DISTINCT(t.code_path)
|
DISTINCT(t.code_path)
|
||||||
FROM
|
FROM t_tag_tree_relate t1
|
||||||
t_tag_tree_relate t1
|
JOIN t_team_member t2 ON t1.relate_id = t2.team_id
|
||||||
JOIN t_team_member t2 ON
|
JOIN t_team t3 ON t3.id = t2.team_id AND t3.validity_start_date < ? AND t3.validity_end_date > ?
|
||||||
t1.relate_id = t2.team_id
|
JOIN t_tag_tree t ON t.id = t1.tag_id
|
||||||
JOIN t_tag_tree t ON
|
|
||||||
t.id = t1.tag_id
|
|
||||||
WHERE
|
WHERE
|
||||||
t1.relate_type = ?
|
t1.relate_type = ?
|
||||||
AND t2.account_id = ?
|
AND t2.account_id = ?
|
||||||
@@ -58,7 +57,8 @@ WHERE
|
|||||||
ORDER BY
|
ORDER BY
|
||||||
t.code_path
|
t.code_path
|
||||||
`
|
`
|
||||||
tr.SelectBySql(sql, &res, entity.TagRelateTypeTeam, accountId)
|
now := time.Now()
|
||||||
|
tr.SelectBySql(sql, &res, now, now, entity.TagRelateTypeTeam, accountId)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import "fmt"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AppName = "mayfly-go"
|
AppName = "mayfly-go"
|
||||||
Version = "v1.8.3"
|
Version = "v1.8.4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAppInfo() string {
|
func GetAppInfo() string {
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Jwt struct {
|
type Jwt struct {
|
||||||
Key string `yaml:"key"`
|
Key string `yaml:"key"`
|
||||||
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
|
||||||
|
RefreshTokenExpireTime uint64 `yaml:"refresh-token-expire-time"` // 刷新token的过期时间,单位分钟
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Jwt) Default() {
|
func (j *Jwt) Default() {
|
||||||
@@ -22,6 +23,10 @@ func (j *Jwt) Default() {
|
|||||||
j.ExpireTime = 1440
|
j.ExpireTime = 1440
|
||||||
logx.Warnf("未配置jwt.expire-time, 默认值: %d", j.ExpireTime)
|
logx.Warnf("未配置jwt.expire-time, 默认值: %d", j.ExpireTime)
|
||||||
}
|
}
|
||||||
|
if j.RefreshTokenExpireTime == 0 {
|
||||||
|
j.RefreshTokenExpireTime = j.ExpireTime * 5
|
||||||
|
logx.Warnf("未配置jwt.refresh-token-expire-time, 默认值: %d", j.RefreshTokenExpireTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Jwt) Valid() {
|
func (j *Jwt) Valid() {
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ type BizError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Success BizError = NewBizCode(200, "success")
|
Success BizError = NewBizCode(200, "success")
|
||||||
BizErr BizError = NewBizCode(400, "biz error")
|
BizErr BizError = NewBizCode(400, "biz error")
|
||||||
ServerError BizError = NewBizCode(500, "server error")
|
ServerError BizError = NewBizCode(500, "server error")
|
||||||
PermissionErr BizError = NewBizCode(501, "token error")
|
PermissionErr BizError = NewBizCode(501, "token error")
|
||||||
|
AccessTokenInvalid BizError = NewBizCode(502, "access token invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
// 错误消息
|
// 错误消息
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ func (j JsonTime) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func (j *JsonTime) UnmarshalJSON(b []byte) error {
|
func (j *JsonTime) UnmarshalJSON(b []byte) error {
|
||||||
s := strings.ReplaceAll(string(b), "\"", "")
|
s := strings.ReplaceAll(string(b), "\"", "")
|
||||||
t, err := time.Parse(timex.DefaultDateTimeFormat, s)
|
// t, err := time.Parse(timex.DefaultDateTimeFormat, s)
|
||||||
|
t, err := time.ParseInLocation(timex.DefaultDateTimeFormat, s, time.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func PermissionHandler(rc *Ctx) error {
|
|||||||
}
|
}
|
||||||
userId, userName, err := ParseToken(tokenStr)
|
userId, userName, err := ParseToken(tokenStr)
|
||||||
if err != nil || userId == 0 {
|
if err != nil || userId == 0 {
|
||||||
return errorx.PermissionErr
|
return errorx.AccessTokenInvalid
|
||||||
}
|
}
|
||||||
// 权限不为nil,并且permission code不为空,则校验是否有权限code
|
// 权限不为nil,并且permission code不为空,则校验是否有权限code
|
||||||
if permission != nil && permission.Code != "" {
|
if permission != nil && permission.Code != "" {
|
||||||
|
|||||||
@@ -9,21 +9,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 创建用户token
|
// 创建用户token
|
||||||
func CreateToken(userId uint64, username string) (string, error) {
|
func CreateToken(userId uint64, username string) (accessToken string, refreshToken string, err error) {
|
||||||
|
jwtConf := config.Conf.Jwt
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
// 带权限创建令牌
|
// 带权限创建令牌
|
||||||
// 设置有效期,过期需要重新登录获取token
|
// 设置有效期,过期需要重新登录获取token
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
accessJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"id": userId,
|
"id": userId,
|
||||||
"username": username,
|
"username": username,
|
||||||
"exp": time.Now().Add(time.Minute * time.Duration(config.Conf.Jwt.ExpireTime)).Unix(),
|
"exp": now.Add(time.Minute * time.Duration(jwtConf.ExpireTime)).Unix(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// refresh token
|
||||||
|
refreshJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"id": userId,
|
||||||
|
"username": username,
|
||||||
|
"exp": now.Add(time.Minute * time.Duration(jwtConf.RefreshTokenExpireTime)).Unix(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// 使用自定义字符串加密 and get the complete encoded token as a string
|
// 使用自定义字符串加密 and get the complete encoded token as a string
|
||||||
tokenString, err := token.SignedString([]byte(config.Conf.Jwt.Key))
|
accessToken, err = accessJwt.SignedString([]byte(jwtConf.Key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return
|
||||||
}
|
}
|
||||||
return tokenString, nil
|
|
||||||
|
refreshToken, err = refreshJwt.SignedString([]byte(jwtConf.Key))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析token,并返回登录者账号信息
|
// 解析token,并返回登录者账号信息
|
||||||
|
|||||||
Binary file not shown.
@@ -951,6 +951,8 @@ DROP TABLE IF EXISTS `t_team`;
|
|||||||
CREATE TABLE `t_team` (
|
CREATE TABLE `t_team` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`name` varchar(36) NOT NULL COMMENT '名称',
|
`name` varchar(36) NOT NULL COMMENT '名称',
|
||||||
|
`validity_start_date` datetime DEFAULT NULL COMMENT '生效开始时间',
|
||||||
|
`validity_end_date` datetime DEFAULT NULL COMMENT '生效结束时间',
|
||||||
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
||||||
`create_time` datetime NOT NULL,
|
`create_time` datetime NOT NULL,
|
||||||
`creator_id` bigint(20) NOT NULL,
|
`creator_id` bigint(20) NOT NULL,
|
||||||
@@ -967,7 +969,7 @@ CREATE TABLE `t_team` (
|
|||||||
-- Records of t_team
|
-- Records of t_team
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO `t_team` VALUES (1, 'default_team', '默认团队', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
|
INSERT INTO `t_team` VALUES (1, 'default_team', '2024-05-01 00:00:00', '2050-05-01 00:00:00', '默认团队', '2022-10-26 20:04:36', 1, 'admin', '2022-10-26 20:04:36', 1, 'admin', 0, NULL);
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|||||||
4
server/resources/script/sql/v1.8/v1.8.4.sql
Normal file
4
server/resources/script/sql/v1.8/v1.8.4.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE t_team ADD validity_start_date DATETIME NULL COMMENT '生效开始时间';
|
||||||
|
ALTER TABLE t_team ADD validity_end_date DATETIME NULL COMMENT '生效结束时间';
|
||||||
|
|
||||||
|
UPDATE t_team SET validity_start_date = NOW(), validity_end_date = '2034-01-01 00:00:00'
|
||||||
Reference in New Issue
Block a user