【V3.5.0】1、【新增】轻量级定时任务 SmartJob;2、【新增】站内信;3、【新增】个人中心;4、【新增】岗位管理;5、【优化】部门员工管理

This commit is contained in:
zhuoda
2024-07-16 00:20:02 +08:00
parent 23e8ea55e1
commit 716b6303e3
504 changed files with 59745 additions and 1110 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
},
]);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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',
},
]);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'))),
},
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
<template>
<NoticeEmployeeList />
</template>
<script setup>
import NoticeEmployeeList from '/@/views/business/oa/notice/notice-employee-list.vue';
</script>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
<a-card class="child-dept-container">
@@ -28,7 +28,7 @@
</a-card>
</template>
<script setup>
import emitter from '../../department-mitt';
import emitter from '../../department-mitt.js';
const props = defineProps({
breadcrumb: Array,

View File

@@ -9,14 +9,9 @@
-->
<template>
<a-card class="tree-container">
<a-row>
<a-row class="smart-margin-bottom10">
<a-input v-model:value.trim="keywords" placeholder="请输入部门名称" />
</a-row>
<a-row class="sort-flag-row" v-if="props.showMenu">
显示排序字段
<template v-if="showSortFlag"> 值越大越靠前 </template>
<a-switch v-model:checked="showSortFlag" />
</a-row>
<a-tree
v-if="!_.isEmpty(departmentTreeData)"
v-model:selectedKeys="selectedKeys"
@@ -26,7 +21,6 @@
:fieldNames="{ title: 'name', key: 'departmentId', value: 'departmentId' }"
style="width: 100%; overflow-x: auto"
:style="[!height ? '' : { height: `${height}px`, overflowY: 'auto' }]"
:showLine="!props.checkable"
:checkable="props.checkable"
:checkStrictly="props.checkStrictly"
:selectable="!props.checkable"
@@ -34,45 +28,18 @@
@select="treeSelectChange"
>
<template #title="item">
<a-popover placement="right" v-if="props.showMenu">
<template #content>
<div style="display: flex; flex-direction: column">
<a-button type="text" @click="addDepartment(item.dataRef)" v-privilege="'system:department:add'">添加下级</a-button>
<a-button type="text" @click="updateDepartment(item.dataRef)" v-privilege="'system:department:update'">修改</a-button>
<a-button
type="text"
v-if="item.departmentId != topDepartmentId"
@click="deleteDepartment(item.departmentId)"
v-privilege="'system:department:delete'"
>删除</a-button
>
</div>
</template>
{{ item.name }}
<!--显示排序字段-->
<template v-if="showSortFlag">
<span class="sort-span">({{ item.sort }})</span>
</template>
</a-popover>
<div v-else>{{ item.name }}</div>
<div>{{ item.name }}</div>
</template>
</a-tree>
<div class="no-data" v-else>暂无结果</div>
<!-- 添加编辑部门弹窗 -->
<DepartmentFormModal ref="departmentFormModal" @refresh="refresh" />
</a-card>
</template>
<script setup>
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { onUnmounted, ref, watch } from 'vue';
import { Modal } from 'ant-design-vue';
import { onMounted, onUnmounted, ref, watch } from 'vue';
import _ from 'lodash';
import { createVNode, onMounted } from 'vue';
import DepartmentFormModal from '../department-form-modal/index.vue';
import { departmentApi } from '/@/api/system/department-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { departmentApi } from '/src/api/system/department-api';
import departmentEmitter from '../../department-mitt';
import { smartSentry } from '/@/lib/smart-sentry';
import { smartSentry } from '/src/lib/smart-sentry';
const DEPARTMENT_PARENT_ID = 0;
@@ -94,7 +61,7 @@
//
showMenu: {
type: Boolean,
default: true,
default: false,
},
});
@@ -106,8 +73,6 @@
const departmentTreeData = ref([]);
// id
const idInfoMap = ref(new Map());
//
const showSortFlag = ref(false);
onMounted(() => {
queryDepartmentTree();
@@ -116,8 +81,8 @@
//
async function refresh() {
await queryDepartmentTree();
if (currentSelectedDpartmentId.value) {
selectTree(currentSelectedDpartmentId.value);
if (currentSelectedDepartmentId.value) {
selectTree(currentSelectedDepartmentId.value);
}
}
@@ -173,7 +138,7 @@
const selectedKeys = ref([]);
const checkedKeys = ref([]);
const breadcrumb = ref([]);
const currentSelectedDpartmentId = ref();
const currentSelectedDepartmentId = ref();
const selectedDepartmentChildren = ref([]);
departmentEmitter.on('selectTree', selectTree);
@@ -216,10 +181,10 @@
return;
}
//
let filterDepartmenet = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
let filterDepartmentList = [];
//
filterDepartmenet.forEach((e) => {
filterDepartment.forEach((e) => {
recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
});
@@ -242,64 +207,6 @@
}
}
// ----------------------- /// ---------------------
const departmentFormModal = ref();
//
function addDepartment(e) {
let data = {
departmentId: 0,
name: '',
parentId: e.departmentId,
};
currentSelectedDpartmentId.value = e.departmentId;
departmentFormModal.value.showModal(data);
}
//
function updateDepartment(e) {
currentSelectedDpartmentId.value = e.departmentId;
departmentFormModal.value.showModal(e);
}
//
function deleteDepartment(id) {
Modal.confirm({
title: '提醒',
icon: createVNode(ExclamationCircleOutlined),
content: '确定要删除该部门吗?',
okText: '删除',
okType: 'danger',
async onOk() {
SmartLoading.show();
try {
//
let selectedKey = null;
if (!_.isEmpty(selectedKeys.value)) {
selectedKey = selectedKeys.value[0];
if (selectedKey == id) {
let selectInfo = departmentList.value.find((e) => e.departmentId == id);
if (selectInfo && selectInfo.parentId) {
selectedKey = selectInfo.parentId;
}
}
}
await departmentApi.deleteDepartment(id);
await queryDepartmentTree();
//
if (selectedKey) {
selectTree(selectedKey);
}
} catch (error) {
smartSentry.captureError(error);
} finally {
SmartLoading.hide();
}
},
cancelText: '取消',
onCancel() {},
});
}
onUnmounted(() => {
departmentEmitter.all.clear();
});

View File

@@ -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>
<a-drawer
@@ -41,6 +41,10 @@
<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>
@@ -58,14 +62,15 @@
import { message } from 'ant-design-vue';
import _ from 'lodash';
import { nextTick, reactive, ref } from 'vue';
import { employeeApi } from '/@/api/system/employee-api';
import { roleApi } from '/@/api/system/role-api';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
import { GENDER_ENUM } from '/@/constants/common-const';
import { regular } from '/@/constants/regular-const';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { smartSentry } from '/@/lib/smart-sentry';
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
@@ -101,7 +106,7 @@
const formRef = ref(); // ref
const formDefault = {
id: undefined,
employeeId: undefined,
actualName: undefined,
departmentId: undefined,
disabledFlag: 0,
@@ -110,6 +115,7 @@
loginName: undefined,
phone: undefined,
roleIdList: undefined,
positionId: undefined,
};
let form = reactive(_.cloneDeep(formDefault));

View File

@@ -19,7 +19,7 @@
</a-radio-group>
<a-input-search v-model:value.trim="params.keyword" placeholder="姓名/手机号/登录账号" @search="queryEmployeeByKeyword(true)">
<template #enterButton>
<a-button style="margin-left: 8px" type="primary">
<a-button type="primary">
<template #icon>
<SearchOutlined />
</template>
@@ -27,7 +27,7 @@
</a-button>
</template>
</a-input-search>
<a-button @click="reset">
<a-button @click="reset" class="smart-margin-left10">
<template #icon>
<ReloadOutlined />
</template>
@@ -36,9 +36,9 @@
</div>
</div>
<div class="btn-group">
<a-button class="btn" type="primary" @click="showDrawer" v-privilege="'system:employee:add'" size="small">添加成员</a-button>
<a-button class="btn" size="small" @click="updateEmployeeDepartment" v-privilege="'system:employee:department:update'">调整部门</a-button>
<a-button class="btn" size="small" @click="batchDelete" v-privilege="'system:employee:delete'">批量删除</a-button>
<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" />
@@ -52,7 +52,7 @@
:data-source="tableData"
:pagination="false"
:loading="tableLoading"
:scroll="{ x: 1200 }"
:scroll="{ x: 1500 }"
row-key="employeeId"
bordered
>
@@ -140,21 +140,21 @@
dataIndex: 'actualName',
width: 85,
},
{
title: '手机号',
dataIndex: 'phone',
width: 80,
},
{
title: '性别',
dataIndex: 'gender',
width: 40,
width: 70,
},
{
title: '登录账号',
dataIndex: 'loginName',
width: 100,
},
{
title: '手机号',
dataIndex: 'phone',
width: 85,
},
{
title: '超管',
dataIndex: 'administratorFlag',
@@ -165,6 +165,11 @@
dataIndex: 'disabledFlag',
width: 60,
},
{
title: '职务',
dataIndex: 'positionName',
width: 100,
},
{
title: '角色',
dataIndex: 'roleNameList',
@@ -176,10 +181,16 @@
ellipsis: true,
width: 200,
},
{
title: '职务',
dataIndex: 'employeeName',
ellipsis: true,
width: 200,
},
{
title: '操作',
dataIndex: 'operate',
width: 120,
width: 140,
},
]);
const tableData = ref();

View File

@@ -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';

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -22,12 +22,18 @@
<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
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"
@@ -73,11 +79,11 @@
import { message, Modal } from 'ant-design-vue';
import _ from 'lodash';
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
import { roleApi } from '/@/api/system/role-api';
import { PAGE_SIZE, showTableTotal, PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
import { SmartLoading } from '/@/components/framework/smart-loading';
import EmployeeTableSelectModal from '/@/components/system/employee-table-select-modal/index.vue';
import { smartSentry } from '/@/lib/smart-sentry';
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');
@@ -110,7 +116,7 @@
queryRoleEmployee();
}
function onSearch(){
function onSearch() {
queryForm.pageNum = 1;
queryRoleEmployee();
}

View File

@@ -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,

View File

@@ -33,10 +33,10 @@
import { message, Modal } from 'ant-design-vue';
import _ from 'lodash';
import { computed, onMounted, ref } from 'vue';
import { roleApi } from '/@/api/system/role-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
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 '/@/lib/smart-sentry';
import { smartSentry } from '/src/lib/smart-sentry';
// ----------------------- ---------------------
const roleList = ref([]);

View File

@@ -23,10 +23,10 @@
import { message } from 'ant-design-vue';
import _ from 'lodash';
import RoleTreeCheckbox from './role-tree-checkbox.vue';
import { roleMenuApi } from '/@/api/system/role-menu-api';
import { useRoleStore } from '/@/store/modules/system/role';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { smartSentry } from '/@/lib/smart-sentry';
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();
@@ -70,5 +70,5 @@ import { smartSentry } from '/@/lib/smart-sentry';
}
</script>
<style scoped lang="less">
@import './index.less';
@import 'index.less';
</style>

View File

@@ -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>
@@ -23,8 +23,8 @@
</template>
<script setup>
import { ref, watch } from 'vue';
import { useRoleStore } from '/@/store/modules/system/role';
import RoleTreeMenu from '../role-tree/role-tree-menu.vue';
import { useRoleStore } from '/src/store/modules/system/role';
import RoleTreeMenu from './role-tree-menu.vue';
let props = defineProps({
tree: {
@@ -45,5 +45,5 @@
);
</script>
<style scoped lang="less">
@import './index.less';
@import 'index.less';
</style>

View File

@@ -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>
@@ -22,10 +22,10 @@
</li>
</template>
<script setup>
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
import { useRoleStore } from '/@/store/modules/system/role';
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/role-tree-menu.vue';
import RoleTreeMenu from './role-tree-menu.vue';
const props = defineProps({
tree: {