mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-12-30 19:36:02 +08:00
【V3.5.0】1、【新增】轻量级定时任务 SmartJob;2、【新增】站内信;3、【新增】个人中心;4、【新增】岗位管理;5、【优化】部门员工管理
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
NODE_ENV=development
|
||||
VITE_APP_TITLE='SmartAdmin 开发环境(Dev)'
|
||||
VITE_APP_API_URL='http://127.0.0.1:1024'
|
||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
NODE_ENV=production
|
||||
VITE_APP_TITLE='SmartAdmin 测试环境(Test)'
|
||||
VITE_APP_API_URL='http://127.0.0.1:1024'
|
||||
VITE_APP_API_URL='http://smartadmin.dev.1024lab.net/api/'
|
||||
|
||||
@@ -11,15 +11,16 @@
|
||||
"scripts": {
|
||||
"localhost": "vite --mode localhost",
|
||||
"dev": "vite",
|
||||
"build:test": "vite build --mode test",
|
||||
"build:test": "vite build --base=/admin/ --mode test",
|
||||
"build:pre": "vite build --mode pre",
|
||||
"build:prod": "vite build --mode production"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^6.1.0",
|
||||
"@wangeditor/editor": "5.1.14",
|
||||
"@wangeditor/editor-for-vue": "5.1.12",
|
||||
"ant-design-vue": "4.2.0",
|
||||
"ant-design-vue": "4.2.1",
|
||||
"axios": "1.6.8",
|
||||
"clipboard": "2.0.11",
|
||||
"crypto-js": "4.1.1",
|
||||
@@ -37,14 +38,15 @@
|
||||
"sortablejs": "1.15.0",
|
||||
"ua-parser-js": "1.0.35",
|
||||
"v-viewer": "~1.6.4",
|
||||
"vue": "3.3.13",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4.3.0",
|
||||
"vue3-json-viewer": "2.2.2"
|
||||
"vue": "3.4.27",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.3.2",
|
||||
"vue3-json-viewer": "2.2.2",
|
||||
"vue3-tabs-chrome": "^0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vue/compiler-sfc": "3.4.21",
|
||||
"@vue/compiler-sfc": "3.4.27",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "~9.0.0",
|
||||
"eslint-plugin-prettier": "~5.0.0",
|
||||
@@ -58,7 +60,7 @@
|
||||
"stylelint-config-standard": "~25.0.0",
|
||||
"stylelint-order": "~5.0.0",
|
||||
"terser": "~5.29.2",
|
||||
"vite": "5.2.6",
|
||||
"vite": "5.2.12",
|
||||
"vue-eslint-parser": "~9.4.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* job api
|
||||
*
|
||||
* @Author: huke
|
||||
* @Date: 2024/06/25
|
||||
*/
|
||||
import { postRequest, getRequest } from '/src/lib/axios';
|
||||
|
||||
export const jobApi = {
|
||||
// 分页查询 @huke
|
||||
queryJob: (param) => {
|
||||
return postRequest('/support/job/query', param);
|
||||
},
|
||||
// 定时任务-查询详情 @huke
|
||||
queryJobInfo: (param) => {
|
||||
return getRequest(`/support/job/${param}`);
|
||||
},
|
||||
// 执行任务 @huke
|
||||
executeJob: (param) => {
|
||||
return postRequest('/support/job/execute', param);
|
||||
},
|
||||
// 定时任务-更新-任务信息 @huke
|
||||
updateJob: (param) => {
|
||||
return postRequest('/support/job/update', param);
|
||||
},
|
||||
// 定时任务-更新-开启状态 @huke
|
||||
updateJobEnabled: (param) => {
|
||||
return postRequest('/support/job/update/enabled', param);
|
||||
},
|
||||
// 定时任务-执行记录-分页查询 @huke
|
||||
queryJobLog: (param) => {
|
||||
return postRequest('/support/job/log/query', param);
|
||||
},
|
||||
};
|
||||
@@ -14,4 +14,8 @@ export const loginLogApi = {
|
||||
queryList: (param) => {
|
||||
return postRequest('/support/loginLog/page/query', param);
|
||||
},
|
||||
// 分页查询当前登录人信息 @author 善逸
|
||||
queryListLogin: (param) => {
|
||||
return postRequest('/support/loginLog/page/query/login', param);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { getRequest, postRequest } from '/src/lib/axios';
|
||||
|
||||
export const messageApi = {
|
||||
// 通知消息-分页查询
|
||||
queryMessage: (param) => {
|
||||
return postRequest('/support/message/queryMyMessage', param);
|
||||
},
|
||||
// 通知消息-查询未读消息数
|
||||
queryUnreadCount: () => {
|
||||
return getRequest('/support/message/getUnreadCount');
|
||||
},
|
||||
// 通知消息-标记已读
|
||||
updateReadFlag: (messageId) => {
|
||||
return getRequest(`/support/message/read/${messageId}`);
|
||||
},
|
||||
};
|
||||
@@ -18,4 +18,8 @@ export const operateLogApi = {
|
||||
detail: (id) => {
|
||||
return getRequest(`/support/operateLog/detail/${id}`);
|
||||
},
|
||||
// 分页查询当前登录人信息 @author 善逸
|
||||
queryListLogin: (param) => {
|
||||
return postRequest('/support/operateLog/page/query/login', param);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,6 +35,18 @@ export const employeeApi = {
|
||||
updateEmployee: (params) => {
|
||||
return postRequest('/employee/update', params);
|
||||
},
|
||||
/**
|
||||
* 更新登录人信息
|
||||
*/
|
||||
updateByLogin: (params) => {
|
||||
return postRequest('/employee/update/login', params);
|
||||
},
|
||||
/**
|
||||
* 更新登录人头像
|
||||
*/
|
||||
updateAvatar: (params) => {
|
||||
return postRequest('/employee/update/avatar', params);
|
||||
},
|
||||
/**
|
||||
* 删除员工
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 职务表 api 封装
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
*/
|
||||
import { postRequest, getRequest } from '/@/lib/axios';
|
||||
|
||||
export const positionApi = {
|
||||
|
||||
/**
|
||||
* 分页查询 @author kaiyun
|
||||
*/
|
||||
queryPage : (param) => {
|
||||
return postRequest('/position/queryPage', param);
|
||||
},
|
||||
|
||||
/**
|
||||
* 增加 @author kaiyun
|
||||
*/
|
||||
add: (param) => {
|
||||
return postRequest('/position/add', param);
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改 @author kaiyun
|
||||
*/
|
||||
update: (param) => {
|
||||
return postRequest('/position/update', param);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 删除 @author kaiyun
|
||||
*/
|
||||
delete: (id) => {
|
||||
return getRequest(`/position/delete/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除 @author kaiyun
|
||||
*/
|
||||
batchDelete: (idList) => {
|
||||
return postRequest('/position/batchDelete', idList);
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询列表 @author kaiyun
|
||||
*/
|
||||
queryList: () => {
|
||||
return getRequest('/position/queryList');
|
||||
},
|
||||
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
* 职位
|
||||
*
|
||||
* @Author: 开云
|
||||
* @Date: 2024-06-27 23:09:02
|
||||
* @Wechat: kaiyun
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model:value="selectValue"
|
||||
:style="`width: ${width}`"
|
||||
:placeholder="props.placeholder"
|
||||
:showSearch="true"
|
||||
:allowClear="true"
|
||||
:size="size"
|
||||
@change="onChange"
|
||||
>
|
||||
<a-select-option v-for="item in positionList" :key="item.positionId" :value="item.positionId">
|
||||
{{ item.positionName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { positionApi } from '/@/api/system/position-api.js';
|
||||
|
||||
// =========== 属性定义 和 事件方法暴露 =============
|
||||
|
||||
const props = defineProps({
|
||||
value: [Number, Array],
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
// =========== 查询数据 =============
|
||||
|
||||
//员工列表数据
|
||||
const positionList = ref([]);
|
||||
async function query() {
|
||||
try {
|
||||
let resp = await positionApi.queryList();
|
||||
positionList.value = resp.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
onMounted(query);
|
||||
|
||||
// =========== 选择 监听、事件 =============
|
||||
|
||||
const selectValue = ref(props.value);
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
selectValue.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
function onChange(value) {
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
}
|
||||
</script>
|
||||
@@ -24,12 +24,16 @@ export const appDefaultConfig = {
|
||||
borderRadius: 6,
|
||||
// 标签页
|
||||
pageTagFlag: true,
|
||||
// 标签页样式: default、 antd
|
||||
pageTagStyle: 'default',
|
||||
// 面包屑
|
||||
breadCrumbFlag: true,
|
||||
// 页脚
|
||||
footerFlag: true,
|
||||
// 帮助文档
|
||||
helpDocFlag: true,
|
||||
// 帮助文档默认展开
|
||||
helpDocExpandFlag: true,
|
||||
// 水印
|
||||
watermarkFlag: true,
|
||||
// 网站名称
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* @Description: file content
|
||||
* @Author: yandy
|
||||
* @Date: 2022-07-24 21:43:43
|
||||
* @LastEditors:
|
||||
* @LastEditTime: 2022-07-24 21:43:43
|
||||
*/
|
||||
export const MESSAGE_TYPE_ENUM = {
|
||||
MAIL: {
|
||||
value: 1,
|
||||
desc: '站内信'
|
||||
},
|
||||
ORDER: {
|
||||
value: 2,
|
||||
desc: '订单'
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const MESSAGE_RECEIVE_TYPE_ENUM = {
|
||||
EMPLOYEE: {
|
||||
value: 1,
|
||||
desc: '员工'
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
MESSAGE_TYPE_ENUM,
|
||||
MESSAGE_RECEIVE_TYPE_ENUM
|
||||
};
|
||||
@@ -17,8 +17,10 @@ import file from './support/file-const';
|
||||
import notice from './business/oa/notice-const';
|
||||
import loginLog from './support/login-log-const';
|
||||
import enterprise from './business/oa/enterprise-const';
|
||||
import message from './business/message/message-const';
|
||||
import codeGeneratorConst from './support/code-generator-const';
|
||||
import changeLogConst from './support/change-log-const';
|
||||
import jobConst from './support/job-const.js';
|
||||
|
||||
export default {
|
||||
FLAG_NUMBER_ENUM,
|
||||
@@ -33,6 +35,8 @@ export default {
|
||||
...file,
|
||||
...notice,
|
||||
...enterprise,
|
||||
...message,
|
||||
...codeGeneratorConst,
|
||||
...changeLogConst,
|
||||
...jobConst,
|
||||
};
|
||||
|
||||
@@ -21,3 +21,14 @@ export const LAYOUT_ENUM = {
|
||||
desc: '顶部',
|
||||
},
|
||||
};
|
||||
|
||||
export const PAGE_TAG_ENUM = {
|
||||
DEFAULT: {
|
||||
value: 'default',
|
||||
desc: '默认',
|
||||
},
|
||||
ANTD: {
|
||||
value: 'antd',
|
||||
desc: 'Ant Design',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* job 常量
|
||||
* @type {{CRON: {value: number, desc: string}, FIXED_DELAY: {value: number, desc: string}}}
|
||||
*/
|
||||
export const TRIGGER_TYPE_ENUM = {
|
||||
CRON: {
|
||||
value: 'cron',
|
||||
desc: 'cron表达式',
|
||||
},
|
||||
FIXED_DELAY: {
|
||||
value: 'fixed_delay',
|
||||
desc: '固定间隔',
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
TRIGGER_TYPE_ENUM,
|
||||
};
|
||||
@@ -40,6 +40,7 @@ export const TABLE_ID_CONST = {
|
||||
SYSTEM: {
|
||||
EMPLOYEE: systemInitTableId + 1, //员工
|
||||
MENU: systemInitTableId + 2, //菜单
|
||||
POSITION:systemInitTableId + 3, //职位
|
||||
},
|
||||
/**
|
||||
* 支撑
|
||||
@@ -53,5 +54,7 @@ export const TABLE_ID_CONST = {
|
||||
LOGIN_LOG: supportInitTableId + 6, //登录日志
|
||||
RELOAD: supportInitTableId + 7, //reload
|
||||
HELP_DOC: supportInitTableId + 8, //帮助文档
|
||||
JOB: supportInitTableId + 9, //Job
|
||||
JOB_LOG: supportInitTableId + 10, //JobLog
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,7 +23,9 @@ export default {
|
||||
'setting.compact': 'Page Compact',
|
||||
'setting.bread': 'Show Bread',
|
||||
'setting.pagetag': 'Show PageTag',
|
||||
'setting.pagetag.style': 'PageTag Style',
|
||||
'setting.footer': 'Show Footer',
|
||||
'setting.helpdoc': 'Show Helpdoc',
|
||||
'setting.helpdoc.expand': 'Helpdoc Expand',
|
||||
'setting.watermark': 'Show Watermark',
|
||||
};
|
||||
|
||||
@@ -23,7 +23,9 @@ export default {
|
||||
'setting.page.width': '页面宽度',
|
||||
'setting.bread': '面包屑',
|
||||
'setting.pagetag': '标签页',
|
||||
'setting.pagetag.style': '标签页样式',
|
||||
'setting.footer': '页脚',
|
||||
'setting.helpdoc': '帮助文档',
|
||||
'setting.helpdoc.expand': '帮助文档展开',
|
||||
'setting.watermark': '水印',
|
||||
};
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<!--
|
||||
* 头像
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:02:01
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:02:01
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-dropdown class="header-trigger">
|
||||
<div class="wrapper">
|
||||
<a-avatar style="margin: 0 5px" :size="24" id="smartAdminAvatar">
|
||||
<img class="avatar-image" :src="avatar" v-if="avatar" />
|
||||
<a-avatar v-else style="margin: 0 5px" :size="20" id="smartAdminAvatar">
|
||||
{{ avatarName }}
|
||||
</a-avatar>
|
||||
<span class="name">{{ actualName }}</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu :class="['avatar-menu']">
|
||||
<a-menu-item @click="showUpdatePwdModal">
|
||||
<a-menu-item @click="toAccount()">
|
||||
<span>个人中心</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="toAccount(ACCOUNT_MENU.PASSWORD.menuId)">
|
||||
<span>修改密码</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="onLogout">
|
||||
@@ -36,6 +40,8 @@
|
||||
import { localClear } from '/@/utils/local-util';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import HeaderResetPassword from './header-reset-password-modal/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu.js';
|
||||
|
||||
// 头像背景颜色
|
||||
const AVATAR_BACKGROUND_COLOR_ARRAY = ['#87d068', '#00B853', '#f56a00', '#1890ff'];
|
||||
@@ -53,6 +59,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ 个人中心 ------------------------
|
||||
const router = useRouter();
|
||||
function toAccount(menuId) {
|
||||
router.push({
|
||||
path: '/account',
|
||||
query: { menuId },
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------ 修改密码 ------------------------
|
||||
const resetPasswordRef = ref();
|
||||
|
||||
@@ -63,7 +78,9 @@
|
||||
// ------------------------ 以下是 头像和姓名 相关 ------------------------
|
||||
|
||||
const avatarName = ref('');
|
||||
const avatar = computed(() => useUserStore().avatar);
|
||||
const actualName = computed(() => useUserStore().actualName);
|
||||
|
||||
// 更新头像信息
|
||||
function updateAvatar() {
|
||||
if (useUserStore().actualName) {
|
||||
@@ -98,7 +115,15 @@
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.header-trigger {
|
||||
height: @header-user-height;
|
||||
line-height: @header-user-height;
|
||||
|
||||
@@ -1,77 +1,65 @@
|
||||
<!--
|
||||
* 消息通知
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:17:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:17:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
|
||||
<template>
|
||||
<a-dropdown trigger="click" v-model:open="show">
|
||||
<div @click="fetchMessage">
|
||||
<a-badge count="12">
|
||||
<a-button type="text" @click="queryMessage" style="padding: 4px 5px">
|
||||
<a-badge :count="unreadMessageCount">
|
||||
<div style="width: 26px; height: 26px">
|
||||
<BellOutlined :style="{ fontSize: '18px' }" />
|
||||
<BellOutlined :style="{ fontSize: '16px' }" />
|
||||
</div>
|
||||
</a-badge>
|
||||
</div>
|
||||
</a-button>
|
||||
|
||||
<template #overlay>
|
||||
<div>
|
||||
<a-card class="message-container" :bodyStyle="{ padding: 0 }">
|
||||
<a-spin :spinning="loading">
|
||||
<a-tabs class="dropdown-tabs" centered :tabBarStyle="{ textAlign: 'center' }" style="width: 300px">
|
||||
<a-tab-pane tab="通知" key="1">
|
||||
<a-tab-pane tab="未读消息" key="message">
|
||||
<a-list class="tab-pane" size="small">
|
||||
<a-list-item>
|
||||
<a-list-item-meta description="7天前">
|
||||
<a-list-item v-for="item in messageList" :key="item.messageId">
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<a href="#">今天洛阳天气39°c,洛阳变各阳...</a>
|
||||
<div class="title">
|
||||
<a @click="gotoMessage">{{ item.title }}</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar src="https://zhuoda.vip/images/blog/profile-128-128.jpg" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta description="7天前">
|
||||
<template #title>
|
||||
<a href="#">六月的雨 就是无情的你...</a>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar src="https://zhuoda.vip/images/blog/profile-128-128.jpg" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta description="7天前">
|
||||
<template #title>
|
||||
<a href="#">今年河南天气炎热,河南变可南...</a>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar src="https://zhuoda.vip/images/blog/profile-128-128.jpg" />
|
||||
<template #description>
|
||||
<span> {{ timeago(item.createTime) }}</span>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="消息" key="2">
|
||||
<a-list class="tab-pane" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="待办" key="3">
|
||||
<a-tab-pane tab="待办工作" key="todo">
|
||||
<a-list class="tab-pane" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { BellOutlined } from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { theme } from 'ant-design-vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
|
||||
defineExpose({ showMessage });
|
||||
|
||||
@@ -82,19 +70,97 @@
|
||||
const loading = ref(false);
|
||||
const show = ref(false);
|
||||
|
||||
const fetchMessage = () => {
|
||||
if (loading.value) {
|
||||
// ------------------------- 查询消息 -------------------------
|
||||
|
||||
// 未读消息
|
||||
const unreadMessageCount = computed(() => {
|
||||
return useUserStore().unreadMessageCount;
|
||||
});
|
||||
|
||||
// 消息列表
|
||||
const messageList = ref([]);
|
||||
|
||||
// 查询我的未读消息
|
||||
async function queryMessage() {
|
||||
try {
|
||||
loading.value = true;
|
||||
let responseModel = await messageApi.queryMessage({
|
||||
pageNum: 1,
|
||||
pageSize: 3,
|
||||
readFlag: false,
|
||||
});
|
||||
messageList.value = responseModel.data.list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 700);
|
||||
};
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
function gotoMessage() {
|
||||
show.value = false;
|
||||
router.push({ path: '/account', query: { menuId: 'message' } });
|
||||
}
|
||||
|
||||
// ------------------------- 时间计算 -------------------------
|
||||
function timeago(dateStr) {
|
||||
let dateTimeStamp = dayjs(dateStr).toDate().getTime();
|
||||
let result = '';
|
||||
let minute = 1000 * 60; //把分,时,天,周,半个月,一个月用毫秒表示
|
||||
let hour = minute * 60;
|
||||
let day = hour * 24;
|
||||
let week = day * 7;
|
||||
let month = day * 30;
|
||||
let now = new Date().getTime(); //获取当前时间毫秒
|
||||
let diffValue = now - dateTimeStamp; //时间差
|
||||
|
||||
if (diffValue < 0) {
|
||||
return '刚刚';
|
||||
}
|
||||
let minC = diffValue / minute; //计算时间差的分,时,天,周,月
|
||||
let hourC = diffValue / hour;
|
||||
let dayC = diffValue / day;
|
||||
let weekC = diffValue / week;
|
||||
let monthC = diffValue / month;
|
||||
if (monthC >= 1 && monthC <= 3) {
|
||||
result = ' ' + parseInt(monthC) + '月前';
|
||||
} else if (weekC >= 1 && weekC <= 3) {
|
||||
result = ' ' + parseInt(weekC) + '周前';
|
||||
} else if (dayC >= 1 && dayC <= 6) {
|
||||
result = ' ' + parseInt(dayC) + '天前';
|
||||
} else if (hourC >= 1 && hourC <= 23) {
|
||||
result = ' ' + parseInt(hourC) + '小时前';
|
||||
} else if (minC >= 1 && minC <= 59) {
|
||||
result = ' ' + parseInt(minC) + '分钟前';
|
||||
} else if (diffValue >= 0 && diffValue <= minute) {
|
||||
result = '刚刚';
|
||||
} else {
|
||||
let datetime = new Date();
|
||||
datetime.setTime(dateTimeStamp);
|
||||
let year = datetime.getFullYear();
|
||||
let month = datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
|
||||
let date = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate();
|
||||
result = year + '-' + month + '-' + date;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="less" scoped>
|
||||
@smart-page-tag-operate-width: 40px;
|
||||
@color-primary: v-bind('token.colorPrimary');
|
||||
|
||||
.message-icon-div {
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
width: 42px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.message-icon-div:hover {
|
||||
background: @hover-bg-color !important;
|
||||
}
|
||||
|
||||
.header-notice {
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
@@ -108,6 +174,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
border: #eeeeee solid 1px;
|
||||
}
|
||||
|
||||
.dropdown-tabs {
|
||||
background-color: @base-bg-color;
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -11,8 +11,13 @@
|
||||
<template>
|
||||
<a-drawer :title="$t('setting.title')" placement="right" :open="visible" @close="close">
|
||||
<a-form layout="horizontal" :label-col="{ span: 8 }">
|
||||
<a-form-item label="语言/Language">
|
||||
<a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
|
||||
<a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.color')">
|
||||
<div style="display: flex; align-items: center">
|
||||
<div class="color-container">
|
||||
<template v-for="(item, index) in themeColors">
|
||||
<div v-if="index === formState.colorIndex" class="color">
|
||||
<CheckSquareFilled :style="{ color: item.primaryColor, fontSize: '22px' }" />
|
||||
@@ -38,6 +43,14 @@
|
||||
<a-form-item :label="$t('setting.border.radius')">
|
||||
<a-slider v-model:value="formState.borderRadius" :min="0" :max="6" @change="changeBorderRadius" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.menu.width')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
||||
<a-input-number @change="changeSideMenuWidth" v-model:value="formState.sideMenuWidth" :min="1" />
|
||||
像素(px)
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.page.width')" v-if="formState.layout === LAYOUT_ENUM.TOP.value">
|
||||
<a-input @change="changePageWidth" v-model:value="formState.pageWidth" />
|
||||
像素(px)或者 百分比
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.compact')">
|
||||
<a-radio-group v-model:value="formState.compactFlag" button-style="solid" @change="changeCompactFlag">
|
||||
<a-radio-button :value="false">默认</a-radio-button>
|
||||
@@ -57,35 +70,39 @@
|
||||
<a-radio-button value="light">Light</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.menu.width')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
|
||||
<a-input-number @change="changeSideMenuWidth" v-model:value="formState.sideMenuWidth" :min="1" />
|
||||
像素(px)
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.page.width')" v-if="formState.layout === LAYOUT_ENUM.TOP.value">
|
||||
<a-input @change="changePageWidth" v-model:value="formState.pageWidth" />
|
||||
像素(px)或者 百分比
|
||||
</a-form-item>
|
||||
<a-form-item label="语言/Language">
|
||||
<a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
|
||||
<a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.bread')">
|
||||
<a-switch @change="changeBreadCrumbFlag" v-model:checked="formState.breadCrumbFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
<a-form-item :label="$t('setting.pagetag.style')">
|
||||
<a-radio-group v-model:value="formState.pageTagStyle" button-style="solid" @change="changePageTagStyle">
|
||||
<a-radio-button value="default">默认</a-radio-button>
|
||||
<a-radio-button value="antd">Ant Design</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.pagetag')">
|
||||
<a-switch @change="changePageTagFlag" v-model:checked="formState.pageTagFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.bread')">
|
||||
<a-switch @change="changeBreadCrumbFlag" v-model:checked="formState.breadCrumbFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.footer')">
|
||||
<a-switch @change="changeFooterFlag" v-model:checked="formState.footerFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.helpdoc')">
|
||||
<a-switch @change="changeHelpDocFlag" v-model:checked="formState.helpDocFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.watermark')">
|
||||
<a-switch @change="changeWatermarkFlag" v-model:checked="formState.watermarkFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.helpdoc')">
|
||||
<a-switch @change="changeHelpDocFlag" v-model:checked="formState.helpDocFlag" checked-children="显示" un-checked-children="隐藏" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('setting.helpdoc.expand')" v-if="formState.helpDocFlag">
|
||||
<a-switch
|
||||
@change="changeHelpDocExpandFlag"
|
||||
v-model:checked="formState.helpDocExpandFlag"
|
||||
checked-children="默认展开"
|
||||
un-checked-children="默认不展开"
|
||||
/>
|
||||
</a-form-item>
|
||||
<br />
|
||||
<br />
|
||||
</a-form>
|
||||
|
||||
<div class="footer">
|
||||
<a-button style="margin-right: 8px" type="primary" @click="copy">复制配置信息</a-button>
|
||||
<a-button type="block" danger @click="reset">恢复默认配置 </a-button>
|
||||
@@ -176,12 +193,16 @@
|
||||
borderRadius: appConfigStore.borderRadius,
|
||||
// 标签页
|
||||
pageTagFlag: appConfigStore.pageTagFlag,
|
||||
// 标签页 样式
|
||||
pageTagStyle: appConfigStore.pageTagStyle,
|
||||
// 面包屑
|
||||
breadCrumbFlag: appConfigStore.breadCrumbFlag,
|
||||
// 页脚
|
||||
footerFlag: appConfigStore.footerFlag,
|
||||
// 帮助文档
|
||||
helpDocFlag: appConfigStore.helpDocFlag,
|
||||
// 帮助文档 默认展开
|
||||
helpDocExpandFlag: appConfigStore.helpDocExpandFlag,
|
||||
// 水印
|
||||
watermarkFlag: appConfigStore.watermarkFlag,
|
||||
};
|
||||
@@ -250,6 +271,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
function changePageTagStyle(e) {
|
||||
appConfigStore.$patch({
|
||||
pageTagStyle: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
function changeFooterFlag(e) {
|
||||
appConfigStore.$patch({
|
||||
footerFlag: e,
|
||||
@@ -262,6 +289,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
function changeHelpDocExpandFlag(e) {
|
||||
appConfigStore.$patch({
|
||||
helpDocExpandFlag: e,
|
||||
});
|
||||
}
|
||||
|
||||
function changeWatermarkFlag(e) {
|
||||
appConfigStore.$patch({
|
||||
watermarkFlag: e,
|
||||
@@ -281,9 +314,14 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.color-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.color {
|
||||
margin-left: 8px;
|
||||
display: inline;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 头部一整行
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:18:20
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:18:20
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-space :size="10">
|
||||
@@ -34,9 +34,9 @@
|
||||
<HeaderAvatar />
|
||||
</div>
|
||||
<!---帮助文档--->
|
||||
<div class="user-space-item" @click="showHelpDoc" v-if="!showHelpDocFlag">
|
||||
<div class="user-space-item" @click="showHelpDoc" v-if="showHelpDocFlag">
|
||||
<span>帮助文档</span>
|
||||
<DoubleLeftOutlined v-if="!showHelpDocFlag" />
|
||||
<DoubleLeftOutlined v-if="!helpDocExpandFlag" />
|
||||
</div>
|
||||
|
||||
<HeaderSetting ref="headerSetting" />
|
||||
@@ -72,6 +72,10 @@
|
||||
return useAppConfigStore().helpDocFlag;
|
||||
});
|
||||
|
||||
const helpDocExpandFlag = computed(() => {
|
||||
return useAppConfigStore().helpDocExpandFlag;
|
||||
});
|
||||
|
||||
//搜索
|
||||
function search() {
|
||||
window.open('https://1024lab.net');
|
||||
@@ -100,7 +104,7 @@
|
||||
|
||||
.user-space-item:hover {
|
||||
color: v-bind('token.colorPrimary');
|
||||
background: @hover-bg-color;
|
||||
background-color: @hover-bg-color !important;
|
||||
}
|
||||
|
||||
.setting {
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<!--
|
||||
* 使用ant design <a-tabs> 组件
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:29:12
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<!-- 标签页,共两部分:1、标签 ;2、标签操作区 -->
|
||||
<a-row style="border-bottom: 1px solid #eeeeee; position: relative" v-show="pageTagFlag">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<div class="smart-page-tag">
|
||||
<a-tabs style="width: 100%" type="card" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab">
|
||||
<a-tab-pane v-for="item in tagNav" :key="item.menuName">
|
||||
<template #tab>
|
||||
<span>
|
||||
{{ item.menuTitle }}
|
||||
<close-outlined @click.stop="closeTag(item, false)" v-if="item.menuName !== HOME_PAGE_NAME" class="smart-page-tag-close" />
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-dropdown>
|
||||
<!--标签页操作区-->
|
||||
<div class="smart-page-tag-operate">
|
||||
<div class="smart-page-tag-operate-icon">
|
||||
<AppstoreOutlined />
|
||||
</div>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { AppstoreOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { theme } from 'ant-design-vue';
|
||||
|
||||
//标签页 是否显示
|
||||
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const mode = ref('top');
|
||||
const tagNav = computed(() => useUserStore().getTagNav || []);
|
||||
const selectedKey = ref(route.name);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(newValue, oldValue) => {
|
||||
selectedKey.value = newValue;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
//选择某个标签页
|
||||
function selectTab(name) {
|
||||
if (selectedKey.value === name) {
|
||||
return;
|
||||
}
|
||||
// 寻找tag
|
||||
let tag = tagNav.value.find((e) => e.menuName === name);
|
||||
if (!tag) {
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
return;
|
||||
}
|
||||
// router.push({ name, query: Object.assign({ _keepAlive: 1 }, tag.menuQuery) });
|
||||
router.push({ name, query: tag.menuQuery });
|
||||
}
|
||||
|
||||
//通过菜单关闭
|
||||
function closeByMenu(closeAll) {
|
||||
let find = tagNav.value.find((e) => e.menuName === selectedKey.value);
|
||||
if (!find || closeAll) {
|
||||
closeTag(null, true);
|
||||
} else {
|
||||
closeTag(find, true);
|
||||
}
|
||||
}
|
||||
|
||||
//直接关闭
|
||||
function closeTag(item, closeAll) {
|
||||
// 关闭单个tag
|
||||
if (item && !closeAll) {
|
||||
let goName = HOME_PAGE_NAME;
|
||||
let goQuery = undefined;
|
||||
if (item.fromMenuName && item.fromMenuName !== item.menuName && tagNav.value.some((e) => e.menuName === item.fromMenuName)) {
|
||||
goName = item.fromMenuName;
|
||||
goQuery = item.fromMenuQuery;
|
||||
} else {
|
||||
// 查询左侧tag
|
||||
let index = tagNav.value.findIndex((e) => e.menuName === item.menuName);
|
||||
if (index > 0) {
|
||||
// 查询左侧tag
|
||||
let leftTagNav = tagNav.value[index - 1];
|
||||
goName = leftTagNav.menuName;
|
||||
goQuery = leftTagNav.menuQuery;
|
||||
}
|
||||
}
|
||||
// router.push({ name: goName, query: Object.assign({ _keepAlive: 1 }, goQuery) });
|
||||
router.push({ name: goName, query: goQuery });
|
||||
} else if (!item && closeAll) {
|
||||
// 关闭所有tag
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
}
|
||||
// 关闭其他tag不做处理 直接调用closeTagNav
|
||||
useUserStore().closeTagNav(item ? item.menuName : null, closeAll);
|
||||
}
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
const borderRadius = computed(() => {
|
||||
return token.value.borderRadius + 'px';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@smart-page-tag-operate-width: 40px;
|
||||
@color-primary: v-bind('token.colorPrimary');
|
||||
|
||||
.smart-page-tag-operate {
|
||||
width: @smart-page-tag-operate-width;
|
||||
height: @smart-page-tag-operate-width;
|
||||
background-color: #ffffff;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: @smart-page-tag-operate-width;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
|
||||
.smart-page-tag-operate-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 1s;
|
||||
transform-origin: 10px 20px;
|
||||
}
|
||||
|
||||
.smart-page-tag-operate-icon:hover {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.smart-page-tag-operate:hover {
|
||||
color: @color-primary;
|
||||
}
|
||||
|
||||
.smart-page-tag {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: @page-tag-height;
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
user-select: none;
|
||||
background: #fff;
|
||||
width: calc(100% - @smart-page-tag-operate-width);
|
||||
|
||||
.smart-page-tag-close {
|
||||
margin-left: 5px;
|
||||
font-size: 10px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/** 覆盖 ant design vue的 tabs 样式,变小一点 **/
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav::before) {
|
||||
border-bottom: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab) {
|
||||
padding: 5px 8px 3px 15px;
|
||||
margin: 8px 0 0 5px;
|
||||
min-width: 60px;
|
||||
height: 32px;
|
||||
border-radius: v-bind(borderRadius) v-bind(borderRadius) 0 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab-active) {
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
:deep(.ant-tabs-nav .ant-tabs-tab:hover) {
|
||||
background-color: white;
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<!--
|
||||
* 标签页
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:29:12
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<!-- 标签页,共两部分:1、标签 ;2、标签操作区 -->
|
||||
<a-row style="border-bottom: 1px solid #eeeeee; position: relative" v-show="pageTagFlag">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<div class="smart-page-tag">
|
||||
<a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab">
|
||||
<a-tab-pane v-for="item in tagNav" :key="item.menuName">
|
||||
<template #tab>
|
||||
<span>
|
||||
{{ item.menuTitle }}
|
||||
<close-outlined @click.stop="closeTag(item, false)" v-if="item.menuName !== HOME_PAGE_NAME" class="smart-page-tag-close" />
|
||||
<home-outlined style="font-size: 12px" v-if="item.menuName === HOME_PAGE_NAME" class="smart-page-tag-close" />
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-dropdown>
|
||||
<!--标签页操作区-->
|
||||
<div class="smart-page-tag-operate">
|
||||
<div class="smart-page-tag-operate-icon">
|
||||
<AppstoreOutlined />
|
||||
</div>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { AppstoreOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { theme } from 'ant-design-vue';
|
||||
|
||||
//标签页 是否显示
|
||||
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const mode = ref('top');
|
||||
const tagNav = computed(() => useUserStore().getTagNav || []);
|
||||
const selectedKey = ref(route.name);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(newValue, oldValue) => {
|
||||
selectedKey.value = newValue;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
//选择某个标签页
|
||||
function selectTab(name) {
|
||||
if (selectedKey.value === name) {
|
||||
return;
|
||||
}
|
||||
// 寻找tag
|
||||
let tag = tagNav.value.find((e) => e.menuName === name);
|
||||
if (!tag) {
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
return;
|
||||
}
|
||||
// router.push({ name, query: Object.assign({ _keepAlive: 1 }, tag.menuQuery) });
|
||||
router.push({ name, query: tag.menuQuery });
|
||||
}
|
||||
|
||||
//通过菜单关闭
|
||||
function closeByMenu(closeAll) {
|
||||
let find = tagNav.value.find((e) => e.menuName === selectedKey.value);
|
||||
if (!find || closeAll) {
|
||||
closeTag(null, true);
|
||||
} else {
|
||||
closeTag(find, true);
|
||||
}
|
||||
}
|
||||
|
||||
//直接关闭
|
||||
function closeTag(item, closeAll) {
|
||||
// 关闭单个tag
|
||||
if (item && !closeAll) {
|
||||
let goName = HOME_PAGE_NAME;
|
||||
let goQuery = undefined;
|
||||
if (item.fromMenuName && item.fromMenuName !== item.menuName && tagNav.value.some((e) => e.menuName === item.fromMenuName)) {
|
||||
goName = item.fromMenuName;
|
||||
goQuery = item.fromMenuQuery;
|
||||
} else {
|
||||
// 查询左侧tag
|
||||
let index = tagNav.value.findIndex((e) => e.menuName === item.menuName);
|
||||
if (index > 0) {
|
||||
// 查询左侧tag
|
||||
let leftTagNav = tagNav.value[index - 1];
|
||||
goName = leftTagNav.menuName;
|
||||
goQuery = leftTagNav.menuQuery;
|
||||
}
|
||||
}
|
||||
// router.push({ name: goName, query: Object.assign({ _keepAlive: 1 }, goQuery) });
|
||||
router.push({ name: goName, query: goQuery });
|
||||
} else if (!item && closeAll) {
|
||||
// 关闭所有tag
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
}
|
||||
// 关闭其他tag不做处理 直接调用closeTagNav
|
||||
useUserStore().closeTagNav(item ? item.menuName : null, closeAll);
|
||||
}
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
const borderRadius = token.value.borderRadius + 'px';
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@smart-page-tag-operate-width: 40px;
|
||||
@color-primary: v-bind('token.colorPrimary');
|
||||
|
||||
.smart-page-tag-operate {
|
||||
width: @smart-page-tag-operate-width;
|
||||
height: @smart-page-tag-operate-width;
|
||||
background-color: #ffffff;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: @smart-page-tag-operate-width;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
|
||||
.smart-page-tag-operate-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 1s;
|
||||
transform-origin: 10px 20px;
|
||||
}
|
||||
|
||||
.smart-page-tag-operate-icon:hover {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.smart-page-tag-operate:hover {
|
||||
color: @color-primary;
|
||||
}
|
||||
|
||||
.smart-page-tag {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: @page-tag-height;
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
user-select: none;
|
||||
background: #fff;
|
||||
width: calc(100% - @smart-page-tag-operate-width);
|
||||
|
||||
.smart-page-tag-close {
|
||||
margin-left: 5px;
|
||||
font-size: 10px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/** 覆盖 ant design vue的 tabs 样式,变小一点 **/
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 0;
|
||||
padding: 0 0 2px 0;
|
||||
max-height: 32px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav::before) {
|
||||
border-bottom: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab) {
|
||||
padding: 5px 8px 3px 20px;
|
||||
border-radius: v-bind(borderRadius);
|
||||
margin: 0 0 0 5px !important;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab-active) {
|
||||
background-color: #eeeeee;
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
:deep(.ant-tabs-nav .ant-tabs-tab:hover) {
|
||||
background-color: #eeeeee;
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,221 +1,23 @@
|
||||
<!--
|
||||
* 标签页
|
||||
* 标签页 入口,支持三种模式:默认、a-tabs, chrome-tabs
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:29:12
|
||||
* @Date: 2024-06-12 20:55:04
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<!-- 标签页,共两部分:1、标签 ;2、标签操作区 -->
|
||||
<a-row style="border-bottom: 1px solid #eeeeee; position: relative" v-show="pageTagFlag">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<div class="smart-page-tag">
|
||||
<a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab">
|
||||
<a-tab-pane v-for="item in tagNav" :key="item.menuName">
|
||||
<template #tab>
|
||||
<span>
|
||||
{{ item.menuTitle }}
|
||||
<close-outlined @click.stop="closeTag(item, false)" v-if="item.menuName !== HOME_PAGE_NAME" class="smart-page-tag-close" />
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template #rightExtra>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-dropdown>
|
||||
<!--标签页操作区-->
|
||||
<div class="smart-page-tag-operate">
|
||||
<div class="smart-page-tag-operate-icon">
|
||||
<AppstoreOutlined />
|
||||
</div>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item @click="closeByMenu(false)">关闭其他</a-menu-item>
|
||||
<a-menu-item @click="closeByMenu(true)">关闭所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-row>
|
||||
<DefaultTab v-if="pageTagStyle === PAGE_TAG_ENUM.DEFAULT.value" />
|
||||
<AntdTab v-if="pageTagStyle === PAGE_TAG_ENUM.ANTD.value" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { AppstoreOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { computed } from 'vue';
|
||||
import { useAppConfigStore } from '/@/store/modules/system/app-config';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import { theme } from 'ant-design-vue';
|
||||
import DefaultTab from './components/default-tab.vue';
|
||||
import AntdTab from './components/antd-tab.vue';
|
||||
import { PAGE_TAG_ENUM } from '/@/constants/layout-const.js';
|
||||
|
||||
//标签页 是否显示
|
||||
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const mode = ref('top');
|
||||
const tagNav = computed(() => useUserStore().getTagNav || []);
|
||||
const selectedKey = ref(route.name);
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(newValue, oldValue) => {
|
||||
selectedKey.value = newValue;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
//选择某个标签页
|
||||
function selectTab(name) {
|
||||
if (selectedKey.value === name) {
|
||||
return;
|
||||
}
|
||||
// 寻找tag
|
||||
let tag = tagNav.value.find((e) => e.menuName === name);
|
||||
if (!tag) {
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
return;
|
||||
}
|
||||
// router.push({ name, query: Object.assign({ _keepAlive: 1 }, tag.menuQuery) });
|
||||
router.push({ name, query: tag.menuQuery });
|
||||
}
|
||||
|
||||
//通过菜单关闭
|
||||
function closeByMenu(closeAll) {
|
||||
let find = tagNav.value.find((e) => e.menuName === selectedKey.value);
|
||||
if (!find || closeAll) {
|
||||
closeTag(null, true);
|
||||
} else {
|
||||
closeTag(find, true);
|
||||
}
|
||||
}
|
||||
|
||||
//直接关闭
|
||||
function closeTag(item, closeAll) {
|
||||
// 关闭单个tag
|
||||
if (item && !closeAll) {
|
||||
let goName = HOME_PAGE_NAME;
|
||||
let goQuery = undefined;
|
||||
if (item.fromMenuName && tagNav.value.some((e) => e.menuName === item.fromMenuName)) {
|
||||
goName = item.fromMenuName;
|
||||
goQuery = item.fromMenuQuery;
|
||||
} else {
|
||||
// 查询左侧tag
|
||||
let index = tagNav.value.findIndex((e) => e.menuName === item.menuName);
|
||||
if (index > 0) {
|
||||
// 查询左侧tag
|
||||
let leftTagNav = tagNav.value[index - 1];
|
||||
goName = leftTagNav.menuName;
|
||||
goQuery = leftTagNav.menuQuery;
|
||||
}
|
||||
}
|
||||
// router.push({ name: goName, query: Object.assign({ _keepAlive: 1 }, goQuery) });
|
||||
router.push({ name: goName, query: goQuery });
|
||||
} else if (!item && closeAll) {
|
||||
// 关闭所有tag
|
||||
router.push({ name: HOME_PAGE_NAME });
|
||||
}
|
||||
// 关闭其他tag不做处理 直接调用closeTagNav
|
||||
useUserStore().closeTagNav(item ? item.menuName : null, closeAll);
|
||||
}
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
const borderRadius = computed(() => {
|
||||
return token.value.borderRadius + 'px';
|
||||
});
|
||||
const pageTagStyle = computed(() => useAppConfigStore().$state.pageTagStyle);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@smart-page-tag-operate-width: 40px;
|
||||
@color-primary: v-bind('token.colorPrimary');
|
||||
|
||||
.smart-page-tag-operate {
|
||||
width: @smart-page-tag-operate-width;
|
||||
height: @smart-page-tag-operate-width;
|
||||
background-color: #ffffff;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: @smart-page-tag-operate-width;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
|
||||
.smart-page-tag-operate-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 1s;
|
||||
transform-origin: 10px 20px;
|
||||
}
|
||||
|
||||
.smart-page-tag-operate-icon:hover {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.smart-page-tag-operate:hover {
|
||||
color: @color-primary;
|
||||
}
|
||||
|
||||
.smart-page-tag {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: @page-tag-height;
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
user-select: none;
|
||||
background: #fff;
|
||||
width: calc(100% - @smart-page-tag-operate-width);
|
||||
|
||||
.smart-page-tag-close {
|
||||
margin-left: 5px;
|
||||
font-size: 10px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/** 覆盖 ant design vue的 tabs 样式,变小一点 **/
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 0;
|
||||
padding: 0 0 2px 0;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav::before) {
|
||||
border-bottom: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-small > .ant-tabs-nav .ant-tabs-tab) {
|
||||
padding: 5px 8px 3px 10px;
|
||||
border-radius: v-bind(borderRadius);
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab-active) {
|
||||
background-color: #eeeeee;
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
:deep(.ant-tabs-nav .ant-tabs-tab:hover) {
|
||||
background-color: #eeeeee;
|
||||
.smart-page-tag-close {
|
||||
color: @color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 展开菜单模式
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:40:16
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-06 20:40:16
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-layout class="admin-layout" style="min-height: 100%">
|
||||
@@ -70,7 +70,15 @@
|
||||
<a-back-top :target="backTopTarget" :visibilityHeight="80" />
|
||||
</a-layout>
|
||||
<!-- 右侧帮助文档 help-doc -->
|
||||
<a-layout-sider v-show="helpDocFlag" theme="light" :width="180" class="help-doc-sider" :trigger="null" style="min-height: 100%">
|
||||
<a-layout-sider
|
||||
v-if="helpDocFlag"
|
||||
v-show="helpDocExpandFlag"
|
||||
theme="light"
|
||||
:width="180"
|
||||
class="help-doc-sider"
|
||||
:trigger="null"
|
||||
style="min-height: 100%"
|
||||
>
|
||||
<SideHelpDoc />
|
||||
</a-layout-sider>
|
||||
</a-layout>
|
||||
@@ -99,6 +107,8 @@
|
||||
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
|
||||
// 是否显示帮助文档
|
||||
const helpDocFlag = computed(() => useAppConfigStore().$state.helpDocFlag);
|
||||
// 是否默认展开帮助文档
|
||||
const helpDocExpandFlag = computed(() => useAppConfigStore().$state.helpDocExpandFlag);
|
||||
// 是否显示页脚
|
||||
const footerFlag = computed(() => useAppConfigStore().$state.footerFlag);
|
||||
// 是否显示水印
|
||||
|
||||
@@ -64,7 +64,15 @@
|
||||
<a-back-top :target="backTopTarget" :visibilityHeight="80" />
|
||||
</a-layout>
|
||||
<!-- 右侧帮助文档 help-doc -->
|
||||
<a-layout-sider v-show="helpDocFlag" theme="light" :width="180" class="help-doc-sider" :trigger="null" style="min-height: 100%">
|
||||
<a-layout-sider
|
||||
v-if="helpDocFlag"
|
||||
v-show="helpDocExpandFlag"
|
||||
theme="light"
|
||||
:width="180"
|
||||
class="help-doc-sider"
|
||||
:trigger="null"
|
||||
style="min-height: 100%"
|
||||
>
|
||||
<SideHelpDoc />
|
||||
</a-layout-sider>
|
||||
</a-layout>
|
||||
@@ -95,6 +103,8 @@
|
||||
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
|
||||
// 是否显示帮助文档
|
||||
const helpDocFlag = computed(() => useAppConfigStore().$state.helpDocFlag);
|
||||
// 是否默认展开帮助文档
|
||||
const helpDocExpandFlag = computed(() => useAppConfigStore().$state.helpDocExpandFlag);
|
||||
// 是否显示页脚
|
||||
const footerFlag = computed(() => useAppConfigStore().$state.footerFlag);
|
||||
// 是否显示水印
|
||||
|
||||
@@ -26,6 +26,7 @@ import { buildRoutes, router } from '/@/router';
|
||||
import { store } from '/@/store';
|
||||
import { useUserStore } from '/@/store/modules/system/user';
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
import 'vue3-tabs-chrome/dist/vue3-tabs-chrome.css';
|
||||
import '/@/theme/index.less';
|
||||
import { localRead } from '/@/utils/local-util.js';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
|
||||
|
||||
@@ -22,6 +22,7 @@ export default {
|
||||
*/
|
||||
smartEnumPlugin.getDescByValue = function (constantName, value) {
|
||||
if (!smartEnumWrapper || !Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) {
|
||||
console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!');
|
||||
return '';
|
||||
}
|
||||
// boolean类型需要做特殊处理
|
||||
@@ -44,6 +45,7 @@ export default {
|
||||
*/
|
||||
smartEnumPlugin.getValueDescList = function (constantName) {
|
||||
if (!Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) {
|
||||
console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!');
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
@@ -61,6 +63,7 @@ export default {
|
||||
*/
|
||||
smartEnumPlugin.getValueDesc = function (constantName) {
|
||||
if (!Object.prototype.hasOwnProperty.call(smartEnumWrapper, constantName)) {
|
||||
console.error('无法找到变量名称:' + constantName + ',请检查 /constants/index.js 文件中是否引入此变量!');
|
||||
return {};
|
||||
}
|
||||
let smartEnum = smartEnumWrapper[constantName];
|
||||
|
||||
@@ -34,6 +34,15 @@ export const homeRouters = [
|
||||
},
|
||||
component: () => import('/@/views/system/home/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
name: 'Account',
|
||||
component: () => import('/@/views/system/account/index.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
hideInMenu: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -12,7 +12,7 @@ export const loginRouters = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('/@/views/system/login/login.vue'),
|
||||
component: () => import('/@/views/system/login3/login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
hideInMenu: true,
|
||||
|
||||
@@ -46,10 +46,10 @@ export const useAppConfigStore = defineStore({
|
||||
}
|
||||
},
|
||||
showHelpDoc() {
|
||||
this.helpDocFlag = true;
|
||||
this.helpDocExpandFlag = true;
|
||||
},
|
||||
hideHelpDoc() {
|
||||
this.helpDocFlag = false;
|
||||
this.helpDocExpandFlag = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@ import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
|
||||
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
|
||||
import { localClear, localRead, localSave } from '/@/utils/local-util';
|
||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'userStore',
|
||||
@@ -21,6 +23,8 @@ export const useUserStore = defineStore({
|
||||
token: '',
|
||||
//员工id
|
||||
employeeId: '',
|
||||
// 头像
|
||||
avatar: '',
|
||||
//登录名
|
||||
loginName: '',
|
||||
//姓名
|
||||
@@ -52,9 +56,11 @@ export const useUserStore = defineStore({
|
||||
// 功能点集合
|
||||
pointsList: [],
|
||||
// 标签页
|
||||
tagNav: [],
|
||||
tagNav: null,
|
||||
// 缓存
|
||||
keepAliveIncludes: [],
|
||||
// 未读消息数量
|
||||
unreadMessageCount: 0,
|
||||
}),
|
||||
getters: {
|
||||
getToken(state) {
|
||||
@@ -89,7 +95,7 @@ export const useUserStore = defineStore({
|
||||
},
|
||||
//标签页
|
||||
getTagNav(state) {
|
||||
if (_.isEmpty(state.tagNav)) {
|
||||
if (_.isNull(state.tagNav)) {
|
||||
let localTagNav = localRead(localKey.USER_TAG_NAV) || '';
|
||||
state.tagNav = localTagNav ? JSON.parse(localTagNav) : [];
|
||||
}
|
||||
@@ -108,13 +114,24 @@ export const useUserStore = defineStore({
|
||||
this.menuList = [];
|
||||
this.tagNav = [];
|
||||
this.userInfo = {};
|
||||
this.unreadMessageCount = 0;
|
||||
localClear();
|
||||
},
|
||||
// 查询未读消息数量
|
||||
async queryUnreadMessageCount() {
|
||||
try {
|
||||
let result = await messageApi.queryUnreadCount();
|
||||
this.unreadMessageCount = result.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
},
|
||||
//设置登录信息
|
||||
setUserLoginInfo(data) {
|
||||
// 用户基本信息
|
||||
this.token = data.token;
|
||||
this.employeeId = data.employeeId;
|
||||
this.avatar = data.avatar;
|
||||
this.loginName = data.loginName;
|
||||
this.actualName = data.actualName;
|
||||
this.phone = data.phone;
|
||||
@@ -137,16 +154,22 @@ export const useUserStore = defineStore({
|
||||
|
||||
//功能点
|
||||
this.pointsList = data.menuList.filter((menu) => menu.menuType === MENU_TYPE_ENUM.POINTS.value && menu.visibleFlag && !menu.disabledFlag);
|
||||
|
||||
// 获取用户未读消息
|
||||
this.queryUnreadMessageCount();
|
||||
},
|
||||
setToken(token) {
|
||||
this.token = token;
|
||||
},
|
||||
//设置标签页
|
||||
setTagNav(route, from) {
|
||||
if (_.isEmpty(this.getTagNav)) this.tagNav = [];
|
||||
if (_.isNull(this.tagNav)) {
|
||||
let localTagNav = localRead(localKey.USER_TAG_NAV) || '';
|
||||
this.tagNav = localTagNav ? JSON.parse(localTagNav) : [];
|
||||
}
|
||||
// name唯一标识
|
||||
let name = route.name;
|
||||
if (!name || name === HOME_PAGE_NAME) {
|
||||
if (!name || name === HOME_PAGE_NAME || name === '403' || name === '404') {
|
||||
return;
|
||||
}
|
||||
let findTag = (this.tagNav || []).find((e) => e.menuName === name);
|
||||
|
||||
@@ -3,7 +3,13 @@ export const themeColors = [
|
||||
{
|
||||
primaryColor: '#1677ff',
|
||||
activeColor: '#0958d9',
|
||||
hoverColor: '#bae0ff',
|
||||
hoverColor: '#4096ff',
|
||||
},
|
||||
// 蓝色2
|
||||
{
|
||||
primaryColor: '#2F54EB',
|
||||
activeColor: '#1d39c4',
|
||||
hoverColor: '#597ef7',
|
||||
},
|
||||
// 绿色
|
||||
{
|
||||
@@ -36,4 +42,22 @@ export const themeColors = [
|
||||
activeColor: '#531dab',
|
||||
hoverColor: '#9254de',
|
||||
},
|
||||
// 极光绿
|
||||
{
|
||||
primaryColor: '#52c41a',
|
||||
activeColor: '#389e0d',
|
||||
hoverColor: '#73d13d',
|
||||
},
|
||||
// 深绿
|
||||
{
|
||||
primaryColor: '#009688',
|
||||
activeColor: '#007069',
|
||||
hoverColor: '#1aa391',
|
||||
},
|
||||
// 橙色
|
||||
{
|
||||
primaryColor: '#fa541c',
|
||||
activeColor: '#d4380d',
|
||||
hoverColor: '#ff7a45',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
/*
|
||||
* SmartAdmin 独有的样式
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 14:43:01
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 14:43:01
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
|
||||
/********************************** 宽度 **********************************/
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
.smart-table-operate {
|
||||
.ant-btn {
|
||||
padding: 0px 3px !important;
|
||||
padding: 0 3px !important;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="addCategory()" type="primary" size="small" v-privilege="`${privilegePrefix}category:add`">
|
||||
<a-button @click="addCategory()" type="primary" v-privilege="`${privilegePrefix}category:add`">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
|
||||
@@ -41,18 +41,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="onSearch" v-privilege="'goods:query'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10" v-privilege="'goods:query'">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch" v-privilege="'goods:query'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" v-privilege="'goods:query'">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -62,28 +64,28 @@
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="addGoods" type="primary" size="small" v-privilege="'goods:add'">
|
||||
<a-button @click="addGoods" type="primary" v-privilege="'goods:add'">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
|
||||
<a-button @click="confirmBatchDelete" danger size="small" :disabled="selectedRowKeyList.length === 0" v-privilege="'goods:batchDelete'">
|
||||
<a-button @click="confirmBatchDelete" danger :disabled="selectedRowKeyList.length === 0" v-privilege="'goods:batchDelete'">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
|
||||
<a-button @click="showImportModal" type="primary" size="small" v-privilege="'goods:importGoods'">
|
||||
<a-button @click="showImportModal" type="primary" v-privilege="'goods:importGoods'">
|
||||
<template #icon>
|
||||
<ImportOutlined />
|
||||
</template>
|
||||
导入
|
||||
</a-button>
|
||||
|
||||
<a-button @click="onExportGoods" type="primary" size="small" v-privilege="'goods:exportGoods'">
|
||||
<a-button @click="onExportGoods" type="primary" v-privilege="'goods:exportGoods'">
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 企业 银行列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-15 20:15:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
@@ -21,18 +21,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button @click="addOrUpdate()" type="primary" class="smart-margin-left20">
|
||||
<template #icon>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 企业 员工
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-15 20:15:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 企业 发票信息
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-15 20:15:49
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
@@ -21,18 +21,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button @click="addOrUpdate()" type="primary" class="smart-margin-left20">
|
||||
<template #icon>
|
||||
|
||||
@@ -21,18 +21,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -40,13 +42,13 @@
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="add()" v-privilege="'oa:enterprise:add'" type="primary" size="small">
|
||||
<a-button @click="add()" v-privilege="'oa:enterprise:add'" type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建企业
|
||||
</a-button>
|
||||
<a-button @click="exportExcel()" v-privilege="'oa:enterprise:exportExcel'" type="primary" size="small">
|
||||
<a-button @click="exportExcel()" v-privilege="'oa:enterprise:exportExcel'" type="primary">
|
||||
<template #icon>
|
||||
<FileExcelOutlined />
|
||||
</template>
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
<!--
|
||||
* 通知 详情 (员工列表)
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-21 19:52:43
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-21 19:52:43
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="分类" class="smart-query-form-item">
|
||||
<a-select v-model:value="queryForm.noticeTypeId" style="width: 100px" :showSearch="true" :allowClear="true">
|
||||
<a-select-option v-for="item in noticeTypeList" :key="item.noticeTypeId" :value="item.noticeTypeId">
|
||||
{{ item.noticeTypeName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value="queryForm.keywords" placeholder="标题、作者、来源、文号" />
|
||||
<a-input style="width: 200px" v-model:value="queryForm.keywords" placeholder="标题、作者、来源、文号" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="发布时间" class="smart-query-form-item">
|
||||
@@ -43,6 +35,7 @@
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
<a-row class="smart-query-form-row"> </a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="false">
|
||||
@@ -51,20 +44,11 @@
|
||||
<a-tab-pane :key="1" tab="未读" />
|
||||
</a-tabs>
|
||||
|
||||
<a-table
|
||||
rowKey="noticeId"
|
||||
:columns="tableColumns"
|
||||
:dataSource="tableData"
|
||||
:scroll="{ x: 1500 }"
|
||||
:pagination="false"
|
||||
:loading="tableLoading"
|
||||
bordered
|
||||
size="small"
|
||||
>
|
||||
<a-table rowKey="noticeId" :columns="tableColumns" :dataSource="tableData" :pagination="false" :loading="tableLoading" bordered size="small">
|
||||
<template #bodyCell="{ column, record, text }">
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<span v-show="record.viewFlag">
|
||||
<a @click="toDetail(record.noticeId)" style="color: #666">【{{ record.noticeTypeName }}】{{ text }}(已读)</a>
|
||||
<a @click="toDetail(record.noticeId)" style="color: #8c8c8c">【{{ record.noticeTypeName }}】{{ text }}(已读)</a>
|
||||
</span>
|
||||
<span v-show="!record.viewFlag">
|
||||
<a @click="toDetail(record.noticeId)"
|
||||
@@ -96,50 +80,39 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import SmartBooleanSelect from '/@/components/framework/boolean-select/index.vue';
|
||||
import { noticeApi } from '/@/api/business/oa/notice-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const tableColumns = reactive([
|
||||
{
|
||||
title: `标题`,
|
||||
dataIndex: 'title',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: `文号`,
|
||||
dataIndex: 'documentNumber',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: `作者`,
|
||||
dataIndex: 'author',
|
||||
width: 40,
|
||||
ellipsis: true,
|
||||
title: '访问量',
|
||||
dataIndex: 'pageViewCount',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: `来源`,
|
||||
dataIndex: 'source',
|
||||
width: 90,
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: `作者`,
|
||||
dataIndex: 'author',
|
||||
width: 80,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
dataIndex: 'publishTime',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '用户/页面浏览量',
|
||||
dataIndex: 'pageViewCount',
|
||||
width: 90,
|
||||
width: 150,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 通知 管理列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-21 19:52:43
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-21 19:52:43
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
|
||||
<template>
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch" class="smart-margin-right10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
@@ -65,7 +65,7 @@
|
||||
<a-card size="small" :bordered="false">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button type="primary" size="small" @click="addOrUpdate()" v-privilege="'oa:notice:add'">
|
||||
<a-button type="primary" @click="addOrUpdate()" v-privilege="'oa:notice:add'">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
|
||||
@@ -22,18 +22,20 @@
|
||||
<a-date-picker valueFormat="YYYY-MM-DD" v-model:value="queryForm.createTime" style="width: 150px" />
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -43,19 +45,13 @@
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="showForm" type="primary" size="small" v-privilege="'support:changeLog:add'">
|
||||
<a-button @click="showForm" type="primary" v-privilege="'support:changeLog:add'">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="confirmBatchDelete"
|
||||
danger
|
||||
size="small"
|
||||
:disabled="selectedRowKeyList.length === 0"
|
||||
v-privilege="'support:changeLog:batchDelete'"
|
||||
>
|
||||
<a-button @click="confirmBatchDelete" danger :disabled="selectedRowKeyList.length === 0" v-privilege="'support:changeLog:batchDelete'">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 代码生成 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-08 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-08 21:50:41
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
@@ -16,18 +16,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch" v-privilege="'support:config:query'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" v-privilege="'support:config:query'" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch" v-privilege="'support:config:query'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" v-privilege="'support:config:query'">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button @click="toEditOrAdd()" v-privilege="'support:config:add'" type="primary" class="smart-margin-left20">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
|
||||
@@ -15,18 +15,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -34,28 +36,21 @@
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="addOrUpdateKey" v-privilege="'support:dict:add'" type="primary" size="small">
|
||||
<a-button @click="addOrUpdateKey" v-privilege="'support:dict:add'" type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
@click="confirmBatchDelete"
|
||||
v-privilege="'support:dict:batchDelete'"
|
||||
type="text"
|
||||
danger
|
||||
size="small"
|
||||
:disabled="selectedRowKeyList.length === 0"
|
||||
>
|
||||
<a-button @click="confirmBatchDelete" v-privilege="'support:dict:batchDelete'" type="text" danger :disabled="selectedRowKeyList.length === 0">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
|
||||
<a-button @click="cacheRefresh" v-privilege="'support:dict:refresh'" type="primary" size="small">
|
||||
<a-button @click="cacheRefresh" v-privilege="'support:dict:refresh'" type="primary">
|
||||
<template #icon>
|
||||
<cloud-sync-outlined />
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* @Author: 1024创新实验室-主任-卓大
|
||||
* @Date: 2020-10-10 22:13:18
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<!---------- 查询表单form begin ----------->
|
||||
@@ -28,18 +28,20 @@
|
||||
<a-range-picker v-model:value="queryForm.createTime" :presets="defaultTimeRanges" style="width: 220px" @change="onChangeCreateTime" />
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="queryData">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="queryData">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -49,7 +51,7 @@
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button type="primary" @click="showUploadModal" size="small">
|
||||
<a-button type="primary" @click="showUploadModal">
|
||||
<template #icon>
|
||||
<cloud-upload-outlined />
|
||||
</template>
|
||||
@@ -63,7 +65,16 @@
|
||||
<!---------- 表格操作行 end ----------->
|
||||
|
||||
<!---------- 表格 begin ----------->
|
||||
<a-table size="small" :dataSource="tableData" :columns="columns" rowKey="fileId" bordered :loading="tableLoading" :pagination="false">
|
||||
<a-table
|
||||
size="small"
|
||||
:scroll="{ x: 1300 }"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
rowKey="fileId"
|
||||
bordered
|
||||
:loading="tableLoading"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'folderType'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('FILE_FOLDER_TYPE_ENUM', text) }}</span>
|
||||
@@ -148,10 +159,6 @@
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '文件key',
|
||||
dataIndex: 'fileKey',
|
||||
},
|
||||
{
|
||||
title: '文件类型',
|
||||
dataIndex: 'fileType',
|
||||
@@ -180,6 +187,7 @@
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 心跳记录
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
@@ -38,18 +38,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch" class="smart-margin-right10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<a-card size="small" :bordered="false">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button type="primary" size="small" @click="addOrUpdate()" v-privilege="'support:helpDoc:add'">
|
||||
<a-button type="primary" @click="addOrUpdate()" v-privilege="'support:helpDoc:add'">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<!--
|
||||
* JOB 表单
|
||||
* @Author: huke
|
||||
* @Date: 2024/06/29
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<!-- 编辑 -->
|
||||
<a-modal :open="updateModalShow" :width="650" title="编辑" ok-text="确认" cancel-text="取消" @cancel="closeUpdateModal" @ok="confirmUpdateJob">
|
||||
<a-form ref="updateFormRef" :model="updateForm" :rules="updateRules" :label-col="{ span: 4 }">
|
||||
<a-form-item label="任务名称" name="jobName">
|
||||
<a-input placeholder="请输入任务名称" v-model:value="updateForm.jobName" :maxlength="100" :showCount="true" />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务描述" name="remark">
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
v-model:value="updateForm.remark"
|
||||
placeholder="(可选)请输入任务备注描述"
|
||||
:maxlength="250"
|
||||
:showCount="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number
|
||||
v-model:value="updateForm.sort"
|
||||
:min="-99999999"
|
||||
:max="99999999"
|
||||
:precision="0"
|
||||
style="width: 100%"
|
||||
placeholder="值越小越靠前"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="执行类" name="jobClass">
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
v-model:value="updateForm.jobClass"
|
||||
placeholder="示例:net.lab1024.sa.base.module.support.job.sample.SmartJobSample1"
|
||||
:maxlength="200"
|
||||
:showCount="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="任务参数" name="param">
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
v-model:value="updateForm.param"
|
||||
placeholder="(可选)请输入任务执行参数"
|
||||
:maxlength="1000"
|
||||
:showCount="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="触发类型" name="triggerType">
|
||||
<a-radio-group v-model:value="updateForm.triggerType">
|
||||
<a-radio-button :value="TRIGGER_TYPE_ENUM.CRON.value">CRON表达式</a-radio-button>
|
||||
<a-radio-button :value="TRIGGER_TYPE_ENUM.FIXED_DELAY.value">固定间隔</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="触发时间" name="triggerTime">
|
||||
<a-input
|
||||
v-if="updateForm.triggerType === TRIGGER_TYPE_ENUM.CRON.value"
|
||||
placeholder="示例:10 15 0/1 * * *"
|
||||
v-model:value="updateForm.cron"
|
||||
:maxlength="100"
|
||||
:showCount="true"
|
||||
/>
|
||||
<a-input-number
|
||||
v-else-if="updateForm.triggerType === TRIGGER_TYPE_ENUM.FIXED_DELAY.value"
|
||||
v-model:value="updateForm.fixedDelay"
|
||||
:min="1"
|
||||
:max="100000000"
|
||||
:precision="0"
|
||||
:defaultValue="100"
|
||||
>
|
||||
<template #addonBefore>每隔</template>
|
||||
<template #addonAfter>秒</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否开启" name="enabledFlag">
|
||||
<a-switch v-model:checked="updateForm.enabledFlag" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 立即执行 -->
|
||||
<a-modal
|
||||
:open="executeModalShow"
|
||||
:width="650"
|
||||
title="执行任务"
|
||||
ok-text="执行"
|
||||
cancel-text="取消"
|
||||
@cancel="closeExecuteModal"
|
||||
@ok="confirmExecuteJob"
|
||||
>
|
||||
<br />
|
||||
<a-alert type="info" show-icon style="margin-left: 25px">
|
||||
<template #message> 点击【执行】后会按照【任务参数】,无论任务是否开启,都会立即执行。 </template>
|
||||
</a-alert>
|
||||
<br />
|
||||
<a-form :label-col="{ span: 4 }">
|
||||
<a-form-item label="任务名称" name="jobName">
|
||||
<a-input v-model:value="executeForm.jobName" :disabled="true" />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务类名" name="jobClass">
|
||||
<a-textarea :auto-size="{ minRows: 2, maxRows: 4 }" v-model:value="executeForm.jobClass" :disabled="true" />
|
||||
</a-form-item>
|
||||
<a-form-item label="任务参数" name="param">
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
v-model:value="executeForm.param"
|
||||
placeholder="(可选)请输入任务执行参数"
|
||||
:maxlength="1000"
|
||||
:showCount="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { jobApi } from '/src/api/support/job-api';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading/index.js';
|
||||
import { TRIGGER_TYPE_ENUM } from '/src/constants/support/job-const.js';
|
||||
|
||||
// emit
|
||||
const emit = defineEmits(['reloadList']);
|
||||
|
||||
const updateModalShow = ref(false);
|
||||
const updateFormRef = ref();
|
||||
|
||||
const updateFormDefault = {
|
||||
jobId: null,
|
||||
jobName: '',
|
||||
jobClass: '',
|
||||
triggerType: null,
|
||||
triggerValue: null,
|
||||
cron: '',
|
||||
fixedDelay: null,
|
||||
param: '',
|
||||
enabledFlag: false,
|
||||
remark: '',
|
||||
sort: null,
|
||||
};
|
||||
let updateForm = reactive({ ...updateFormDefault });
|
||||
const updateRules = {
|
||||
jobName: [{ required: true, message: '请输入任务名称' }],
|
||||
jobClass: [{ required: true, message: '请输入执行类' }],
|
||||
triggerType: [{ required: true, message: '请选择触发类型' }],
|
||||
sort: [{ required: true, message: '请输入排序' }],
|
||||
};
|
||||
|
||||
// 打开编辑弹框
|
||||
function openUpdateModal(record) {
|
||||
Object.assign(updateForm, record);
|
||||
if (TRIGGER_TYPE_ENUM.CRON.value === record.triggerType) {
|
||||
updateForm.cron = record.triggerValue;
|
||||
}
|
||||
if (TRIGGER_TYPE_ENUM.FIXED_DELAY.value === record.triggerType) {
|
||||
updateForm.fixedDelay = record.triggerValue;
|
||||
}
|
||||
updateModalShow.value = true;
|
||||
}
|
||||
|
||||
// 关闭编辑弹框
|
||||
function closeUpdateModal() {
|
||||
Object.assign(updateForm, updateFormDefault);
|
||||
updateModalShow.value = false;
|
||||
}
|
||||
|
||||
// 确认更新
|
||||
async function confirmUpdateJob() {
|
||||
updateFormRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
SmartLoading.show();
|
||||
if (TRIGGER_TYPE_ENUM.CRON.value === updateForm.triggerType) {
|
||||
updateForm.triggerValue = updateForm.cron;
|
||||
}
|
||||
if (TRIGGER_TYPE_ENUM.FIXED_DELAY.value === updateForm.triggerType) {
|
||||
updateForm.triggerValue = updateForm.fixedDelay;
|
||||
}
|
||||
try {
|
||||
await jobApi.updateJob(updateForm);
|
||||
message.success('更新成功');
|
||||
closeUpdateModal();
|
||||
emit('reloadList');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
message.error('参数验证错误,请检查表单数据');
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------ 执行任务 -------------------------------------
|
||||
const executeModalShow = ref(false);
|
||||
const executeFormDefault = {
|
||||
jobId: null,
|
||||
jobName: '',
|
||||
jobClass: '',
|
||||
param: null,
|
||||
};
|
||||
let executeForm = reactive({ ...executeFormDefault });
|
||||
|
||||
// 打开执行弹框
|
||||
function openExecuteModal(record) {
|
||||
Object.assign(executeForm, record);
|
||||
executeModalShow.value = true;
|
||||
}
|
||||
|
||||
// 关闭执行弹框
|
||||
function closeExecuteModal() {
|
||||
Object.assign(executeForm, executeFormDefault);
|
||||
executeModalShow.value = false;
|
||||
}
|
||||
|
||||
// 确认执行
|
||||
async function confirmExecuteJob() {
|
||||
try {
|
||||
let executeParam = {
|
||||
jobId: executeForm.jobId,
|
||||
param: executeForm.param,
|
||||
};
|
||||
await jobApi.executeJob(executeParam);
|
||||
// loading 延迟后再提示刷新
|
||||
SmartLoading.show();
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
message.success('执行成功');
|
||||
closeExecuteModal();
|
||||
emit('reloadList');
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openUpdateModal,
|
||||
openExecuteModal,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,200 @@
|
||||
<!--
|
||||
* job log列表
|
||||
* @Author: huke
|
||||
* @Date: 2024/06/25
|
||||
-->
|
||||
<template>
|
||||
<a-drawer v-model:open="showFlag" :width="1000" :title="title" placement="right" :destroyOnClose="true">
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 200px" v-model:value="queryForm.searchWord" placeholder="请输入关键字" :maxlength="30" />
|
||||
</a-form-item>
|
||||
<a-form-item label="执行结果" class="smart-query-form-item">
|
||||
<a-select style="width: 100px" v-model:value="queryForm.successFlag" placeholder="请选择" allowClear>
|
||||
<a-select-option :key="1"> 成功 </a-select-option>
|
||||
<a-select-option :key="0"> 失败 </a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="执行时间" class="smart-query-form-item">
|
||||
<a-space direction="vertical" :size="12">
|
||||
<a-range-picker v-model:value="searchDate" style="width: 220px" @change="dateChange" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row justify="end">
|
||||
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.JOB_LOG" :refresh="queryLogList" />
|
||||
</a-row>
|
||||
|
||||
<a-table size="small" :loading="tableLoading" bordered :dataSource="tableData" :columns="columns" rowKey="jobLogId" :pagination="false">
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'executeStartTime'">
|
||||
<div><a-tag color="green">始</a-tag>{{ record.executeStartTime }}</div>
|
||||
<div style="margin-top: 5px"><a-tag color="blue">终</a-tag>{{ record.executeEndTime }}</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'executeTimeMillis'"> {{ record.executeTimeMillis }} ms </template>
|
||||
<template v-if="column.dataIndex === 'successFlag'">
|
||||
<div v-if="record.successFlag" style="color: #39c710"><CheckOutlined />成功</div>
|
||||
<div v-else style="color: #f50"><WarningOutlined />失败</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="queryLogList"
|
||||
@showSizeChange="queryLogList"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { jobApi } from '/src/api/support/job-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/src/constants/common-const';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
import TableOperator from '/src/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/src/constants/support/table-id-const';
|
||||
|
||||
const showFlag = ref(false);
|
||||
const title = ref('');
|
||||
|
||||
function show(jobId, name) {
|
||||
queryForm.jobId = jobId;
|
||||
queryLogList();
|
||||
showFlag.value = true;
|
||||
title.value = name;
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '执行人',
|
||||
dataIndex: 'createName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '执行参数',
|
||||
dataIndex: 'param',
|
||||
width: 80,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '执行时间',
|
||||
dataIndex: 'executeStartTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '执行用时',
|
||||
dataIndex: 'executeTimeMillis',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '结果',
|
||||
dataIndex: 'successFlag',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '执行结果',
|
||||
dataIndex: 'executeResult',
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'ip',
|
||||
dataIndex: 'ip',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '进程id',
|
||||
dataIndex: 'processId',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '程序目录',
|
||||
dataIndex: 'programPath',
|
||||
ellipsis: true,
|
||||
},
|
||||
]);
|
||||
|
||||
// ---------------- 查询数据 -----------------------
|
||||
const queryFormState = {
|
||||
searchWord: '',
|
||||
jobId: null,
|
||||
successFlag: null,
|
||||
endTime: null,
|
||||
startTime: null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// 日期选择
|
||||
let searchDate = ref();
|
||||
|
||||
function dateChange(dates, dateStrings) {
|
||||
queryForm.startTime = dateStrings[0];
|
||||
queryForm.endTime = dateStrings[1];
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
Object.assign(queryForm, queryFormState);
|
||||
queryLogList();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
queryLogList();
|
||||
}
|
||||
|
||||
async function queryLogList() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await jobApi.queryJobLog(queryForm);
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,322 @@
|
||||
<!--
|
||||
* JOB 列表
|
||||
* @Author: huke
|
||||
* @Date: 2024/06/25
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 200px" v-model:value="queryForm.searchWord" placeholder="请输入关键字" :maxlength="30" />
|
||||
</a-form-item>
|
||||
<a-form-item label="触发类型" class="smart-query-form-item">
|
||||
<a-select style="width: 155px" v-model:value="queryForm.triggerType" placeholder="请选择触发类型" allowClear>
|
||||
<a-select-option v-for="item in $smartEnumPlugin.getValueDescList('TRIGGER_TYPE_ENUM')" :key="item.value" :value="item.value">
|
||||
{{ item.desc }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" class="smart-query-form-item">
|
||||
<a-select style="width: 150px" v-model:value="queryForm.enabledFlag" placeholder="请选择状态" allowClear>
|
||||
<a-select-option :key="1"> 开启 </a-select-option>
|
||||
<a-select-option :key="0"> 停止 </a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch" v-privilege="'support:job:query'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" v-privilege="'support:job:query'">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row justify="end">
|
||||
<TableOperator class="smart-margin-bottom5" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.JOB" :refresh="queryJobList" />
|
||||
</a-row>
|
||||
|
||||
<a-table
|
||||
:scroll="{ x: 1800 }"
|
||||
size="small"
|
||||
:loading="tableLoading"
|
||||
bordered
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
rowKey="jobId"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'jobClass'">
|
||||
<a-tooltip>
|
||||
<template #title>{{ record.jobClass }}</template>
|
||||
{{ handleJobClass(record.jobClass) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'triggerType'">
|
||||
<a-tag v-if="record.triggerType === TRIGGER_TYPE_ENUM.CRON.value" color="success">{{ record.triggerTypeDesc }}</a-tag>
|
||||
<a-tag v-else-if="record.triggerType === TRIGGER_TYPE_ENUM.FIXED_DELAY.value" color="processing">{{ record.triggerTypeDesc }}</a-tag>
|
||||
<a-tag v-else color="pink">{{ record.triggerTypeDesc }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'lastJob'">
|
||||
<div v-if="record.lastJobLog">
|
||||
<a-tooltip>
|
||||
<template #title>{{ handleExecuteResult(record.lastJobLog.executeResult) }}</template>
|
||||
<CheckOutlined v-if="record.lastJobLog.successFlag" style="color: #39c710" />
|
||||
<WarningOutlined v-else style="color: #f50" />
|
||||
{{ record.lastJobLog.executeStartTime }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'nextJob'">
|
||||
<a-tooltip v-if="record.enabledFlag && record.nextJobExecuteTimeList">
|
||||
<template #title>
|
||||
<div>下次执行(预估时间)</div>
|
||||
<div v-for="item in record.nextJobExecuteTimeList" :key="item">{{ item }}</div>
|
||||
</template>
|
||||
{{ record.nextJobExecuteTimeList[0] }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'enabledFlag'">
|
||||
<a-switch
|
||||
v-model:checked="record.enabledFlag"
|
||||
@change="(checked) => handleEnabledUpdate(checked, record)"
|
||||
:loading="record.enabledLoading"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button v-privilege="'support:job:update'" @click="openUpdateModal(record)" type="link">编辑</a-button>
|
||||
<a-button v-privilege="'support:job:execute'" type="link" @click="openExecuteModal(record)">执行</a-button>
|
||||
<a-button v-privilege="'support:job:log:query'" @click="openJobLogModal(record.jobId, record.jobName)" type="link">记录</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="queryJobList"
|
||||
@showSizeChange="queryJobList"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 表单操作 -->
|
||||
<JobFormModal ref="jobFormModal" @reloadList="queryJobList" />
|
||||
|
||||
<!-- 记录 -->
|
||||
<JobLogListModal ref="jobLogModal" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { jobApi } from '/@/api/support/job-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
|
||||
import JobFormModal from './components/job-form-modal.vue';
|
||||
import JobLogListModal from './components/job-log-list-modal.vue';
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: 'id',
|
||||
width: 50,
|
||||
dataIndex: 'jobId',
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'jobName',
|
||||
minWidth: 150,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '执行类',
|
||||
dataIndex: 'jobClass',
|
||||
minWidth: 180,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '触发类型',
|
||||
dataIndex: 'triggerType',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '触发配置',
|
||||
dataIndex: 'triggerValue',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '上次执行',
|
||||
width: 180,
|
||||
dataIndex: 'lastJob',
|
||||
},
|
||||
{
|
||||
title: '下次执行',
|
||||
width: 150,
|
||||
dataIndex: 'nextJob',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'enabledFlag',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '执行参数',
|
||||
dataIndex: 'param',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '任务描述',
|
||||
dataIndex: 'remark',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
width: 65,
|
||||
},
|
||||
{
|
||||
title: '更新人',
|
||||
dataIndex: 'updateName',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 130,
|
||||
},
|
||||
]);
|
||||
|
||||
// ---------------- 查询数据 -----------------------
|
||||
|
||||
const queryFormState = {
|
||||
searchWord: '',
|
||||
enabledFlag: null,
|
||||
triggerType: null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
function resetQuery() {
|
||||
Object.assign(queryForm, queryFormState);
|
||||
queryJobList();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
queryJobList();
|
||||
}
|
||||
|
||||
// 处理执行类展示 默认返回类
|
||||
function handleJobClass(jobClass) {
|
||||
return jobClass.split('.').pop();
|
||||
}
|
||||
|
||||
// 上次处理结果展示 最多展示300
|
||||
function handleExecuteResult(result) {
|
||||
let num = 400;
|
||||
return result ? result.substring(0, num) + (result.length > num ? ' ...' : '') : '';
|
||||
}
|
||||
|
||||
async function queryJobList() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await jobApi.queryJob(queryForm);
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(queryJobList);
|
||||
|
||||
// 更新状态
|
||||
async function handleEnabledUpdate(checked, record) {
|
||||
record.enabledLoading = true;
|
||||
try {
|
||||
let updateForm = {
|
||||
jobId: record.jobId,
|
||||
enabledFlag: checked,
|
||||
};
|
||||
await jobApi.updateJobEnabled(updateForm);
|
||||
// 重新查询任务详情
|
||||
let jobInfo = await queryJobInfo(record.jobId);
|
||||
Object.assign(record, jobInfo);
|
||||
message.success('更新成功');
|
||||
} catch (e) {
|
||||
record.enabledFlag = !checked;
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
record.enabledLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询任务详情
|
||||
async function queryJobInfo(jobId) {
|
||||
try {
|
||||
let res = await jobApi.queryJobInfo(jobId);
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------ 执行记录 -------------------------------------
|
||||
const jobLogModal = ref();
|
||||
function openJobLogModal(jobId, name) {
|
||||
jobLogModal.value.show(jobId, name);
|
||||
}
|
||||
|
||||
// ------------------------------------ 表单操作 -------------------------------------
|
||||
const jobFormModal = ref();
|
||||
|
||||
// 打开更新表单
|
||||
function openUpdateModal(record) {
|
||||
jobFormModal.value.openUpdateModal(record);
|
||||
}
|
||||
// 打开执行表单
|
||||
function openExecuteModal(record) {
|
||||
jobFormModal.value.openExecuteModal(record);
|
||||
}
|
||||
</script>
|
||||
@@ -28,18 +28,20 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
@@ -49,7 +51,7 @@
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="confirmBatchDelete" danger size="small" :disabled="selectedRowKeyList.length === 0">
|
||||
<a-button @click="confirmBatchDelete" danger :disabled="selectedRowKeyList.length === 0">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 登录、登出 日志
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form" v-privilege="'support:loginLog:query'" ref="queryFormRef">
|
||||
@@ -23,18 +23,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 操作记录 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form" v-privilege="'support:operateLog:query'">
|
||||
@@ -27,18 +27,20 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="ajaxQuery" class="smart-margin-right10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="ajaxQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { defineAsyncComponent, markRaw } from 'vue';
|
||||
|
||||
/**
|
||||
* 菜单展示
|
||||
* defineAsyncComponent 异步组件
|
||||
* markRaw 将一个Vue组件对象转换为响应式对象时,可能会导致不必要的性能开销。使用markRaw方法将组件对象标记为非响应式对象
|
||||
*/
|
||||
export const ACCOUNT_MENU = {
|
||||
CENTER: {
|
||||
menuId: 'center',
|
||||
menuName: '个人中心',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/center/index.vue'))),
|
||||
},
|
||||
PASSWORD: {
|
||||
menuId: 'password',
|
||||
menuName: '修改密码',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/password/index.vue'))),
|
||||
},
|
||||
MESSAGE: {
|
||||
menuId: 'message',
|
||||
menuName: '我的消息',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/message/index.vue'))),
|
||||
},
|
||||
NOTICE: {
|
||||
menuId: 'notice',
|
||||
menuName: '通知公告',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/notice/index.vue'))),
|
||||
},
|
||||
LOGIN_LOG: {
|
||||
menuId: 'login-log',
|
||||
menuName: '登录日志',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/login-log/index.vue'))),
|
||||
},
|
||||
OPERATE_LOG: {
|
||||
menuId: 'operate-log',
|
||||
menuName: '操作日志',
|
||||
components: markRaw(defineAsyncComponent(() => import('./components/operate-log/index.vue'))),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div class="center-container">
|
||||
<!-- 页面标题-->
|
||||
<div class="header-title">个人中心</div>
|
||||
|
||||
<!-- 内容区域-->
|
||||
<div class="center-form-area">
|
||||
<a-row>
|
||||
<a-col flex="350px">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<a-form-item label="登录账号" name="loginName">
|
||||
<a-input class="form-item" v-model:value.trim="form.loginName" placeholder="请输入登录账号" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="员工名称" name="actualName">
|
||||
<a-input class="form-item" v-model:value.trim="form.actualName" placeholder="请输入员工名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<smart-enum-select class="form-item" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号码" name="phone">
|
||||
<a-input class="form-item" v-model:value.trim="form.phone" placeholder="请输入手机号码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" name="departmentId">
|
||||
<DepartmentTreeSelect class="form-item" ref="departmentTreeSelect" width="100%" :init="false" v-model:value="form.departmentId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea class="form-item" v-model:value="form.remark" placeholder="请输入备注" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button type="primary" @click="onSubmit">更新个人信息</a-button>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-form style="padding-left: 80px" layout="vertical">
|
||||
<a-form-item label="头像" name="avatar">
|
||||
<br />
|
||||
<a-upload
|
||||
name="avatar"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
:headers="{ 'x-access-token': useUserStore().getToken }"
|
||||
:customRequest="customRequest"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<div v-if="avatarUrl" class="avatar-container">
|
||||
<img :src="avatarUrl" class="avatar-image" alt="avatar" />
|
||||
<div class="overlay">
|
||||
<span>更新头像</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<loading-outlined v-if="updateAvatarLoading" />
|
||||
<plus-outlined v-else />
|
||||
<div class="ant-upload-text">上传头像</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { regular } from '/@/constants/regular-const.js';
|
||||
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import { loginApi } from '/@/api/system/login-api.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { fileApi } from '/@/api/support/file-api.js';
|
||||
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const.js';
|
||||
|
||||
// 组件ref
|
||||
const formRef = ref();
|
||||
|
||||
const formDefault = {
|
||||
// 员工ID
|
||||
employeeId: undefined,
|
||||
// 头像
|
||||
avatar: undefined,
|
||||
// 登录账号
|
||||
loginName: '',
|
||||
// 员工名称
|
||||
actualName: '',
|
||||
// 性别
|
||||
gender: undefined,
|
||||
// 手机号码
|
||||
phone: '',
|
||||
// 部门id
|
||||
departmentId: undefined,
|
||||
// 是否启用
|
||||
disabledFlag: undefined,
|
||||
// 备注
|
||||
remark: '',
|
||||
};
|
||||
let form = reactive({ ...formDefault });
|
||||
const rules = {
|
||||
actualName: [
|
||||
{ required: true, message: '姓名不能为空' },
|
||||
{ max: 30, message: '姓名不能大于30个字符', trigger: 'blur' },
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空' },
|
||||
{ pattern: regular.phone, message: '请输入正确的手机号码', trigger: 'blur' },
|
||||
],
|
||||
gender: [{ required: true, message: '性别不能为空' }],
|
||||
departmentId: [{ required: true, message: '部门不能为空' }],
|
||||
};
|
||||
// 头像地址
|
||||
let avatarUrl = ref();
|
||||
|
||||
// 查询登录信息
|
||||
async function getLoginInfo() {
|
||||
try {
|
||||
//获取登录用户信息
|
||||
const res = await loginApi.getLoginInfo();
|
||||
let data = res.data;
|
||||
//更新用户信息到pinia
|
||||
useUserStore().setUserLoginInfo(data);
|
||||
// 当前form展示
|
||||
form.employeeId = data.employeeId;
|
||||
form.loginName = data.loginName;
|
||||
form.actualName = data.actualName;
|
||||
form.gender = data.gender;
|
||||
form.phone = data.phone;
|
||||
form.departmentId = data.departmentId;
|
||||
form.disabledFlag = data.disabledFlag;
|
||||
form.remark = data.remark;
|
||||
// 头像展示
|
||||
avatarUrl.value = data.avatar;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 头像上传
|
||||
const accept = ref('.jpg,.jpeg,.png,.gif');
|
||||
const maxSize = ref(10);
|
||||
const folder = ref(FILE_FOLDER_TYPE_ENUM.COMMON.value);
|
||||
let updateAvatarLoading = ref(false);
|
||||
function beforeUpload(file, files) {
|
||||
const suffixIndex = file.name.lastIndexOf('.');
|
||||
const fileSuffix = file.name.substring(suffixIndex <= -1 ? 0 : suffixIndex);
|
||||
if (accept.value.indexOf(fileSuffix) === -1) {
|
||||
message.error(`只支持上传 ${accept.value.replaceAll(',', ' ')} 格式的文件`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isLimitSize = file.size / 1024 / 1024 < maxSize.value;
|
||||
if (!isLimitSize) {
|
||||
message.error(`单个文件大小必须小于 ${maxSize.value} Mb`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function customRequest(options) {
|
||||
updateAvatarLoading.value = true;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', options.file);
|
||||
let res = await fileApi.uploadFile(formData, folder.value);
|
||||
let file = res.data;
|
||||
avatarUrl.value = file.fileUrl;
|
||||
// 更新头像
|
||||
let updateAvatarForm = { avatar: file.fileKey };
|
||||
await employeeApi.updateAvatar(updateAvatarForm);
|
||||
message.success('更新成功');
|
||||
// 重新获取详情,刷新整体缓存
|
||||
await getLoginInfo();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
updateAvatarLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新员工信息
|
||||
async function updateEmployee() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.updateByLogin(form);
|
||||
message.success('更新成功');
|
||||
// 重新获取详情,刷新整体缓存
|
||||
await getLoginInfo();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 表单提交
|
||||
function onSubmit() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
updateEmployee();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLoginInfo();
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.center-container {
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.center-form-area {
|
||||
margin-top: 20px;
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
&:hover .overlay {
|
||||
opacity: 1; /* 鼠标悬停时显示蒙版 */
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-uploader {
|
||||
:deep(.ant-upload) {
|
||||
border-radius: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-upload-select-picture-card i {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ant-upload-select-picture-card .ant-upload-text {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,192 @@
|
||||
<!--
|
||||
* 登录、登出 日志
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form" ref="queryFormRef">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="时间" class="smart-query-form-item">
|
||||
<a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table size="small" :dataSource="tableData" :columns="columns" bordered rowKey="loginLogId" :pagination="false" :loading="tableLoading">
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'loginResult'">
|
||||
<template v-if="text === LOGIN_RESULT_ENUM.LOGIN_SUCCESS.value">
|
||||
<a-tag color="success">登录成功</a-tag>
|
||||
</template>
|
||||
<template v-if="text === LOGIN_RESULT_ENUM.LOGIN_FAIL.value">
|
||||
<a-tag color="error">登录失败</a-tag>
|
||||
</template>
|
||||
<template v-if="text === LOGIN_RESULT_ENUM.LOGIN_OUT.value">
|
||||
<a-tag color="processing">退出登录</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'userAgent'">
|
||||
<div>{{ record.browser }} / {{ record.os }} / {{ record.device }}</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="ajaxQuery"
|
||||
@showSizeChange="ajaxQuery"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
import uaparser from 'ua-parser-js';
|
||||
import { LOGIN_RESULT_ENUM } from '/@/constants/support/login-log-const';
|
||||
import { loginLogApi } from '/@/api/support/login-log-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { calcTableHeight } from '/@/lib/table-auto-height';
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '登录方式',
|
||||
dataIndex: 'remark',
|
||||
ellipsis: true,
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '登录设备',
|
||||
dataIndex: 'userAgent',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'IP地区',
|
||||
dataIndex: 'loginIpRegion',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'loginIp',
|
||||
ellipsis: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '结果',
|
||||
dataIndex: 'loginResult',
|
||||
ellipsis: true,
|
||||
width: 90,
|
||||
},
|
||||
]);
|
||||
|
||||
const queryFormState = {
|
||||
userName: '',
|
||||
ip: '',
|
||||
startDate: undefined,
|
||||
endDate: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
const createDateRange = ref([]);
|
||||
const defaultChooseTimeRange = defaultTimeRanges;
|
||||
// 时间变动
|
||||
function changeCreateDate(dates, dateStrings) {
|
||||
queryForm.startDate = dateStrings[0];
|
||||
queryForm.endDate = dateStrings[1];
|
||||
}
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
function resetQuery() {
|
||||
Object.assign(queryForm, queryFormState);
|
||||
createDateRange.value = [];
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await loginLogApi.queryListLogin(queryForm);
|
||||
|
||||
for (const e of responseModel.data.list) {
|
||||
if (!e.userAgent) {
|
||||
continue;
|
||||
}
|
||||
let ua = uaparser(e.userAgent);
|
||||
e.browser = ua.browser.name;
|
||||
e.os = ua.os.name;
|
||||
e.device = ua.device.vendor ? ua.device.vendor + ua.device.model : '';
|
||||
}
|
||||
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- 表格自适应高度 --------------------
|
||||
const scrollY = ref(100);
|
||||
const queryFormRef = ref();
|
||||
|
||||
function autoCalcTableHeight() {
|
||||
calcTableHeight(scrollY, [queryFormRef], 10);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', autoCalcTableHeight);
|
||||
|
||||
onMounted(() => {
|
||||
ajaxQuery();
|
||||
autoCalcTableHeight();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', autoCalcTableHeight);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-drawer v-model:open="showFlag" :width="800" title="消息内容" placement="right" :destroyOnClose="true">
|
||||
<a-descriptions bordered :column="2" size="small">
|
||||
<a-descriptions-item :labelStyle="{ width: '80px' }" :span="1" label="类型"
|
||||
>{{ $smartEnumPlugin.getDescByValue('MESSAGE_TYPE_ENUM', messageDetail.messageType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :labelStyle="{ width: '120px' }" :span="1" label="发送时间">{{ messageDetail.createTime }}</a-descriptions-item>
|
||||
<a-descriptions-item :labelStyle="{ width: '80px' }" :span="2" label="标题">{{ messageDetail.title }}</a-descriptions-item>
|
||||
<a-descriptions-item :labelStyle="{ width: '80px' }" :span="2" label="内容">
|
||||
<pre>{{ messageDetail.content }}</pre>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { messageApi } from '/@/api/support/message-api.js';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const messageDetail = reactive({
|
||||
messageType: '',
|
||||
title: '',
|
||||
content: '',
|
||||
createTime: '',
|
||||
});
|
||||
|
||||
const showFlag = ref(false);
|
||||
|
||||
function show(data) {
|
||||
Object.assign(messageDetail, data);
|
||||
showFlag.value = true;
|
||||
read(data);
|
||||
}
|
||||
|
||||
async function read(message) {
|
||||
if (!message.readFlag) {
|
||||
await messageApi.updateReadFlag(message.messageId);
|
||||
await useUserStore().queryUnreadMessageCount();
|
||||
emit('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ show });
|
||||
</script>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value.trim="queryForm.searchWord" placeholder="标题/内容" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="类型" class="smart-query-form-item">
|
||||
<smart-enum-select v-model:value="queryForm.messageType" placeholder="消息类型" enum-name="MESSAGE_TYPE_ENUM" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="消息时间" class="smart-query-form-item">
|
||||
<a-space direction="vertical" :size="12">
|
||||
<a-range-picker v-model:value="searchDate" @change="dateChange" style="width: 220px" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="已读" class="smart-query-form-item">
|
||||
<a-radio-group v-model:value="queryForm.readFlag" @change="quickQuery">
|
||||
<a-radio-button :value="null">全部</a-radio-button>
|
||||
<a-radio-button :value="false">未读</a-radio-button>
|
||||
<a-radio-button :value="true">已读</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="quickQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table size="small" :dataSource="tableData" :columns="columns" rowKey="messageId" :pagination="false" bordered>
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<template v-if="column.dataIndex === 'messageType'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('MESSAGE_TYPE_ENUM', text) }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'readFlag'">
|
||||
<span v-show="record.readFlag">已读</span>
|
||||
<span v-show="!record.readFlag" style="color: red">未读</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'title'">
|
||||
<span v-show="record.readFlag">
|
||||
<a @click="toDetail(record)" style="color: #8c8c8c"
|
||||
>【{{ $smartEnumPlugin.getDescByValue('MESSAGE_TYPE_ENUM', record.messageType) }}】{{ text }}</a
|
||||
>
|
||||
</span>
|
||||
<span v-show="!record.readFlag">
|
||||
<a @click="toDetail(record)">【{{ $smartEnumPlugin.getDescByValue('MESSAGE_TYPE_ENUM', record.messageType) }}】{{ text }} </a>
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="ajaxQuery"
|
||||
@showSizeChange="ajaxQuery"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MessageDetail ref="messageDetailRef" @refresh="ajaxQuery" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { messageApi } from '/src/api/support/message-api';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select//index.vue';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
import MessageDetail from './components/message-detail.vue';
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
title: '消息',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: '已读',
|
||||
width: 80,
|
||||
dataIndex: 'readFlag',
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
]);
|
||||
|
||||
const queryFormState = {
|
||||
searchWord: '',
|
||||
messageType: null,
|
||||
dataId: null,
|
||||
readFlag: null,
|
||||
endDate: null,
|
||||
startDate: null,
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
searchCount: true,
|
||||
receiverType: null,
|
||||
receiverId: null,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
// 日期选择
|
||||
let searchDate = ref();
|
||||
|
||||
function dateChange(dates, dateStrings) {
|
||||
queryForm.startDate = dateStrings[0];
|
||||
queryForm.endDate = dateStrings[1];
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
searchDate.value = [];
|
||||
Object.assign(queryForm, queryFormState);
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
function quickQuery() {
|
||||
queryForm.pageNum = 1;
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
// 查询
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await messageApi.queryMessage(queryForm);
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- 详情 -----------------------------------
|
||||
|
||||
const messageDetailRef = ref();
|
||||
|
||||
function toDetail(message) {
|
||||
messageDetailRef.value.show(message);
|
||||
}
|
||||
|
||||
onMounted(ajaxQuery);
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<NoticeEmployeeList />
|
||||
</template>
|
||||
<script setup>
|
||||
import NoticeEmployeeList from '/@/views/business/oa/notice/notice-employee-list.vue';
|
||||
</script>
|
||||
@@ -0,0 +1,193 @@
|
||||
<!--
|
||||
* 操作记录 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-06-02 20:23:08
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="请求时间" class="smart-query-form-item">
|
||||
<a-range-picker @change="changeCreateDate" v-model:value="createDateRange" :presets="defaultChooseTimeRange" style="width: 240px" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="快速筛选" class="smart-query-form-item">
|
||||
<a-radio-group v-model:value="queryForm.successFlag" @change="onSearch">
|
||||
<a-radio-button :value="undefined">全部</a-radio-button>
|
||||
<a-radio-button :value="true">成功</a-radio-button>
|
||||
<a-radio-button :value="false">失败</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="ajaxQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table size="small" :loading="tableLoading" :dataSource="tableData" :columns="columns" bordered rowKey="operateLogId" :pagination="false">
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'successFlag'">
|
||||
<a-tag :color="text ? 'success' : 'error'">{{ text ? '成功' : '失败' }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'userAgent'">
|
||||
<div>{{ record.browser }} / {{ record.os }} / {{ record.device }}</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button @click="showDetail(record.operateLogId)" type="link">详情</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="ajaxQuery"
|
||||
@showSizeChange="ajaxQuery"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<OperateLogDetailModal ref="detailModal" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import OperateLogDetailModal from '/@/views/support/operate-log/operate-log-detail-modal.vue';
|
||||
import { operateLogApi } from '/@/api/support/operate-log-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { defaultTimeRanges } from '/@/lib/default-time-ranges';
|
||||
import uaparser from 'ua-parser-js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '操作模块',
|
||||
dataIndex: 'module',
|
||||
ellipsis: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '操作内容',
|
||||
dataIndex: 'content',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'IP地区',
|
||||
dataIndex: 'ipRegion',
|
||||
ellipsis: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '客户端',
|
||||
dataIndex: 'userAgent',
|
||||
ellipsis: true,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '结果',
|
||||
dataIndex: 'successFlag',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 60,
|
||||
},
|
||||
]);
|
||||
|
||||
const queryFormState = {
|
||||
userName: '',
|
||||
successFlag: undefined,
|
||||
startDate: undefined,
|
||||
endDate: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
const createDateRange = ref([]);
|
||||
const defaultChooseTimeRange = defaultTimeRanges;
|
||||
// 时间变动
|
||||
function changeCreateDate(dates, dateStrings) {
|
||||
queryForm.startDate = dateStrings[0];
|
||||
queryForm.endDate = dateStrings[1];
|
||||
}
|
||||
|
||||
const tableLoading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const total = ref(0);
|
||||
|
||||
function resetQuery() {
|
||||
Object.assign(queryForm, queryFormState);
|
||||
createDateRange.value = [];
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
ajaxQuery();
|
||||
}
|
||||
|
||||
async function ajaxQuery() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let responseModel = await operateLogApi.queryListLogin(queryForm);
|
||||
|
||||
for (const e of responseModel.data.list) {
|
||||
if (!e.userAgent) {
|
||||
continue;
|
||||
}
|
||||
let ua = uaparser(e.userAgent);
|
||||
e.browser = ua.browser.name;
|
||||
e.os = ua.os.name;
|
||||
e.device = ua.device.vendor ? ua.device.vendor + ua.device.model : '';
|
||||
}
|
||||
|
||||
const list = responseModel.data.list;
|
||||
total.value = responseModel.data.total;
|
||||
tableData.value = list;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(ajaxQuery);
|
||||
|
||||
// ---------------------- 详情 ----------------------
|
||||
const detailModal = ref();
|
||||
function showDetail(operateLogId) {
|
||||
detailModal.value.show(operateLogId);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="password-container">
|
||||
<!-- 页面标题-->
|
||||
<div class="header-title">修改密码</div>
|
||||
<!-- 内容区域-->
|
||||
<div class="password-form-area">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<a-form-item label="原密码" name="oldPassword">
|
||||
<a-input class="form-item" v-model:value.trim="form.oldPassword" type="password" placeholder="请输入原密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="新密码" name="newPassword" :help="tips">
|
||||
<a-input class="form-item" v-model:value.trim="form.newPassword" type="password" placeholder="请输入新密码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="确认密码" name="confirmPwd" :help="tips">
|
||||
<a-input class="form-item" v-model:value.trim="form.confirmPwd" type="password" placeholder="请输入确认密码" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button type="primary" @click="onSubmit">修改密码</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
|
||||
import { employeeApi } from '/@/api/system/employee-api.js';
|
||||
import { smartSentry } from '/@/lib/smart-sentry.js';
|
||||
|
||||
const formRef = ref();
|
||||
const tips = '密码长度8-20位且包含大写字母、小写字母、数字三种'; //校验规则
|
||||
const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
|
||||
|
||||
const rules = {
|
||||
oldPassword: [{ required: true, message: '请输入原密码' }],
|
||||
newPassword: [{ required: true, type: 'string', pattern: reg, message: '密码格式错误' }],
|
||||
confirmPwd: [{ required: true, type: 'string', pattern: reg, message: '请输入确认密码' }],
|
||||
};
|
||||
|
||||
const formDefault = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
};
|
||||
let form = reactive({
|
||||
...formDefault,
|
||||
});
|
||||
|
||||
async function onSubmit() {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
if (form.newPassword !== form.confirmPwd) {
|
||||
message.error('新密码与确认密码不一致');
|
||||
return;
|
||||
}
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.updateEmployeePassword(form);
|
||||
message.success('修改成功');
|
||||
form.oldPassword = '';
|
||||
form.newPassword = '';
|
||||
form.confirmPwd = '';
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.password-container {
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.password-form-area {
|
||||
margin-top: 30px;
|
||||
|
||||
.form-item {
|
||||
width: 500px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="account-container">
|
||||
<!--菜单列-->
|
||||
<div class="account-menu-list">
|
||||
<a-menu v-model:selectedKeys="selectedKeys" mode="inline" @click="selectMenu($event.key)">
|
||||
<a-menu-item v-for="item in menuList" :key="item.menuId">
|
||||
<span v-if="item.menuId === 'message'">
|
||||
{{ item.menuName }}
|
||||
<a-badge :count="unreadMessageCount" style="margin-left: 10px" />
|
||||
</span>
|
||||
<span v-if="item.menuId !== 'message'">{{ item.menuName }} </span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
<!--内容区域-->
|
||||
<div class="account-content">
|
||||
<component :is="selectedMenu.components" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { ACCOUNT_MENU } from '/@/views/system/account/account-menu.js';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useUserStore } from '/@/store/modules/system/user.js';
|
||||
|
||||
// 菜单展示
|
||||
let menuList = computed(() => {
|
||||
return _.values(ACCOUNT_MENU);
|
||||
});
|
||||
// 选中的菜单
|
||||
let selectedMenu = ref({ menuId: 0 });
|
||||
let selectedKeys = computed(() => {
|
||||
return _.isEmpty(selectedMenu.value) ? [] : [selectedMenu.value.menuId];
|
||||
});
|
||||
|
||||
function selectMenu(menuId) {
|
||||
selectedMenu.value = menuList.value.find((e) => e.menuId === menuId);
|
||||
}
|
||||
|
||||
// ------------------------- 未读消息数量 -------------------------
|
||||
const unreadMessageCount = computed(() => {
|
||||
return useUserStore().unreadMessageCount;
|
||||
});
|
||||
|
||||
// ------------------------- 绑定路由参数 -------------------------
|
||||
const route = useRoute();
|
||||
onMounted(() => {
|
||||
if (_.isEmpty(menuList.value)) {
|
||||
return;
|
||||
}
|
||||
let menuId;
|
||||
if (route.query.menuId) {
|
||||
menuId = route.query.menuId;
|
||||
} else {
|
||||
let firstMenu = menuList.value[0];
|
||||
menuId = firstMenu.menuId;
|
||||
}
|
||||
selectMenu(menuId);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery, oldQuery) => {
|
||||
let menuId;
|
||||
if (route.query.menuId) {
|
||||
menuId = route.query.menuId;
|
||||
} else {
|
||||
let firstMenu = menuList.value[0];
|
||||
menuId = firstMenu.menuId;
|
||||
}
|
||||
selectMenu(menuId);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.account-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
padding: 20px 0;
|
||||
|
||||
.account-menu-list {
|
||||
width: 180px;
|
||||
height: calc(100% - 100);
|
||||
border-right: solid 1px #efefef;
|
||||
}
|
||||
|
||||
.account-content {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="部门名称" class="smart-query-form-item">
|
||||
<a-input style="width: 300px" v-model:value="keywords" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button-group>
|
||||
<a-button v-privilege="'support:department:query'" type="primary" @click="onSearch">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button v-privilege="'support:department:query'" @click="resetQuery">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button v-privilege="'system:department:add'" type="primary" @click="addDepartment" class="smart-margin-left20">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-card size="small" :bordered="true">
|
||||
<a-table
|
||||
size="small"
|
||||
bordered
|
||||
:loading="tableLoading"
|
||||
rowKey="departmentId"
|
||||
:columns="columns"
|
||||
:data-source="departmentTreeData"
|
||||
:defaultExpandAllRows="false"
|
||||
:defaultExpandedRowKeys="defaultExpandedRowList"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button @click="addDepartment(record)" v-privilege="'system:department:add'" type="link">添加下级</a-button>
|
||||
<a-button @click="updateDepartment(record)" v-privilege="'system:department:update'" type="link">编辑</a-button>
|
||||
<a-button
|
||||
danger
|
||||
v-if="record.departmentId !== topDepartmentId"
|
||||
v-privilege="'system:department:delete'"
|
||||
@click="deleteDepartment(record.departmentId)"
|
||||
type="link"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 添加编辑部门弹窗 -->
|
||||
<DepartmentFormModal ref="departmentFormModal" @refresh="queryDepartmentTree" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch, createVNode } from 'vue';
|
||||
import { departmentApi } from '/src/api/system/department-api';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import _ from 'lodash';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
import DepartmentFormModal from './components/department-form-modal.vue';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
const DEPARTMENT_PARENT_ID = 0;
|
||||
|
||||
// ----------------------- 筛选 ---------------------
|
||||
const keywords = ref('');
|
||||
|
||||
// ----------------------- 部门树的展示 ---------------------
|
||||
const tableLoading = ref(false);
|
||||
|
||||
const topDepartmentId = ref();
|
||||
// 所有部门列表
|
||||
const departmentList = ref([]);
|
||||
// 部门树形数据
|
||||
const departmentTreeData = ref([]);
|
||||
// 存放部门id和部门,用于查找
|
||||
const idInfoMap = ref(new Map());
|
||||
// 默认展开的行
|
||||
const defaultExpandedRowList = reactive([]);
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '部门名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'managerName',
|
||||
key: 'managerName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
},
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
queryDepartmentTree();
|
||||
});
|
||||
|
||||
// 查询部门列表并构建 部门树
|
||||
async function queryDepartmentTree() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
let res = await departmentApi.queryAllDepartment();
|
||||
let data = res.data;
|
||||
|
||||
data.forEach((e) => {
|
||||
idInfoMap.value.set(e.departmentId, e);
|
||||
});
|
||||
|
||||
departmentList.value = data;
|
||||
departmentTreeData.value = buildDepartmentTree(data, DEPARTMENT_PARENT_ID);
|
||||
|
||||
// 默认显示 最顶级ID为列表中返回的第一条数据的ID
|
||||
if (!_.isEmpty(departmentTreeData.value) && departmentTreeData.value.length > 0) {
|
||||
topDepartmentId.value = departmentTreeData.value[0].departmentId;
|
||||
}
|
||||
|
||||
defaultExpandedRowList.value = [];
|
||||
defaultExpandedRowList.push(topDepartmentId.value);
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建部门树
|
||||
function buildDepartmentTree(data, parentId) {
|
||||
let children = data.filter((e) => e.parentId === parentId) || [];
|
||||
if (!_.isEmpty(children)) {
|
||||
children.forEach((e) => {
|
||||
e.children = buildDepartmentTree(data, e.departmentId);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 重置
|
||||
function resetQuery() {
|
||||
keywords.value = '';
|
||||
onSearch();
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch() {
|
||||
if (!keywords.value) {
|
||||
departmentTreeData.value = buildDepartmentTree(departmentList.value, DEPARTMENT_PARENT_ID);
|
||||
return;
|
||||
}
|
||||
let originData = departmentList.value.concat();
|
||||
if (!originData) {
|
||||
return;
|
||||
}
|
||||
// 筛选出名称符合的部门
|
||||
let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
|
||||
let filterDepartmentList = [];
|
||||
// 循环筛选出的部门 构建部门树
|
||||
filterDepartment.forEach((e) => {
|
||||
recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
|
||||
});
|
||||
departmentTreeData.value = buildDepartmentTree(filterDepartmentList, DEPARTMENT_PARENT_ID);
|
||||
}
|
||||
|
||||
// 根据ID递归筛选部门
|
||||
function recursionFilterDepartment(resList, id, unshift) {
|
||||
let info = idInfoMap.value.get(id);
|
||||
if (!info || resList.some((e) => e.departmentId === id)) {
|
||||
return;
|
||||
}
|
||||
if (unshift) {
|
||||
resList.unshift(info);
|
||||
} else {
|
||||
resList.push(info);
|
||||
}
|
||||
if (info.parentId && info.parentId !== 0) {
|
||||
recursionFilterDepartment(resList, info.parentId, unshift);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 表单操作:添加部门/修改部门/删除部门/上下移动 ---------------------
|
||||
const departmentFormModal = ref();
|
||||
// 添加
|
||||
function addDepartment(e) {
|
||||
let data = {
|
||||
departmentId: 0,
|
||||
name: '',
|
||||
parentId: e.departmentId || null,
|
||||
};
|
||||
departmentFormModal.value.showModal(data);
|
||||
}
|
||||
// 编辑
|
||||
function updateDepartment(e) {
|
||||
departmentFormModal.value.showModal(e);
|
||||
}
|
||||
|
||||
// 删除
|
||||
function deleteDepartment(id) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: '确定要删除该部门吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await departmentApi.deleteDepartment(id);
|
||||
await queryDepartmentTree();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
* 当前所选部门的子部门 人员管理右上半部分
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="child-dept-container">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item v-for="(item, index) in props.breadcrumb" :key="index">
|
||||
{{ item }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
|
||||
<a-list class="department-list" :data-source="props.selectedDepartmentChildren">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="department-item" @click="selectTree(item.departmentId)">
|
||||
{{ item.name }}
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import emitter from '../../department-mitt.js';
|
||||
|
||||
const props = defineProps({
|
||||
breadcrumb: Array,
|
||||
selectedDepartmentChildren: Array,
|
||||
});
|
||||
|
||||
function selectTree(id) {
|
||||
emitter.emit('selectTree', id);
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-list-item) {
|
||||
padding: 6px 0px;
|
||||
}
|
||||
.child-dept-container {
|
||||
.department-list-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.department-list {
|
||||
height: 170px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.department-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,245 @@
|
||||
<!--
|
||||
* 部门树形结构
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="tree-container">
|
||||
<a-row class="smart-margin-bottom10">
|
||||
<a-input v-model:value.trim="keywords" placeholder="请输入部门名称" />
|
||||
</a-row>
|
||||
<a-tree
|
||||
v-if="!_.isEmpty(departmentTreeData)"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
class="tree"
|
||||
:treeData="departmentTreeData"
|
||||
:fieldNames="{ title: 'name', key: 'departmentId', value: 'departmentId' }"
|
||||
style="width: 100%; overflow-x: auto"
|
||||
:style="[!height ? '' : { height: `${height}px`, overflowY: 'auto' }]"
|
||||
:checkable="props.checkable"
|
||||
:checkStrictly="props.checkStrictly"
|
||||
:selectable="!props.checkable"
|
||||
:defaultExpandAll="true"
|
||||
@select="treeSelectChange"
|
||||
>
|
||||
<template #title="item">
|
||||
<div>{{ item.name }}</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
<div class="no-data" v-else>暂无结果</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { departmentApi } from '/src/api/system/department-api';
|
||||
import departmentEmitter from '../../department-mitt';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
const DEPARTMENT_PARENT_ID = 0;
|
||||
|
||||
// ----------------------- 组件参数 ---------------------
|
||||
|
||||
const props = defineProps({
|
||||
// 是否可以选中
|
||||
checkable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 父子节点选中状态不再关联
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 树高度 超出出滚动条
|
||||
height: Number,
|
||||
// 显示菜单
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------- 部门树的展示 ---------------------
|
||||
const topDepartmentId = ref();
|
||||
// 所有部门列表
|
||||
const departmentList = ref([]);
|
||||
// 部门树形数据
|
||||
const departmentTreeData = ref([]);
|
||||
// 存放部门id和部门,用于查找
|
||||
const idInfoMap = ref(new Map());
|
||||
|
||||
onMounted(() => {
|
||||
queryDepartmentTree();
|
||||
});
|
||||
|
||||
// 刷新
|
||||
async function refresh() {
|
||||
await queryDepartmentTree();
|
||||
if (currentSelectedDepartmentId.value) {
|
||||
selectTree(currentSelectedDepartmentId.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询部门列表并构建 部门树
|
||||
async function queryDepartmentTree() {
|
||||
let res = await departmentApi.queryAllDepartment();
|
||||
let data = res.data;
|
||||
departmentList.value = data;
|
||||
departmentTreeData.value = buildDepartmentTree(data, DEPARTMENT_PARENT_ID);
|
||||
|
||||
data.forEach((e) => {
|
||||
idInfoMap.value.set(e.departmentId, e);
|
||||
});
|
||||
|
||||
// 默认显示 最顶级ID为列表中返回的第一条数据的ID
|
||||
if (!_.isEmpty(departmentTreeData.value) && departmentTreeData.value.length > 0) {
|
||||
topDepartmentId.value = departmentTreeData.value[0].departmentId;
|
||||
}
|
||||
|
||||
selectTree(departmentTreeData.value[0].departmentId);
|
||||
}
|
||||
|
||||
// 构建部门树
|
||||
function buildDepartmentTree(data, parentId) {
|
||||
let children = data.filter((e) => e.parentId === parentId) || [];
|
||||
children.forEach((e) => {
|
||||
e.children = buildDepartmentTree(data, e.departmentId);
|
||||
});
|
||||
updateDepartmentPreIdAndNextId(children);
|
||||
return children;
|
||||
}
|
||||
|
||||
// 更新树的前置id和后置id
|
||||
function updateDepartmentPreIdAndNextId(data) {
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
if (index === 0) {
|
||||
data[index].nextId = data.length > 1 ? data[1].departmentId : undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index === data.length - 1) {
|
||||
data[index].preId = data[index - 1].departmentId;
|
||||
data[index].nextId = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
data[index].preId = data[index - 1].departmentId;
|
||||
data[index].nextId = data[index + 1].departmentId;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 树的选中 ---------------------
|
||||
const selectedKeys = ref([]);
|
||||
const checkedKeys = ref([]);
|
||||
const breadcrumb = ref([]);
|
||||
const currentSelectedDepartmentId = ref();
|
||||
const selectedDepartmentChildren = ref([]);
|
||||
|
||||
departmentEmitter.on('selectTree', selectTree);
|
||||
|
||||
function selectTree(id) {
|
||||
selectedKeys.value = [id];
|
||||
treeSelectChange(selectedKeys.value);
|
||||
}
|
||||
|
||||
function treeSelectChange(idList) {
|
||||
if (_.isEmpty(idList)) {
|
||||
breadcrumb.value = [];
|
||||
selectedDepartmentChildren.value = [];
|
||||
return;
|
||||
}
|
||||
let id = idList[0];
|
||||
selectedDepartmentChildren.value = departmentList.value.filter((e) => e.parentId == id);
|
||||
let filterDepartmentList = [];
|
||||
recursionFilterDepartment(filterDepartmentList, id, true);
|
||||
breadcrumb.value = filterDepartmentList.map((e) => e.name);
|
||||
}
|
||||
|
||||
// ----------------------- 筛选 ---------------------
|
||||
const keywords = ref('');
|
||||
watch(
|
||||
() => keywords.value,
|
||||
() => {
|
||||
onSearch();
|
||||
}
|
||||
);
|
||||
|
||||
// 筛选
|
||||
function onSearch() {
|
||||
if (!keywords.value) {
|
||||
departmentTreeData.value = buildDepartmentTree(departmentList.value, DEPARTMENT_PARENT_ID);
|
||||
return;
|
||||
}
|
||||
let originData = departmentList.value.concat();
|
||||
if (!originData) {
|
||||
return;
|
||||
}
|
||||
// 筛选出名称符合的部门
|
||||
let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
|
||||
let filterDepartmentList = [];
|
||||
// 循环筛选出的部门 构建部门树
|
||||
filterDepartment.forEach((e) => {
|
||||
recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
|
||||
});
|
||||
|
||||
departmentTreeData.value = buildDepartmentTree(filterDepartmentList, DEPARTMENT_PARENT_ID);
|
||||
}
|
||||
|
||||
// 根据ID递归筛选部门
|
||||
function recursionFilterDepartment(resList, id, unshift) {
|
||||
let info = idInfoMap.value.get(id);
|
||||
if (!info || resList.some((e) => e.departmentId == id)) {
|
||||
return;
|
||||
}
|
||||
if (unshift) {
|
||||
resList.unshift(info);
|
||||
} else {
|
||||
resList.push(info);
|
||||
}
|
||||
if (info.parentId && info.parentId != 0) {
|
||||
recursionFilterDepartment(resList, info.parentId, unshift);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
departmentEmitter.all.clear();
|
||||
});
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
queryDepartmentTree,
|
||||
selectedDepartmentChildren,
|
||||
breadcrumb,
|
||||
selectedKeys,
|
||||
checkedKeys,
|
||||
keywords,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.tree-container {
|
||||
height: 100%;
|
||||
.tree {
|
||||
height: 618px;
|
||||
margin-top: 10px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sort-flag-row {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sort-span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.no-data {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<!--
|
||||
* 员工 表单 弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-drawer
|
||||
:title="form.employeeId ? '编辑' : '添加'"
|
||||
:width="600"
|
||||
:open="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
@close="onClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-alert message="超管需要直接在数据库表 t_employee修改哦" type="error" closable />
|
||||
<br />
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<a-form-item label="姓名" name="actualName">
|
||||
<a-input v-model:value.trim="form.actualName" placeholder="请输入姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value.trim="form.phone" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" name="departmentId">
|
||||
<DepartmentTreeSelect ref="departmentTreeSelect" width="100%" :init="false" v-model:value="form.departmentId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="登录名" name="loginName">
|
||||
<a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
|
||||
<p class="hint">初始密码默认为:随机</p>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="disabledFlag">
|
||||
<a-select v-model:value="form.disabledFlag" placeholder="请选择状态">
|
||||
<a-select-option :value="0">启用</a-select-option>
|
||||
<a-select-option :value="1">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="职务" name="positionId">
|
||||
<PositionSelect v-model:value="form.positionId" placeholder="请选择职务" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="角色" name="roleIdList">
|
||||
<a-select mode="multiple" v-model:value="form.roleIdList" optionFilterProp="title" placeholder="请选择角色">
|
||||
<a-select-option v-for="item in roleList" :key="item.roleId" :title="item.roleName">{{ item.roleName }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="footer">
|
||||
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
|
||||
<a-button type="primary" style="margin-right: 8px" @click="onSubmit(false)">保存</a-button>
|
||||
<a-button v-if="!form.employeeId" type="primary" @click="onSubmit(true)">保存并继续添加</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
import { employeeApi } from '/src/api/system/employee-api';
|
||||
import { roleApi } from '/src/api/system/role-api';
|
||||
import DepartmentTreeSelect from '/src/components/system/department-tree-select/index.vue';
|
||||
import SmartEnumSelect from '/src/components/framework/smart-enum-select/index.vue';
|
||||
import PositionSelect from '/src/components/system/position-select/index.vue';
|
||||
import { GENDER_ENUM } from '/src/constants/common-const';
|
||||
import { regular } from '/src/constants/regular-const';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
const departmentTreeSelect = ref();
|
||||
// emit
|
||||
const emit = defineEmits(['refresh', 'show-account']);
|
||||
|
||||
// ----------------------- 显示/隐藏 ---------------------
|
||||
|
||||
const visible = ref(false); // 是否展示抽屉
|
||||
// 隐藏
|
||||
function onClose() {
|
||||
reset();
|
||||
visible.value = false;
|
||||
}
|
||||
// 显示
|
||||
async function showDrawer(rowData) {
|
||||
Object.assign(form, formDefault);
|
||||
if (rowData && !_.isEmpty(rowData)) {
|
||||
Object.assign(form, rowData);
|
||||
}
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
queryAllRole();
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------- 表单显示 ---------------------
|
||||
|
||||
const roleList = ref([]); //角色列表
|
||||
async function queryAllRole() {
|
||||
let res = await roleApi.queryAll();
|
||||
roleList.value = res.data;
|
||||
}
|
||||
|
||||
const formRef = ref(); // 组件ref
|
||||
const formDefault = {
|
||||
employeeId: undefined,
|
||||
actualName: undefined,
|
||||
departmentId: undefined,
|
||||
disabledFlag: 0,
|
||||
leaveFlag: 0,
|
||||
gender: GENDER_ENUM.MAN.value,
|
||||
loginName: undefined,
|
||||
phone: undefined,
|
||||
roleIdList: undefined,
|
||||
positionId: undefined,
|
||||
};
|
||||
|
||||
let form = reactive(_.cloneDeep(formDefault));
|
||||
function reset() {
|
||||
Object.assign(form, formDefault);
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
|
||||
// ----------------------- 表单提交 ---------------------
|
||||
// 表单规则
|
||||
const rules = {
|
||||
actualName: [
|
||||
{ required: true, message: '姓名不能为空' },
|
||||
{ max: 30, message: '姓名不能大于30个字符', trigger: 'blur' },
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空' },
|
||||
{ pattern: regular.phone, message: '请输入正确的手机号码', trigger: 'blur' },
|
||||
],
|
||||
loginName: [
|
||||
{ required: true, message: '登录账号不能为空' },
|
||||
{ max: 30, message: '登录账号不能大于30个字符', trigger: 'blur' },
|
||||
],
|
||||
gender: [{ required: true, message: '性别不能为空' }],
|
||||
departmentId: [{ required: true, message: '部门不能为空' }],
|
||||
disabledFlag: [{ required: true, message: '状态不能为空' }],
|
||||
leaveFlag: [{ required: true, message: '在职状态不能为空' }],
|
||||
};
|
||||
|
||||
// 校验表单
|
||||
function validateForm(formRef) {
|
||||
return new Promise((resolve) => {
|
||||
formRef
|
||||
.validate()
|
||||
.then(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 提交数据
|
||||
async function onSubmit(keepAdding) {
|
||||
let validateFormRes = await validateForm(formRef.value);
|
||||
if (!validateFormRes) {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
return;
|
||||
}
|
||||
SmartLoading.show();
|
||||
if (form.employeeId) {
|
||||
await updateEmployee(keepAdding);
|
||||
} else {
|
||||
await addEmployee(keepAdding);
|
||||
}
|
||||
}
|
||||
|
||||
async function addEmployee(keepAdding) {
|
||||
try {
|
||||
let { data } = await employeeApi.addEmployee(form);
|
||||
message.success('添加成功');
|
||||
emit('show-account', form.loginName, data);
|
||||
if (keepAdding) {
|
||||
reset();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
async function updateEmployee(keepAdding) {
|
||||
try {
|
||||
let result = await employeeApi.updateEmployee(form);
|
||||
message.success('更新成功');
|
||||
if (keepAdding) {
|
||||
reset();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
showDrawer,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
text-align: right;
|
||||
z-index: 1;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 5px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,412 @@
|
||||
<!--
|
||||
* 员工 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="employee-container">
|
||||
<div class="header">
|
||||
<a-typography-title :level="5">部门人员</a-typography-title>
|
||||
<div class="query-operate">
|
||||
<a-radio-group v-model:value="params.disabledFlag" style="margin: 8px; flex-shrink: 0" @change="queryEmployeeByKeyword(false)">
|
||||
<a-radio-button :value="undefined">全部</a-radio-button>
|
||||
<a-radio-button :value="false">启用</a-radio-button>
|
||||
<a-radio-button :value="true">禁用</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-input-search v-model:value.trim="params.keyword" placeholder="姓名/手机号/登录账号" @search="queryEmployeeByKeyword(true)">
|
||||
<template #enterButton>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<a-button @click="reset" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a-button class="btn" type="primary" @click="showDrawer" v-privilege="'system:employee:add'">添加成员</a-button>
|
||||
<a-button class="btn" @click="updateEmployeeDepartment" v-privilege="'system:employee:department:update'">调整部门</a-button>
|
||||
<a-button class="btn" @click="batchDelete" v-privilege="'system:employee:delete'">批量删除</a-button>
|
||||
|
||||
<span class="smart-table-column-operate">
|
||||
<TableOperator v-model="columns" :tableId="TABLE_ID_CONST.SYSTEM.EMPLOYEE" :refresh="queryEmployee" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
:loading="tableLoading"
|
||||
:scroll="{ x: 1500 }"
|
||||
row-key="employeeId"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<template v-if="column.dataIndex === 'administratorFlag'">
|
||||
<a-tag color="error" v-if="text">超管</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'disabledFlag'">
|
||||
<a-tag :color="text ? 'error' : 'processing'">{{ text ? '禁用' : '启用' }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'gender'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('GENDER_ENUM', text) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operate'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button v-privilege="'system:employee:update'" type="link" size="small" @click="showDrawer(record)">编辑</a-button>
|
||||
<a-button
|
||||
v-privilege="'system:employee:password:reset'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="resetPassword(record.employeeId, record.loginName)"
|
||||
>重置密码</a-button
|
||||
>
|
||||
<a-button v-privilege="'system:employee:disabled'" type="link" @click="updateDisabled(record.employeeId, record.disabledFlag)">{{
|
||||
record.disabledFlag ? '启用' : '禁用'
|
||||
}}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="params.pageSize"
|
||||
v-model:current="params.pageNum"
|
||||
v-model:pageSize="params.pageSize"
|
||||
:total="total"
|
||||
@change="queryEmployee"
|
||||
@showSizeChange="queryEmployee"
|
||||
:show-total="showTableTotal"
|
||||
/>
|
||||
</div>
|
||||
<EmployeeFormModal ref="employeeFormModal" @refresh="queryEmployee" @show-account="showAccount" />
|
||||
<EmployeeDepartmentFormModal ref="employeeDepartmentFormModal" @refresh="queryEmployee" />
|
||||
<EmployeePasswordDialog ref="employeePasswordDialog" />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { computed, createVNode, reactive, ref, watch } from 'vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { PAGE_SIZE } from '/@/constants/common-const';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import EmployeeFormModal from '../employee-form-modal/index.vue';
|
||||
import EmployeeDepartmentFormModal from '../employee-department-form-modal/index.vue';
|
||||
import EmployeePasswordDialog from '../employee-password-dialog/index.vue';
|
||||
import { PAGE_SIZE_OPTIONS, showTableTotal } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
|
||||
const props = defineProps({
|
||||
departmentId: Number,
|
||||
breadcrumb: Array,
|
||||
});
|
||||
|
||||
//-------------回显账号密码信息----------
|
||||
let employeePasswordDialog = ref();
|
||||
function showAccount(accountName, passWord) {
|
||||
employeePasswordDialog.value.showModal(accountName, passWord);
|
||||
}
|
||||
|
||||
// ----------------------- 表格/列表/ 搜索 ---------------------
|
||||
//字段
|
||||
const columns = ref([
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'actualName',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'loginName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '超管',
|
||||
dataIndex: 'administratorFlag',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'disabledFlag',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'positionName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roleNameList',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departmentName',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'employeeName',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operate',
|
||||
width: 140,
|
||||
},
|
||||
]);
|
||||
const tableData = ref();
|
||||
|
||||
let defaultParams = {
|
||||
departmentId: undefined,
|
||||
disabledFlag: false,
|
||||
keyword: undefined,
|
||||
searchCount: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
sortItemList: undefined,
|
||||
};
|
||||
const params = reactive({ ...defaultParams });
|
||||
const total = ref(0);
|
||||
|
||||
// 搜索重置
|
||||
function reset() {
|
||||
Object.assign(params, defaultParams);
|
||||
queryEmployee();
|
||||
}
|
||||
|
||||
const tableLoading = ref(false);
|
||||
// 查询
|
||||
async function queryEmployee() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
params.departmentId = props.departmentId;
|
||||
let res = await employeeApi.queryEmployee(params);
|
||||
for (const item of res.data.list) {
|
||||
item.roleNameList = _.join(item.roleNameList, ',');
|
||||
}
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
// 清除选中
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据关键字 查询
|
||||
async function queryEmployeeByKeyword(allDepartment) {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
params.pageNum = 1;
|
||||
params.departmentId = allDepartment ? undefined : props.departmentId;
|
||||
let res = await employeeApi.queryEmployee(params);
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
// 清除选中
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.departmentId,
|
||||
() => {
|
||||
if (props.departmentId !== params.departmentId) {
|
||||
params.pageNum = 1;
|
||||
queryEmployee();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// ----------------------- 多选操作 ---------------------
|
||||
|
||||
let selectedRowKeys = ref([]);
|
||||
let selectedRows = ref([]);
|
||||
// 是否有选中:用于 批量操作按钮的禁用
|
||||
const hasSelected = computed(() => selectedRowKeys.value.length > 0);
|
||||
|
||||
function onSelectChange(keyArray, selectRows) {
|
||||
selectedRowKeys.value = keyArray;
|
||||
selectedRows.value = selectRows;
|
||||
}
|
||||
|
||||
// 批量删除员工
|
||||
function batchDelete() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择要删除的员工');
|
||||
return;
|
||||
}
|
||||
const actualNameArray = selectedRows.value.map((e) => e.actualName);
|
||||
const employeeIdArray = selectedRows.value.map((e) => e.employeeId);
|
||||
Modal.confirm({
|
||||
title: '确定要删除如下员工吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: _.join(actualNameArray, ','),
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.batchDeleteEmployee(employeeIdArray);
|
||||
message.success('删除成功');
|
||||
queryEmployee();
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// 批量更新员工部门
|
||||
const employeeDepartmentFormModal = ref();
|
||||
|
||||
function updateEmployeeDepartment() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择要调整部门的员工');
|
||||
return;
|
||||
}
|
||||
const employeeIdArray = selectedRows.value.map((e) => e.employeeId);
|
||||
employeeDepartmentFormModal.value.showModal(employeeIdArray);
|
||||
}
|
||||
|
||||
// ----------------------- 添加、修改、禁用、重置密码 ------------------------------------
|
||||
|
||||
const employeeFormModal = ref(); //组件
|
||||
|
||||
// 展示编辑弹窗
|
||||
function showDrawer(rowData) {
|
||||
let params = {};
|
||||
if (rowData) {
|
||||
params = _.cloneDeep(rowData);
|
||||
params.disabledFlag = params.disabledFlag ? 1 : 0;
|
||||
} else if (props.departmentId) {
|
||||
params.departmentId = props.departmentId;
|
||||
}
|
||||
employeeFormModal.value.showDrawer(params);
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
function resetPassword(id, name) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: '确定要重置密码吗?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let { data: passWord } = await employeeApi.resetPassword(id);
|
||||
message.success('重置成功');
|
||||
employeePasswordDialog.value.showModal(name, passWord);
|
||||
queryEmployee();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// 禁用 / 启用
|
||||
function updateDisabled(id, disabledFlag) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: `确定要${disabledFlag ? '启用' : '禁用'}吗?`,
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.updateDisabled(id);
|
||||
message.success(`${disabledFlag ? '启用' : '禁用'}成功`);
|
||||
queryEmployee();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.employee-container {
|
||||
height: 100%;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.query-operate {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin: 10px 0;
|
||||
.btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 组织架构
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<div class="height100">
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
<a-col :span="18" class="height100">
|
||||
<div class="employee-box height100">
|
||||
<!-- <DepartmentChildren style="flex-grow: 1" :breadcrumb="breadcrumb" :selectedDepartmentChildren="selectedDepartmentChildren" /> -->
|
||||
<EmployeeList style="flex-grow: 2.5" class="employee" :departmentId="selectedDepartmentId" />
|
||||
</div>
|
||||
</a-col>
|
||||
@@ -26,7 +25,6 @@
|
||||
<script setup>
|
||||
import _ from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
import DepartmentChildren from './components/department-children/index.vue';
|
||||
import DepartmentTree from './components/department-tree/index.vue';
|
||||
import EmployeeList from './components/employee-list/index.vue';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 登录
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="box-item desc">
|
||||
<div class="welcome">
|
||||
<p>欢迎登录 SmartAdmin V3</p>
|
||||
<p class="sub-welcome">高质量代码的快速开发平台</p>
|
||||
<p class="sub-welcome">「高质量代码、简洁、安全」的开发平台</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-item login">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 登录
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="box-item desc">
|
||||
<div class="welcome">
|
||||
<p>欢迎登录 SmartAdmin V3</p>
|
||||
<p class="sub-welcome">高质量代码的快速开发平台</p>
|
||||
<p class="sub-welcome">「高质量代码、简洁、安全」的开发平台</p>
|
||||
</div>
|
||||
<img class="welcome-img" :src="leftBg2" />
|
||||
</div>
|
||||
|
||||
@@ -24,19 +24,21 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item class="smart-query-form-item smart-margin-left10">
|
||||
<a-button type="primary" @click="query">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="query">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button class="smart-margin-left20" @click="moreQueryConditionFlag = !moreQueryConditionFlag">
|
||||
<template #icon>
|
||||
<MoreOutlined />
|
||||
@@ -64,14 +66,14 @@
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button v-privilege="'system:menu:add'" type="primary" size="small" @click="showDrawer">
|
||||
<a-button v-privilege="'system:menu:add'" type="primary" @click="showDrawer">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加菜单
|
||||
</a-button>
|
||||
|
||||
<a-button v-privilege="'system:menu:batchDelete'" type="primary" danger size="small" @click="batchDelete" :disabled="!hasSelected">
|
||||
<a-button v-privilege="'system:menu:batchDelete'" type="primary" danger @click="batchDelete" :disabled="!hasSelected">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<!--
|
||||
* 职务表
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
-->
|
||||
<template>
|
||||
<a-modal
|
||||
:title="form.positionId ? '编辑' : '添加'"
|
||||
width="600px"
|
||||
:open="visibleFlag"
|
||||
@cancel="onClose"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
forceRender
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 6 }">
|
||||
<a-form-item label="职务名称" name="positionName">
|
||||
<a-input style="width: 100%" v-model:value="form.positionName" placeholder="职务名称"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="职级" name="level">
|
||||
<a-input style="width: 100%" v-model:value="form.level" placeholder="职级"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number :min="0" :step="1" :precision="0" style="width: 100%" v-model:value="form.sort" placeholder="排序"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-input style="width: 100%" v-model:value="form.remark" placeholder="备注"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="onClose">取消</a-button>
|
||||
<a-button type="primary" @click="onSubmit">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, nextTick } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { positionApi } from '/@/api/system/position-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
// ------------------------ 事件 ------------------------
|
||||
|
||||
const emits = defineEmits(['reloadList']);
|
||||
|
||||
// ------------------------ 显示与隐藏 ------------------------
|
||||
// 是否显示
|
||||
const visibleFlag = ref(false);
|
||||
|
||||
function show (rowData) {
|
||||
Object.assign(form, formDefault);
|
||||
if (rowData && !_.isEmpty(rowData)) {
|
||||
Object.assign(form, rowData);
|
||||
}
|
||||
visibleFlag.value = true;
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate();
|
||||
});
|
||||
}
|
||||
|
||||
function onClose () {
|
||||
Object.assign(form, formDefault);
|
||||
visibleFlag.value = false;
|
||||
}
|
||||
|
||||
// ------------------------ 表单 ------------------------
|
||||
|
||||
// 组件ref
|
||||
const formRef = ref();
|
||||
|
||||
const formDefault = {
|
||||
positionId: undefined,
|
||||
positionName: undefined, //职务名称
|
||||
level: undefined,//职纪
|
||||
sort: 0,
|
||||
remark: undefined, //备注
|
||||
};
|
||||
|
||||
let form = reactive({ ...formDefault });
|
||||
|
||||
const rules = {
|
||||
positionName: [{ required: true, message: '请输入职务名称' }],
|
||||
};
|
||||
|
||||
// 点击确定,验证表单
|
||||
async function onSubmit () {
|
||||
try {
|
||||
await formRef.value.validateFields();
|
||||
save();
|
||||
} catch (err) {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
}
|
||||
}
|
||||
|
||||
// 新建、编辑API
|
||||
async function save () {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
if (form.positionId) {
|
||||
await positionApi.update(form);
|
||||
} else {
|
||||
await positionApi.add(form);
|
||||
}
|
||||
message.success('操作成功');
|
||||
emits('reloadList');
|
||||
onClose();
|
||||
} catch (err) {
|
||||
smartSentry.captureError(err);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,262 @@
|
||||
<!--
|
||||
* 职务表
|
||||
*
|
||||
* @Author: kaiyun
|
||||
* @Date: 2024-06-23 23:31:38
|
||||
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
|
||||
-->
|
||||
<template>
|
||||
<!---------- 查询表单form begin ----------->
|
||||
<a-form class="smart-query-form">
|
||||
<a-row class="smart-query-form-row">
|
||||
<a-form-item label="关键字查询" class="smart-query-form-item">
|
||||
<a-input style="width: 200px" v-model:value="queryForm.keywords" placeholder="关键字查询" />
|
||||
</a-form-item>
|
||||
<a-form-item class="smart-query-form-item">
|
||||
<a-button type="primary" @click="queryData">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<!---------- 查询表单form end ----------->
|
||||
|
||||
<a-card size="small" :bordered="false" :hoverable="true">
|
||||
<!---------- 表格操作行 begin ----------->
|
||||
<a-row class="smart-table-btn-block">
|
||||
<div class="smart-table-operate-block">
|
||||
<a-button @click="showForm" type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button @click="confirmBatchDelete" type="primary" danger :disabled="selectedRowKeyList.length === 0">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="smart-table-setting-block">
|
||||
<TableOperator v-model="columns" :tableId="TABLE_ID_CONST.SYSTEM.EMPLOYEE" :refresh="queryData" />
|
||||
</div>
|
||||
</a-row>
|
||||
<!---------- 表格操作行 end ----------->
|
||||
|
||||
<!---------- 表格 begin ----------->
|
||||
<a-table
|
||||
size="small"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
rowKey="positionId"
|
||||
bordered
|
||||
:loading="tableLoading"
|
||||
:pagination="false"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button @click="showForm(record)" type="link">编辑</a-button>
|
||||
<a-button @click="onDelete(record)" danger type="link">删除</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!---------- 表格 end ----------->
|
||||
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="queryData"
|
||||
@showSizeChange="queryData"
|
||||
:show-total="(total) => `共${total}条`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PositionForm ref="formRef" @reloadList="queryData" />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { positionApi } from '/@/api/system/position-api';
|
||||
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import PositionForm from './position-form.vue';
|
||||
import _ from 'lodash';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
// ---------------------------- 表格列 ----------------------------
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '职务名称',
|
||||
dataIndex: 'positionName',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '职级',
|
||||
dataIndex: 'level',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
width: 90,
|
||||
},
|
||||
]);
|
||||
|
||||
// ---------------------------- 查询数据表单和方法 ----------------------------
|
||||
|
||||
const queryFormState = {
|
||||
keywords: undefined, //关键字查询
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
// 查询表单form
|
||||
const queryForm = reactive({ ...queryFormState });
|
||||
// 表格加载loading
|
||||
const tableLoading = ref(false);
|
||||
// 表格数据
|
||||
const tableData = ref([]);
|
||||
// 总数
|
||||
const total = ref(0);
|
||||
|
||||
// 重置查询条件
|
||||
function resetQuery() {
|
||||
let pageSize = queryForm.pageSize;
|
||||
Object.assign(queryForm, queryFormState);
|
||||
queryForm.pageSize = pageSize;
|
||||
queryData();
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
async function queryData() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
let queryResult = await positionApi.queryPage(queryForm);
|
||||
tableData.value = queryResult.data.list;
|
||||
total.value = queryResult.data.total;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(queryData);
|
||||
|
||||
// ---------------------------- 添加/修改 ----------------------------
|
||||
const formRef = ref();
|
||||
|
||||
function showForm(data) {
|
||||
formRef.value.show(data);
|
||||
}
|
||||
|
||||
// ---------------------------- 单个删除 ----------------------------
|
||||
//确认删除
|
||||
function onDelete(data) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
requestDelete(data);
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
//请求删除
|
||||
async function requestDelete(data) {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await positionApi.delete(data.positionId);
|
||||
message.success('删除成功');
|
||||
queryData();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------- 批量删除 ----------------------------
|
||||
|
||||
// 选择表格行
|
||||
const selectedRowKeyList = ref([]);
|
||||
|
||||
function onSelectChange(selectedRowKeys) {
|
||||
selectedRowKeyList.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function confirmBatchDelete() {
|
||||
if (_.isEmpty(selectedRowKeyList.value)) {
|
||||
message.success('请选择要删除的数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要批量删除这些数据吗?',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
requestBatchDelete();
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
//请求批量删除
|
||||
async function requestBatchDelete() {
|
||||
try {
|
||||
SmartLoading.show();
|
||||
await positionApi.batchDelete(selectedRowKeyList.value);
|
||||
message.success('删除成功');
|
||||
queryData();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -46,8 +46,8 @@
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { inject, onMounted, ref, watch } from 'vue';
|
||||
import { roleApi } from '/@/api/system/role-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { roleApi } from '/src/api/system/role-api';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
const props = defineProps({
|
||||
value: Number,
|
||||
@@ -0,0 +1,274 @@
|
||||
<!--
|
||||
* 角色 员工 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div class="header">
|
||||
<div>
|
||||
关键字:
|
||||
<a-input style="width: 250px" v-model:value="queryForm.keywords" placeholder="姓名/手机号/登录账号" />
|
||||
<a-button class="button-style" v-if="selectRoleId" type="primary" @click="onSearch">搜索</a-button>
|
||||
<a-button class="button-style" v-if="selectRoleId" type="default" @click="resetQueryRoleEmployee">重置</a-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a-button class="button-style" v-if="selectRoleId" type="primary" @click="addRoleEmployee" v-privilege="'system:role:employee:add'"
|
||||
>添加员工</a-button
|
||||
>
|
||||
<a-button
|
||||
class="button-style"
|
||||
v-if="selectRoleId"
|
||||
type="primary"
|
||||
danger
|
||||
@click="batchDelete"
|
||||
v-privilege="'system:role:employee:batch:delete'"
|
||||
>批量移除</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:loading="tableLoading"
|
||||
:dataSource="tableData"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 400 }"
|
||||
rowKey="employeeId"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
|
||||
size="small"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ text, record, column }">
|
||||
<template v-if="column.dataIndex === 'disabledFlag'">
|
||||
<a-tag :color="text ? 'error' : 'processing'">{{ text ? '禁用' : '启用' }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'gender'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('GENDER_ENUM', text) }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operate'">
|
||||
<a @click="deleteEmployeeRole(record.employeeId)" v-privilege="'system:role:employee:delete'">移除</a>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="queryForm.pageSize"
|
||||
v-model:current="queryForm.pageNum"
|
||||
v-model:pageSize="queryForm.pageSize"
|
||||
:total="total"
|
||||
@change="queryRoleEmployee"
|
||||
@showSizeChange="queryRoleEmployee"
|
||||
:show-total="showTableTotal"
|
||||
/>
|
||||
</div>
|
||||
<EmployeeTableSelectModal ref="selectEmployeeModal" @selectData="selectData" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { roleApi } from '/src/api/system/role-api';
|
||||
import { PAGE_SIZE, showTableTotal, PAGE_SIZE_OPTIONS } from '/src/constants/common-const';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
import EmployeeTableSelectModal from '/src/components/system/employee-table-select-modal/index.vue';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
let selectRoleId = inject('selectRoleId');
|
||||
|
||||
// ----------------------- 员工列表:显示和搜索 ------------------------
|
||||
watch(
|
||||
() => selectRoleId.value,
|
||||
() => queryRoleEmployee()
|
||||
);
|
||||
|
||||
onMounted(queryRoleEmployee);
|
||||
|
||||
const defaultQueryForm = {
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
roleId: undefined,
|
||||
keywords: undefined,
|
||||
};
|
||||
// 查询表单
|
||||
const queryForm = reactive({ ...defaultQueryForm });
|
||||
// 总数
|
||||
const total = ref(0);
|
||||
// 表格数据
|
||||
const tableData = ref([]);
|
||||
// 表格loading效果
|
||||
const tableLoading = ref(false);
|
||||
|
||||
function resetQueryRoleEmployee() {
|
||||
queryForm.keywords = '';
|
||||
queryRoleEmployee();
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
queryForm.pageNum = 1;
|
||||
queryRoleEmployee();
|
||||
}
|
||||
|
||||
async function queryRoleEmployee() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
queryForm.roleId = selectRoleId.value;
|
||||
let res = await roleApi.queryRoleEmployee(queryForm);
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'actualName',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'loginName',
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departmentName',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'disabledFlag',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operate',
|
||||
width: 60,
|
||||
},
|
||||
]);
|
||||
|
||||
// ----------------------- 添加成员 ---------------------------------
|
||||
const selectEmployeeModal = ref();
|
||||
|
||||
async function addRoleEmployee() {
|
||||
let res = await roleApi.getRoleAllEmployee(selectRoleId.value);
|
||||
let selectedIdList = res.data.map((e) => e.roleId) || [];
|
||||
selectEmployeeModal.value.showModal(selectedIdList);
|
||||
}
|
||||
|
||||
async function selectData(list) {
|
||||
if (_.isEmpty(list)) {
|
||||
message.warning('请选择角色人员');
|
||||
return;
|
||||
}
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let params = {
|
||||
employeeIdList: list,
|
||||
roleId: selectRoleId.value,
|
||||
};
|
||||
await roleApi.batchAddRoleEmployee(params);
|
||||
message.success('添加成功');
|
||||
await queryRoleEmployee();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 移除成员 ---------------------------------
|
||||
// 删除角色成员方法
|
||||
async function deleteEmployeeRole(employeeId) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除该角色成员么?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await roleApi.deleteEmployeeRole(employeeId, selectRoleId.value);
|
||||
message.success('移除成功');
|
||||
await queryRoleEmployee();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------- 批量删除 ---------------------------------
|
||||
|
||||
const selectedRowKeyList = ref([]);
|
||||
const hasSelected = computed(() => selectedRowKeyList.value.length > 0);
|
||||
|
||||
function onSelectChange(selectedRowKeys) {
|
||||
selectedRowKeyList.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
// 批量移除
|
||||
function batchDelete() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择要删除的角色成员');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定移除这些角色成员吗?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let params = {
|
||||
employeeIdList: selectedRowKeyList.value,
|
||||
roleId: selectRoleId.value,
|
||||
};
|
||||
await roleApi.batchRemoveRoleEmployee(params);
|
||||
message.success('移除成功');
|
||||
selectedRowKeyList.value = [];
|
||||
await queryRoleEmployee();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.button-style {
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<!--
|
||||
* 角色 表单
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
* 角色 表单
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
@@ -32,9 +32,9 @@
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { roleApi } from '/@/api/system/role-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { roleApi } from '/src/api/system/role-api';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
let emits = defineEmits(['refresh']);
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
const formRef = ref();
|
||||
|
||||
const formDefault = {
|
||||
id: undefined,
|
||||
roleId: undefined,
|
||||
remark: undefined,
|
||||
roleCode: undefined,
|
||||
roleName: undefined,
|
||||
@@ -0,0 +1,116 @@
|
||||
<!--
|
||||
* 角色 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<a-card title="角色列表" class="role-container" style="padding: 0">
|
||||
<template #extra>
|
||||
<a-button type="primary" size="small" @click="showRoleFormModal" v-privilege="'system:role:add'">添加</a-button>
|
||||
</template>
|
||||
<a-menu mode="vertical" v-model:selectedKeys="selectedKeys">
|
||||
<a-menu-item v-for="item in roleList" :key="item.roleId">
|
||||
<a-popover placement="right">
|
||||
<template #content>
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<a-button type="text" @click="deleteRole(item.roleId)" v-privilege="'system:role:delete'">删除</a-button>
|
||||
<a-button type="text" @click="showRoleFormModal(item)" v-privilege="'system:role:update'">编辑</a-button>
|
||||
</div>
|
||||
</template>
|
||||
{{ item.roleName }}
|
||||
</a-popover>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-card>
|
||||
<RoleFormModal ref="roleFormModal" @refresh="queryAllRole" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { roleApi } from '/src/api/system/role-api';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
import RoleFormModal from '../role-form-modal/index.vue';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
// ----------------------- 角色列表显示 ---------------------
|
||||
const roleList = ref([]);
|
||||
|
||||
onMounted(queryAllRole);
|
||||
|
||||
// 查询列表
|
||||
async function queryAllRole() {
|
||||
let res = await roleApi.queryAll();
|
||||
roleList.value = res.data;
|
||||
if (!_.isEmpty(res.data) && res.data[0].roleId) {
|
||||
selectedKeys.value = [res.data[0].roleId];
|
||||
}
|
||||
}
|
||||
|
||||
let selectedKeys = ref([]);
|
||||
const selectRoleId = computed(() => {
|
||||
if (!selectedKeys.value && _.isEmpty(selectedKeys.value)) {
|
||||
return null;
|
||||
}
|
||||
return selectedKeys.value[0];
|
||||
});
|
||||
// ----------------------- 添加、修改、删除 ---------------------------------
|
||||
const roleFormModal = ref();
|
||||
|
||||
// 显示表单框
|
||||
function showRoleFormModal(role) {
|
||||
roleFormModal.value.showModal(role);
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
function deleteRole(roleId) {
|
||||
if (!roleId) {
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除该角色么?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await roleApi.deleteRole(roleId);
|
||||
message.info('删除成功');
|
||||
queryAllRole();
|
||||
} catch (e) {
|
||||
smartSentry.captureError(e);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
selectRoleId,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.role-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
.ant-menu-inline,
|
||||
.ant-menu-vertical,
|
||||
.ant-menu-vertical-left {
|
||||
border-right: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
* 角色 树形结构
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div class="tree-header">
|
||||
<p>设置角色对应的功能操作、后台管理权限</p>
|
||||
<a-button v-if="selectRoleId" type="primary" @click="saveChange" v-privilege="'system:role:menu:update'"> 保存 </a-button>
|
||||
</div>
|
||||
<!-- 功能权限勾选部分 -->
|
||||
<RoleTreeCheckbox :tree="tree" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { inject, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import RoleTreeCheckbox from './role-tree-checkbox.vue';
|
||||
import { roleMenuApi } from '/src/api/system/role-menu-api';
|
||||
import { useRoleStore } from '/src/store/modules/system/role';
|
||||
import { SmartLoading } from '/src/components/framework/smart-loading';
|
||||
import { smartSentry } from '/src/lib/smart-sentry';
|
||||
|
||||
let roleStore = useRoleStore();
|
||||
let tree = ref();
|
||||
let selectRoleId = inject('selectRoleId');
|
||||
|
||||
watch(selectRoleId, () => getRoleSelectedMenu(), {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
async function getRoleSelectedMenu() {
|
||||
if (!selectRoleId.value) {
|
||||
return;
|
||||
}
|
||||
let res = await roleMenuApi.getRoleSelectedMenu(selectRoleId.value);
|
||||
let data = res.data;
|
||||
if (_.isEmpty(roleStore.treeMap)) {
|
||||
roleStore.initTreeMap(data.menuTreeList || []);
|
||||
}
|
||||
roleStore.initCheckedData(data.selectedMenuId || []);
|
||||
tree.value = data.menuTreeList;
|
||||
}
|
||||
async function saveChange() {
|
||||
let checkedData = roleStore.checkedData;
|
||||
if (_.isEmpty(checkedData)) {
|
||||
message.error('还未选择任何权限');
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
roleId: selectRoleId.value,
|
||||
menuIdList: checkedData,
|
||||
};
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await roleMenuApi.updateRoleMenu(params);
|
||||
message.success('保存成功');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@import 'index.less';
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!--
|
||||
* 角色
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div style="overflow: auto">
|
||||
<a-checkbox-group v-model:value="checkedData">
|
||||
<div class="checked-box">
|
||||
<ul>
|
||||
<!--li 菜单模块 start-->
|
||||
<RoleTreeMenu :tree="props.tree" :index="0" />
|
||||
<!--li 菜单模块 end-->
|
||||
</ul>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoleStore } from '/src/store/modules/system/role';
|
||||
import RoleTreeMenu from './role-tree-menu.vue';
|
||||
|
||||
let props = defineProps({
|
||||
tree: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
defineEmits(['update:value']);
|
||||
|
||||
let roleStore = useRoleStore();
|
||||
let checkedData = ref();
|
||||
watch(
|
||||
() => roleStore.checkedData,
|
||||
(e) => (checkedData.value = e),
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@import 'index.less';
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
* 角色 菜单
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 22:34:00
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<li v-for="module in props.tree" :key="module.menuId">
|
||||
<div class="menu" :style="{ marginLeft: `${props.index * 4}%` }">
|
||||
<a-checkbox @change="selectCheckbox(module)" class="checked-box-label" :value="module.menuId">{{ module.menuName }} </a-checkbox>
|
||||
<div v-if="module.children && module.children.some((e) => e.menuType == MENU_TYPE_ENUM.POINTS.value)">
|
||||
<RoleTreePoint :tree="module.children" @selectCheckbox="selectCheckbox" />
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="module.children && !module.children.some((e) => e.menuType == MENU_TYPE_ENUM.POINTS.value)">
|
||||
<RoleTreeMenu :tree="module.children" :index="props.index + 1" />
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
<script setup>
|
||||
import { MENU_TYPE_ENUM } from '/src/constants/system/menu-const';
|
||||
import { useRoleStore } from '/src/store/modules/system/role';
|
||||
import RoleTreePoint from './role-tree-point.vue';
|
||||
import RoleTreeMenu from './role-tree-menu.vue';
|
||||
|
||||
const props = defineProps({
|
||||
tree: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
defineEmits(['update:value']);
|
||||
let roleStore = useRoleStore();
|
||||
function selectCheckbox(module) {
|
||||
if (!module.menuId) {
|
||||
return;
|
||||
}
|
||||
// 是否勾选
|
||||
let checkedData = roleStore.checkedData;
|
||||
let findIndex = checkedData.indexOf(module.menuId);
|
||||
// 选中
|
||||
if (findIndex == -1) {
|
||||
// 选中本级以及子级
|
||||
roleStore.addCheckedDataAndChildren(module);
|
||||
// 选中上级
|
||||
roleStore.selectUpperLevel(module);
|
||||
// 是否有关联菜单 有则选中
|
||||
if (module.contextMenuId) {
|
||||
roleStore.addCheckedData(module.contextMenuId);
|
||||
}
|
||||
} else {
|
||||
// 取消选中本级以及子级
|
||||
roleStore.deleteCheckedDataAndChildren(module);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/api/'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 开发环境(Dev)'
|
||||
|
||||
VITE_APP_PROFILE = 'dev'
|
||||
|
||||
VITE_APP_MODE = 'development'
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://127.0.0.1:1024'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 本地环境(Localhost)'
|
||||
|
||||
VITE_APP_PROFILE = 'local'
|
||||
|
||||
VITE_APP_MODE = 'local'
|
||||
8
smart-admin-web/typescript-ant-design-vue3/.env.pre
Normal file
8
smart-admin-web/typescript-ant-design-vue3/.env.pre
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://127.0.0.1:1024'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 预发布环境(Pre)'
|
||||
|
||||
VITE_APP_PROFILE = 'pre'
|
||||
|
||||
VITE_APP_MODE = 'production'
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://127.0.0.1:1024'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin'
|
||||
|
||||
VITE_APP_PROFILE = 'prod'
|
||||
|
||||
VITE_APP_MODE = 'production'
|
||||
8
smart-admin-web/typescript-ant-design-vue3/.env.test
Normal file
8
smart-admin-web/typescript-ant-design-vue3/.env.test
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
VITE_APP_API_URL = 'http://smartadmin.dev.1024lab.net/api/'
|
||||
|
||||
VITE_APP_PROJECT_TITLE = 'SmartAdmin 测试环境(Test)'
|
||||
|
||||
VITE_APP_PROFILE = 'test'
|
||||
|
||||
VITE_APP_MODE = 'development'
|
||||
17
smart-admin-web/typescript-ant-design-vue3/.eslintignore
Normal file
17
smart-admin-web/typescript-ant-design-vue3/.eslintignore
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
*.sh
|
||||
node_modules
|
||||
lib
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
||||
src/assets
|
||||
76
smart-admin-web/typescript-ant-design-vue3/.eslintrc.js
Normal file
76
smart-admin-web/typescript-ant-design-vue3/.eslintrc.js
Normal file
@@ -0,0 +1,76 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'plugin:vue/base'
|
||||
],
|
||||
globals: {
|
||||
defineProps: "readonly",
|
||||
defineEmits: "readonly",
|
||||
defineExpose: "readonly",
|
||||
withDefaults: "readonly"
|
||||
},
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/html-closing-bracket-newline': 'off',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/attribute-hyphenation': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'never',
|
||||
component: 'always',
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always',
|
||||
},
|
||||
],
|
||||
// Enable vue/script-setup-uses-vars rule
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
}
|
||||
};
|
||||
6
smart-admin-web/typescript-ant-design-vue3/.gitignore
vendored
Normal file
6
smart-admin-web/typescript-ant-design-vue3/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.idea
|
||||
31
smart-admin-web/typescript-ant-design-vue3/.prettierrc.js
Normal file
31
smart-admin-web/typescript-ant-design-vue3/.prettierrc.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 代码格式化配置
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-09-12 14:44:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),2012-2022
|
||||
*/
|
||||
module.exports = {
|
||||
printWidth: 150, // 每行代码长度(默认80)
|
||||
tabWidth: 2, // 缩进空格数
|
||||
useTabs: false, //不用tab缩进
|
||||
semi: true, //// 在语句末尾打印分号
|
||||
singleQuote: true, // 使用单引号而不是双引号
|
||||
vueIndentScriptAndStyle: true, //Vue文件脚本和样式标签缩进
|
||||
quoteProps: 'as-needed', // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
jsxSingleQuote: true, // 在JSX中使用单引号而不是双引号
|
||||
trailingComma: 'es5', //多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
bracketSpacing: true, // 在对象文字中的括号之间打印空格
|
||||
jsxBracketSameLine: false, //jsx 标签的反尖括号需要换行
|
||||
arrowParens: 'always', // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
|
||||
rangeStart: 0, // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false, // 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
insertPragma: false, // 不需要自动在文件开头插入 @prettier
|
||||
proseWrap: 'preserve', // 使用默认的折行标准 always\never\preserve
|
||||
htmlWhitespaceSensitivity: 'css', // 指定HTML文件的全局空格敏感度 css\strict\ignore
|
||||
endOfLine: 'auto', // 因为prettier的规范和eslint的换行规则不同,所以这个必须配置。要不然每次打开文件都会有一堆的警告;换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user