xq feat:"新增了标题栏的下拉列表(未接入接口)"

This commit is contained in:
2025-07-02 20:05:17 +08:00
parent 4974dd7c39
commit 93c9d6bead
355 changed files with 28011 additions and 1 deletions

View File

@ -0,0 +1,245 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="key键" prop="testKey">
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="值" prop="value">
<el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="主键" align="center" prop="id" />
<el-table-column label="部门id" align="center" prop="deptId" />
<el-table-column label="用户id" align="center" prop="userId" />
<el-table-column label="排序号" align="center" prop="orderNum" />
<el-table-column label="key键" align="center" prop="testKey" />
<el-table-column label="值" align="center" prop="value" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['demo:demo:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['demo:demo:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改测试单对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="demoFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="部门id" prop="deptId">
<el-input v-model="form.deptId" placeholder="请输入部门id" />
</el-form-item>
<el-form-item label="用户id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入用户id" />
</el-form-item>
<el-form-item label="排序号" prop="orderNum">
<el-input v-model="form.orderNum" placeholder="请输入排序号" />
</el-form-item>
<el-form-item label="key键" prop="testKey">
<el-input v-model="form.testKey" placeholder="请输入key键" />
</el-form-item>
<el-form-item label="值" prop="value">
<el-input v-model="form.value" placeholder="请输入值" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Demo" lang="ts">
import { listDemo, getDemo, delDemo, addDemo, updateDemo } from '@/api/demo/demo';
import { DemoVO, DemoQuery, DemoForm } from '@/api/demo/demo/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const demoList = ref<DemoVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const demoFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DemoForm = {
id: undefined,
deptId: undefined,
userId: undefined,
orderNum: undefined,
testKey: undefined,
value: undefined
};
const data = reactive<PageData<DemoForm, DemoQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deptId: undefined,
userId: undefined,
orderNum: undefined,
testKey: undefined,
value: undefined
},
rules: {
id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
deptId: [{ required: true, message: '部门id不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '排序号不能为空', trigger: 'blur' }],
testKey: [{ required: true, message: 'key键不能为空', trigger: 'blur' }],
value: [{ required: true, message: '值不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询测试单列表 */
const getList = async () => {
loading.value = true;
const res = await listDemo(queryParams.value);
demoList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
demoFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DemoVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加测试单';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DemoVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getDemo(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改测试单';
};
/** 提交按钮 */
const submitForm = () => {
demoFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateDemo(form.value).finally(() => (buttonLoading.value = false));
} else {
await addDemo(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('修改成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DemoVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除测试单编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delDemo(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'demo/demo/export',
{
...queryParams.value
},
`demo_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,259 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="树节点名" prop="treeName">
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['demo:tree:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="treeTableRef"
v-loading="loading"
:data="treeList"
row-key="id"
border
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="父id" align="center" prop="parentId" />
<el-table-column label="部门id" align="center" prop="deptId" />
<el-table-column label="用户id" align="center" prop="userId" />
<el-table-column label="树节点名" align="center" prop="treeName" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['demo:tree:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['demo:tree:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['demo:tree:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加或修改测试树对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="treeFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="父id" prop="parentId">
<el-tree-select
v-model="form.parentId"
:data="treeOptions"
:props="{ value: 'id', label: 'treeName', children: 'children' } as any"
value-key="id"
placeholder="请选择父id"
check-strictly
/>
</el-form-item>
<el-form-item label="部门id" prop="deptId">
<el-input v-model="form.deptId" placeholder="请输入部门id" />
</el-form-item>
<el-form-item label="用户id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入用户id" />
</el-form-item>
<el-form-item label="值" prop="treeName">
<el-input v-model="form.treeName" placeholder="请输入值" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Tree" lang="ts">
import { listTree, getTree, delTree, addTree, updateTree } from '@/api/demo/tree';
import { TreeVO, TreeQuery, TreeForm } from '@/api/demo/tree/types';
type TreeOption = {
id: number;
treeName: string;
children?: TreeOption[];
};
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const treeList = ref<TreeVO[]>([]);
const treeOptions = ref<TreeOption[]>([]);
const buttonLoading = ref(false);
const showSearch = ref(true);
const isExpandAll = ref(true);
const loading = ref(false);
const queryFormRef = ref<ElFormInstance>();
const treeFormRef = ref<ElFormInstance>();
const treeTableRef = ref<ElTableInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: TreeForm = {
id: undefined,
parentId: undefined,
deptId: undefined,
userId: undefined,
treeName: undefined
};
const data = reactive<PageData<TreeForm, TreeQuery>>({
form: { ...initFormData },
queryParams: {
parentId: undefined,
deptId: undefined,
userId: undefined,
treeName: undefined
},
rules: {
id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
parentId: [{ required: true, message: '父id不能为空', trigger: 'blur' }],
deptId: [{ required: true, message: '部门id不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
treeName: [{ required: true, message: '值不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询测试树列表 */
const getList = async () => {
loading.value = true;
const res = await listTree(queryParams.value);
const data = proxy?.handleTree<TreeVO>(res.data, 'id', 'parentId');
if (data) {
treeList.value = data;
loading.value = false;
}
};
/** 查询测试树下拉树结构 */
const getTreeselect = async () => {
const res = await listTree();
treeOptions.value = [];
const data: TreeOption = { id: 0, treeName: '顶级节点', children: [] };
data.children = proxy?.handleTree<TreeOption>(res.data, 'id', 'parentId');
treeOptions.value.push(data);
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
form.value = { ...initFormData };
treeFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = (row?: TreeVO) => {
reset();
getTreeselect();
if (row && row.id) {
form.value.parentId = row.id;
} else {
form.value.parentId = 0;
}
dialog.visible = true;
dialog.title = '添加测试树';
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(treeList.value, isExpandAll.value);
};
/** 展开/折叠操作 */
const toggleExpandAll = (data: TreeVO[], status: boolean) => {
data.forEach((item) => {
treeTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 修改按钮操作 */
const handleUpdate = async (row: TreeVO) => {
reset();
await getTreeselect();
if (row) {
form.value.parentId = row.id;
}
const res = await getTree(row.id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改测试树';
};
/** 提交按钮 */
const submitForm = () => {
treeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateTree(form.value).finally(() => (buttonLoading.value = false));
} else {
await addTree(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: TreeVO) => {
await proxy?.$modal.confirm('是否确认删除测试树编号为"' + row.id + '"的数据项?');
loading.value = true;
await delTree(row.id).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

76
src/views/error/401.vue Normal file
View File

@ -0,0 +1,76 @@
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back"> 返回 </el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">401错误!</h1>
<h2>您没有访问权限</h2>
<h6>对不起您没有访问权限请不要进行非法操作您可以返回主页面</h6>
<ul class="list-unstyled">
<li class="link-type">
<router-link to="/"> 回首页 </router-link>
</li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream." />
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import errImage from '@/assets/401_images/401.gif';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const errGif = ref(errImage + '?' + +new Date());
function back() {
if (proxy?.$route.query.noGoBack) {
proxy.$router.push({ path: '/' });
} else {
proxy?.$router.go(-1);
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none !important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

223
src/views/error/404.vue Normal file
View File

@ -0,0 +1,223 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404" />
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404" />
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404" />
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404" />
</div>
<div class="bullshit">
<div class="bullshit__oops">404错误!</div>
<div class="bullshit__headline">
{{ message }}
</div>
<div class="bullshit__info">
对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容
</div>
<router-link to="/index" class="bullshit__return-home"> 返回首页 </router-link>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const message = computed(() => {
return '找不到网页!';
});
</script>
<style lang="scss" scoped>
.wscn-http404-container {
transform: translate(-50%, -50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

165
src/views/index.vue Normal file
View File

@ -0,0 +1,165 @@
<template>
<div class="app-container home">
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Vue-Plus多租户管理系统</h2>
<p>
RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement Plus<br />
* 后端开发框架 Spring Boot<br />
* 容器框架 Undertow 基于 Netty 的高性能容器<br />
* 权限认证框架 Sa-Token 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 缓存数据库 Redis 适配 6.X 最低 4.X<br />
* 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
* 数据库框架 p6spy 更强劲的 SQL 分析<br />
* 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
* 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
* Redis客户端 Redisson 性能强劲API丰富<br />
* 分布式限流 Redisson 全局请求IP集群ID 多种限流<br />
* 分布式锁 Lock4j 注解锁工具锁 多种多样<br />
* 分布式幂等 Lock4j 基于分布式锁实现<br />
* 分布式链路追踪 SkyWalking 支持链路追踪网格分析度量聚合可视化<br />
* 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
* 文件存储 Minio 本地存储<br />
* 文件存储 七牛阿里腾讯 云存储<br />
* 监控框架 SpringBoot-Admin 全方位服务监控<br />
* 校验框架 Validation 增强接口安全性 严谨性<br />
* Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
* 文档框架 SpringDocjavadoc 无注解零入侵基于java注释<br />
* 工具类框架 HutoolLombok 减少代码冗余 增加安全性<br />
* 代码生成器 适配MPSpringDoc规范化代码 一键生成前后端代码<br />
* 部署方式 Docker 容器编排 一键部署业务集群<br />
* 国际化 SpringMessage Spring标准国际化方案<br />
</p>
<p><b>当前版本:</b> <span>v5.4.1</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
<p>
RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement UI<br />
* 后端开发框架 Spring Boot<br />
* 微服务开发框架 Spring CloudSpring Cloud Alibaba<br />
* 容器框架 Undertow 基于 XNIO 的高性能容器<br />
* 权限认证框架 Sa-TokenJwt 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 关系数据库 Oracle 适配 11g 12c<br />
* 关系数据库 PostgreSQL 适配 13 14<br />
* 关系数据库 SQLServer 适配 2017 2019<br />
* 缓存数据库 Redis 适配 6.X 最低 5.X<br />
* 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
* 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
* RPC远程调用 Apache Dubbo 原生态使用体验高性能<br />
* 分布式限流熔断 Alibaba Sentinel 无侵入高扩展<br />
* 分布式事务 Alibaba Seata 无侵入高扩展 支持 四种模式<br />
* 分布式消息队列 Apache Kafka 高性能高速度<br />
* 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
* 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
* 分布式搜索引擎 ElasticSearch 业界知名<br />
* 分布式链路追踪 Apache SkyWalking 链路追踪网格分析度量聚合可视化<br />
* 分布式日志中心 ELK 业界成熟解决方案<br />
* 分布式监控 PrometheusGrafana 全方位性能监控<br />
* 其余与 Vue 版本一致<br />
</p>
<p><b>当前版本:</b> <span>v2.4.1</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
</el-row>
<el-divider />
</div>
</template>
<script setup name="Index" lang="ts">
const goTarget = (url: string) => {
window.open(url, '__blank');
};
</script>
<style lang="scss" scoped>
.home {
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eee;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.col-item {
margin-bottom: 20px;
}
ul {
padding: 0;
margin: 0;
}
font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 13px;
color: #676a6c;
overflow-x: hidden;
ul {
list-style-type: none;
}
h4 {
margin-top: 0px;
}
h2 {
margin-top: 10px;
font-size: 26px;
font-weight: 100;
}
p {
margin-top: 10px;
b {
font-weight: 700;
}
}
.update-log {
ol {
display: block;
list-style-type: decimal;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 40px;
}
}
}
</style>

312
src/views/login.vue Normal file
View File

@ -0,0 +1,312 @@
<template>
<div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-box">
<h3 class="title">{{ title }}</h3>
<lang-select />
</div>
<el-form-item v-if="tenantEnabled" prop="tenantId">
<el-select v-model="loginForm.tenantId" filterable :placeholder="proxy.$t('login.selectPlaceholder')" style="width: 100%">
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.password')"
@keyup.enter="handleLogin"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item v-if="captchaEnabled" prop="code">
<el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.code')"
style="width: 63%"
@keyup.enter="handleLogin"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="login-code">
<img :src="codeUrl" class="login-code-img" @click="getCode" />
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
<el-form-item style="float: right">
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
<svg-icon icon-class="wechat" />
</el-button>
<el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
<svg-icon icon-class="maxkey" />
</el-button>
<el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
<svg-icon icon-class="topiam" />
</el-button>
<el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
<svg-icon icon-class="gitee" />
</el-button>
<el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
<svg-icon icon-class="github" />
</el-button>
</el-form-item>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ proxy.$t('login.login') }}</span>
<span v-else>{{ proxy.$t('login.logging') }}</span>
</el-button>
<div v-if="register" style="float: right">
<router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getCodeImg, getTenantList } from '@/api/login';
import { authBinding } from '@/api/system/social/auth';
import { useUserStore } from '@/store/modules/user';
import { LoginData, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
import { HttpStatus } from '@/enums/RespEnum';
import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE;
const userStore = useUserStore();
const router = useRouter();
const { t } = useI18n();
const loginForm = ref<LoginData>({
tenantId: '000000',
username: 'admin',
password: 'admin123',
rememberMe: false,
code: '',
uuid: ''
} as LoginData);
const loginRules: ElFormRules = {
tenantId: [{ required: true, trigger: 'blur', message: t('login.rule.tenantId.required') }],
username: [{ required: true, trigger: 'blur', message: t('login.rule.username.required') }],
password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }],
code: [{ required: true, trigger: 'change', message: t('login.rule.code.required') }]
};
const codeUrl = ref('');
const loading = ref(false);
// 验证码开关
const captchaEnabled = ref(true);
// 租户开关
const tenantEnabled = ref(true);
// 注册开关
const register = ref(false);
const redirect = ref('/');
const loginRef = ref<ElFormInstance>();
// 租户列表
const tenantList = ref<TenantVO[]>([]);
watch(
() => router.currentRoute.value,
(newRoute: any) => {
redirect.value = newRoute.query && newRoute.query.redirect && decodeURIComponent(newRoute.query.redirect);
},
{ immediate: true }
);
const handleLogin = () => {
loginRef.value?.validate(async (valid: boolean, fields: any) => {
if (valid) {
loading.value = true;
// 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
localStorage.setItem('tenantId', String(loginForm.value.tenantId));
localStorage.setItem('username', String(loginForm.value.username));
localStorage.setItem('password', String(loginForm.value.password));
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
} else {
// 否则移除
localStorage.removeItem('tenantId');
localStorage.removeItem('username');
localStorage.removeItem('password');
localStorage.removeItem('rememberMe');
}
// 调用action的登录方法
const [err] = await to(userStore.login(loginForm.value));
if (!err) {
const redirectUrl = redirect.value || '/';
await router.push(redirectUrl);
loading.value = false;
} else {
loading.value = false;
// 重新获取验证码
if (captchaEnabled.value) {
await getCode();
}
}
} else {
console.log('error submit!', fields);
}
});
};
/**
* 获取验证码
*/
const getCode = async () => {
const res = await getCodeImg();
const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) {
codeUrl.value = 'data:image/gif;base64,' + data.img;
loginForm.value.uuid = data.uuid;
}
};
const getLoginData = () => {
const tenantId = localStorage.getItem('tenantId');
const username = localStorage.getItem('username');
const password = localStorage.getItem('password');
const rememberMe = localStorage.getItem('rememberMe');
loginForm.value = {
tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
username: username === null ? String(loginForm.value.username) : username,
password: password === null ? String(loginForm.value.password) : String(password),
rememberMe: rememberMe === null ? false : Boolean(rememberMe)
} as LoginData;
};
/**
* 获取租户列表
*/
const initTenantList = async () => {
const { data } = await getTenantList(false);
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
if (tenantList.value != null && tenantList.value.length !== 0) {
loginForm.value.tenantId = tenantList.value[0].tenantId;
}
}
};
/**
* 第三方登录
* @param type
*/
const doSocialLogin = (type: string) => {
authBinding(type, loginForm.value.tenantId).then((res: any) => {
if (res.code === HttpStatus.SUCCESS) {
// 获取授权地址跳转
window.location.href = res.data;
} else {
ElMessage.error(res.msg);
}
});
};
onMounted(() => {
getCode();
initTenantList();
getLoginData();
});
</script>
<style lang="scss" scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
}
.title-box {
display: flex;
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
:deep(.lang-select--style) {
line-height: 0;
color: #7483a3;
}
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
z-index: 1;
.el-input {
height: 40px;
input {
height: 40px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 40px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial, serif;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 40px;
padding-left: 12px;
}
</style>

View File

@ -0,0 +1,9 @@
<template>
<div>
<i-frame v-model:src="url"></i-frame>
</div>
</template>
<script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_MONITOR_ADMIN);
</script>

192
src/views/monitor/cache/index.vue vendored Normal file
View File

@ -0,0 +1,192 @@
<template>
<div class="p-2">
<el-row :gutter="10">
<el-col :span="24" class="card-box">
<el-card shadow="hover">
<template #header>
<Monitor style="width: 1em; height: 1em; vertical-align: middle" />
<span style="vertical-align: middle">基本信息</span>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<table style="width: 100%">
<tbody>
<tr>
<td class="el-table__cell is-leaf">
<div class="cell">Redis版本</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.redis_version }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">运行模式</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">端口</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.tcp_port }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">客户端数</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.connected_clients }}</div>
</td>
</tr>
<tr>
<td class="el-table__cell is-leaf">
<div class="cell">运行时间()</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.uptime_in_days }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">使用内存</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.used_memory_human }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">使用CPU</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">内存配置</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.maxmemory_human }}</div>
</td>
</tr>
<tr>
<td class="el-table__cell is-leaf">
<div class="cell">AOF是否开启</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.aof_enabled === '0' ? '' : '' }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">RDB是否成功</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">{{ cache.info.rdb_last_bgsave_status }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">Key数量</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.dbSize" class="cell">{{ cache.dbSize }}</div>
</td>
<td class="el-table__cell is-leaf">
<div class="cell">网络入口/出口</div>
</td>
<td class="el-table__cell is-leaf">
<div v-if="cache.info" class="cell">
{{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
</div>
</td>
</tr>
</tbody>
</table>
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card shadow="hover">
<template #header>
<PieChart style="width: 1em; height: 1em; vertical-align: middle" />
<span style="vertical-align: middle">命令统计</span>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="commandstats" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card shadow="hover">
<template #header>
<Odometer style="width: 1em; height: 1em; vertical-align: middle" /> <span style="vertical-align: middle">内存信息</span>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="usedmemory" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Cache" lang="ts">
import { getCache } from '@/api/monitor/cache';
import * as echarts from 'echarts';
import { CacheVO } from '@/api/monitor/cache/types';
const cache = ref<Partial<CacheVO>>({});
const commandstats = ref();
const usedmemory = ref();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const getList = async () => {
proxy?.$modal.loading('正在加载缓存监控数据,请稍候!');
const res = await getCache();
proxy?.$modal.closeLoading();
cache.value = res.data;
const commandstatsIntance = echarts.init(commandstats.value, 'macarons');
commandstatsIntance.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
series: [
{
name: '命令',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: res.data.commandStats,
animationEasing: 'cubicInOut',
animationDuration: 1000
}
]
});
const usedmemoryInstance = echarts.init(usedmemory.value, 'macarons');
usedmemoryInstance.setOption({
tooltip: {
formatter: '{b} <br/>{a} : ' + cache.value.info.used_memory_human
},
series: [
{
name: '峰值',
type: 'gauge',
min: 0,
max: 1000,
detail: {
formatter: cache.value.info.used_memory_human
},
data: [
{
value: parseFloat(cache.value.info.used_memory_human),
name: '内存消耗'
}
]
}
]
});
window.addEventListener('resize', () => {
commandstatsIntance.resize();
usedmemoryInstance.resize();
});
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,209 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="登录状态" clearable>
<el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="登录时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" @click="handleClean">清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfor:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock">
解锁
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfor:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="loginInfoTableRef"
v-loading="loading"
:data="loginInfoList"
:default-sort="defaultSort"
border
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="访问编号" align="center" prop="infoId" />
<el-table-column
label="用户名称"
align="center"
prop="userName"
:show-overflow-tooltip="true"
sortable="custom"
:sort-orders="['descending', 'ascending']"
/>
<el-table-column label="客户端" align="center" prop="clientKey" :show-overflow-tooltip="true" />
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
<el-table-column label="登录状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_common_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
<el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="Logininfor" lang="ts">
import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from '@/api/monitor/loginInfo';
import { LoginInfoQuery, LoginInfoVO } from '@/api/monitor/loginInfo/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const { sys_common_status } = toRefs<any>(proxy?.useDict('sys_common_status'));
const loginInfoList = ref<LoginInfoVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const selectName = ref<Array<string>>([]);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: 'loginTime', order: 'descending' });
const queryFormRef = ref<ElFormInstance>();
const loginInfoTableRef = ref<ElTableInstance>();
// 查询参数
const queryParams = ref<LoginInfoQuery>({
pageNum: 1,
pageSize: 10,
ipaddr: '',
userName: '',
status: '',
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
});
/** 查询登录日志列表 */
const getList = async () => {
loading.value = true;
const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
loginInfoList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
loginInfoTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: LoginInfoVO[]) => {
ids.value = selection.map((item) => item.infoId);
multiple.value = !selection.length;
single.value = selection.length != 1;
selectName.value = selection.map((item) => item.userName);
};
/** 排序触发事件 */
const handleSortChange = (column: any) => {
queryParams.value.orderByColumn = column.prop;
queryParams.value.isAsc = column.order;
getList();
};
/** 删除按钮操作 */
const handleDelete = async (row?: LoginInfoVO) => {
const infoIds = row?.infoId || ids.value;
await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
await delLoginInfo(infoIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 清空按钮操作 */
const handleClean = async () => {
await proxy?.$modal.confirm('是否确认清空所有登录日志数据项?');
await cleanLoginInfo();
await getList();
proxy?.$modal.msgSuccess('清空成功');
};
/** 解锁按钮操作 */
const handleUnlock = async () => {
const username = selectName.value;
await proxy?.$modal.confirm('是否确认解锁用户"' + username + '"数据项?');
await unlockLoginInfo(username);
proxy?.$modal.msgSuccess('用户' + username + '解锁成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'monitor/logininfor/export',
{
...queryParams.value
},
`logininfor_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,117 @@
<template>
<div class="p-2">
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover">
<el-table
v-loading="loading"
border
:data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
style="width: 100%"
>
<el-table-column label="序号" width="50" type="index" align="center">
<template #default="scope">
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="客户端" align="center" prop="clientKey" :show-overflow-tooltip="true" />
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="强退" placement="top">
<el-button v-hasPermi="['monitor:online:forceLogout']" link type="primary" icon="Delete" @click="handleForceLogout(scope.row)">
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" />
</el-card>
</div>
</template>
<script setup name="Online" lang="ts">
import { forceLogout, list as initData } from '@/api/monitor/online';
import { OnlineQuery, OnlineVO } from '@/api/monitor/online/types';
import api from '@/api/system/user';
import { to } from 'await-to-js';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const onlineList = ref<OnlineVO[]>([]);
const loading = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const queryParams = ref<OnlineQuery>({
pageNum: 1,
pageSize: 10,
ipaddr: '',
userName: ''
});
/** 查询登录日志列表 */
const getList = async () => {
loading.value = true;
const res = await initData(queryParams.value);
onlineList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 强退按钮操作 */
const handleForceLogout = async (row: OnlineVO) => {
const [err] = await to(proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?') as any);
if (!err) {
await forceLogout(row.tokenId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,261 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="操作地址" prop="operIp">
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="系统模块" prop="title">
<el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="操作人员" prop="operName">
<el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="businessType">
<el-select v-model="queryParams.businessType" placeholder="操作类型" clearable>
<el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="操作状态" clearable>
<el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="操作时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="WarnTriangleFilled" @click="handleClean">清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="operLogTableRef"
v-loading="loading"
:data="operlogList"
border
:default-sort="defaultSort"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="日志编号" align="center" prop="operId" />
<el-table-column label="系统模块" align="center" prop="title" :show-overflow-tooltip="true" />
<el-table-column label="操作类型" align="center" prop="businessType">
<template #default="scope">
<dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
</template>
</el-table-column>
<el-table-column
label="操作人员"
align="center"
width="110"
prop="operName"
:show-overflow-tooltip="true"
sortable="custom"
:sort-orders="['descending', 'ascending']"
/>
<el-table-column label="部门" align="center" prop="deptName" width="130" :show-overflow-tooltip="true" />
<el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
<el-table-column label="操作状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_common_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.operTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="消耗时间"
align="center"
prop="costTime"
width="110"
:show-overflow-tooltip="true"
sortable="custom"
:sort-orders="['descending', 'ascending']"
>
<template #default="scope">
<span>{{ scope.row.costTime }}毫秒</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="详细" placement="top">
<el-button v-hasPermi="['monitor:operlog:query']" link type="primary" icon="View" @click="handleView(scope.row)"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 操作日志详细 -->
<OperInfoDialog ref="operInfoDialogRef" />
</div>
</template>
<script setup name="Operlog" lang="ts">
import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
import { OperLogForm, OperLogQuery, OperLogVO } from '@/api/monitor/operlog/types';
import OperInfoDialog from './oper-info-dialog.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_oper_type, sys_common_status } = toRefs<any>(proxy?.useDict('sys_oper_type', 'sys_common_status'));
const operlogList = ref<OperLogVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: 'operTime', order: 'descending' });
const operLogTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const data = reactive<PageData<OperLogForm, OperLogQuery>>({
form: {
operId: undefined,
tenantId: undefined,
title: '',
businessType: 0,
businessTypes: undefined,
method: '',
requestMethod: '',
operatorType: 0,
operName: '',
deptName: '',
operUrl: '',
operIp: '',
operLocation: '',
operParam: '',
jsonResult: '',
status: 0,
errorMsg: '',
operTime: '',
costTime: 0
},
queryParams: {
pageNum: 1,
pageSize: 10,
operIp: '',
title: '',
operName: '',
businessType: '',
status: '',
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
},
rules: {}
});
const { queryParams, form } = toRefs(data);
/** 查询登录日志 */
const getList = async () => {
loading.value = true;
const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
operlogList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 操作日志类型字典翻译 */
const typeFormat = (row: OperLogForm) => {
return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
operLogTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: OperLogVO[]) => {
ids.value = selection.map((item) => item.operId);
multiple.value = !selection.length;
};
/** 排序触发事件 */
const handleSortChange = (column: any) => {
queryParams.value.orderByColumn = column.prop;
queryParams.value.isAsc = column.order;
getList();
};
const operInfoDialogRef = ref<InstanceType<typeof OperInfoDialog>>();
/** 详细按钮操作 */
const handleView = (row: OperLogVO) => {
operInfoDialogRef.value.openDialog(row);
};
/** 删除按钮操作 */
const handleDelete = async (row?: OperLogVO) => {
const operIds = row?.operId || ids.value;
await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
await delOperlog(operIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 清空按钮操作 */
const handleClean = async () => {
await proxy?.$modal.confirm('是否确认清空所有操作日志数据项?');
await cleanOperlog();
await getList();
proxy?.$modal.msgSuccess('清空成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'monitor/operlog/export',
{
...queryParams.value
},
`config_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,111 @@
<template>
<el-dialog v-model="open" title="操作日志详细" width="700px" append-to-body close-on-click-modal @closed="info = null">
<el-descriptions v-if="info" :column="1" border>
<el-descriptions-item label="操作状态">
<template #default>
<el-tag v-if="info.status === 0" type="success">正常</el-tag>
<el-tag v-else-if="info.status === 1" type="danger">失败</el-tag>
</template>
</el-descriptions-item>
<el-descriptions-item label="登录信息">
<template #default> {{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }} </template>
</el-descriptions-item>
<el-descriptions-item label="请求信息">
<template #default> {{ info.requestMethod }} {{ info.operUrl }} </template>
</el-descriptions-item>
<el-descriptions-item label="操作模块">
<template #default> {{ info.title }} / {{ typeFormat(info) }} </template>
</el-descriptions-item>
<el-descriptions-item label="操作方法">
<template #default>
{{ info.method }}
</template>
</el-descriptions-item>
<el-descriptions-item label="请求参数">
<template #default>
<div class="max-h-300px overflow-y-auto">
<VueJsonPretty :data="formatToJsonObject(info.operParam)" />
</div>
</template>
</el-descriptions-item>
<el-descriptions-item label="返回参数">
<template #default>
<div class="max-h-300px overflow-y-auto">
<VueJsonPretty :data="formatToJsonObject(info.jsonResult)" />
</div>
</template>
</el-descriptions-item>
<el-descriptions-item label="消耗时间">
<template #default>
<span> {{ info.costTime }}ms </span>
</template>
</el-descriptions-item>
<el-descriptions-item label="操作时间">
<template #default> {{ proxy.parseTime(info.operTime) }}</template>
</el-descriptions-item>
<el-descriptions-item v-if="info.status === 1" label="异常信息">
<template #default>
<span class="text-danger"> {{ info.errorMsg }}</span>
</template>
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</template>
<script setup lang="ts">
import type { OperLogForm } from '@/api/monitor/operlog/types';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
const open = ref(false);
const info = ref<OperLogForm | null>(null);
function openDialog(row: OperLogForm) {
info.value = row;
open.value = true;
}
function closeDialog() {
open.value = false;
}
defineExpose({
openDialog,
closeDialog
});
/**
* json转为对象
* @param data 原始数据
*/
function formatToJsonObject(data: string) {
try {
return JSON.parse(data);
} catch (error) {
return data;
}
}
/**
* 字典信息
*/
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_oper_type } = toRefs<any>(proxy?.useDict('sys_oper_type'));
const typeFormat = (row: OperLogForm) => {
return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
};
</script>
<style lang="scss" scoped>
/**
label宽度固定
*/
:deep(.el-descriptions__label) {
min-width: 100px;
}
/**
文字超过 换行显示
*/
:deep(.el-descriptions__content) {
max-width: 300px;
}
</style>

View File

@ -0,0 +1,9 @@
<template>
<div>
<i-frame v-model:src="url"></i-frame>
</div>
</template>
<script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_SNAILJOB_ADMIN);
</script>

View File

@ -0,0 +1,14 @@
<template>
<div></div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const { params, query } = route;
const { path } = params;
router.replace({ path: '/' + path, query });
</script>

263
src/views/register.vue Normal file
View File

@ -0,0 +1,263 @@
<template>
<div class="register">
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<div class="title-box">
<h3 class="title">{{ title }}</h3>
<lang-select />
</div>
<el-form-item v-if="tenantEnabled" prop="tenantId">
<el-select v-model="registerForm.tenantId" filterable :placeholder="proxy.$t('register.selectPlaceholder')" style="width: 100%">
<el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.password')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.confirmPassword')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item v-if="captchaEnabled" prop="code">
<el-input
v-model="registerForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.code')"
style="width: 63%"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="register-code">
<img :src="codeUrl" class="register-code-img" @click="getCode" />
</div>
</el-form-item>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleRegister">
<span v-if="!loading">{{ proxy.$t('register.register') }}</span>
<span v-else>{{ proxy.$t('register.registering') }}</span>
</el-button>
<div style="float: right">
<router-link class="link-type" :to="'/login'">{{ proxy.$t('register.switchLoginPage') }}</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getCodeImg, register, getTenantList } from '@/api/login';
import { RegisterForm, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE;
const router = useRouter();
const { t } = useI18n();
const registerForm = ref<RegisterForm>({
tenantId: '',
username: '',
password: '',
confirmPassword: '',
code: '',
uuid: '',
userType: 'sys_user'
});
// 租户开关
const tenantEnabled = ref(true);
const equalToPassword = (rule: any, value: string, callback: any) => {
if (registerForm.value.password !== value) {
callback(new Error(t('register.rule.confirmPassword.equalToPassword')));
} else {
callback();
}
};
const registerRules: ElFormRules = {
tenantId: [{ required: true, trigger: 'blur', message: t('register.rule.tenantId.required') }],
username: [
{ required: true, trigger: 'blur', message: t('register.rule.username.required') },
{ min: 2, max: 20, message: t('register.rule.username.length', { min: 2, max: 20 }), trigger: 'blur' }
],
password: [
{ required: true, trigger: 'blur', message: t('register.rule.password.required') },
{ min: 5, max: 20, message: t('register.rule.password.length', { min: 5, max: 20 }), trigger: 'blur' },
{ pattern: /^[^<>"'|\\]+$/, message: t('register.rule.password.pattern', { strings: '< > " \' \\ |' }), trigger: 'blur' }
],
confirmPassword: [
{ required: true, trigger: 'blur', message: t('register.rule.confirmPassword.required') },
{ required: true, validator: equalToPassword, trigger: 'blur' }
],
code: [{ required: true, trigger: 'change', message: t('register.rule.code.required') }]
};
const codeUrl = ref('');
const loading = ref(false);
const captchaEnabled = ref(true);
const registerRef = ref<ElFormInstance>();
// 租户列表
const tenantList = ref<TenantVO[]>([]);
const handleRegister = () => {
registerRef.value?.validate(async (valid: boolean) => {
if (valid) {
loading.value = true;
const [err] = await to(register(registerForm.value));
if (!err) {
const username = registerForm.value.username;
await ElMessageBox.alert('<span style="color: red; ">' + t('register.registerSuccess', { username }) + '</font>', '系统提示', {
app: undefined,
dangerouslyUseHTMLString: true,
type: 'success'
});
await router.push('/login');
} else {
loading.value = false;
if (captchaEnabled.value) {
getCode();
}
}
}
});
};
const getCode = async () => {
const res = await getCodeImg();
const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) {
codeUrl.value = 'data:image/gif;base64,' + data.img;
registerForm.value.uuid = data.uuid;
}
};
const initTenantList = async () => {
const { data } = await getTenantList(false);
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
if (tenantList.value != null && tenantList.value.length !== 0) {
registerForm.value.tenantId = tenantList.value[0].tenantId;
}
}
};
onMounted(() => {
getCode();
initTenantList();
});
</script>
<style lang="scss" scoped>
.register {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
}
.title-box {
display: flex;
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
:deep(.lang-select--style) {
line-height: 0;
color: #7483a3;
}
}
.register-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 40px;
input {
height: 40px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0;
}
}
.register-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.register-code {
width: 33%;
height: 40px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-register-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial, serif;
font-size: 12px;
letter-spacing: 1px;
}
.register-code-img {
height: 40px;
padding-left: 12px;
}
</style>

View File

@ -0,0 +1,316 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="clientList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="id" align="center" prop="id" />
<el-table-column label="客户端id" align="center" prop="clientId" />
<el-table-column label="客户端key" align="center" prop="clientKey" />
<el-table-column label="客户端秘钥" align="center" prop="clientSecret" />
<el-table-column label="授权类型" align="center">
<template #default="scope">
<dict-tag :options="sys_grant_type" :value="scope.row.grantTypeList" />
</template>
</el-table-column>
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="Token活跃超时时间" align="center" prop="activeTimeout" />
<el-table-column label="Token固定超时时间" align="center" prop="timeout" />
<el-table-column key="status" label="状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:client:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:client:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改客户端管理对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="clientFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请输入客户端key" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="form.clientSecret" :disabled="form.id != null" placeholder="请输入客户端秘钥" />
</el-form-item>
<el-form-item label="授权类型" prop="grantTypeList">
<el-select v-model="form.grantTypeList" multiple placeholder="请输入授权类型">
<el-option v-for="dict in sys_grant_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="form.deviceType" placeholder="请输入设备类型">
<el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="activeTimeout" label-width="auto">
<template #label>
<span>
<el-tooltip content="指定时间无操作则过期单位默认30分钟1800秒" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
Token活跃超时时间
</span>
</template>
<el-input v-model="form.activeTimeout" placeholder="请输入Token活跃超时时间" />
</el-form-item>
<el-form-item prop="timeout" label-width="auto">
<template #label>
<span>
<el-tooltip content="指定时间必定过期单位默认七天604800秒" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
Token固定超时时间
</span>
</template>
<el-input v-model="form.timeout" placeholder="请输入Token固定超时时间" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Client" lang="ts">
import { listClient, getClient, delClient, addClient, updateClient, changeStatus } from '@/api/system/client';
import { ClientVO, ClientQuery, ClientForm } from '@/api/system/client/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const { sys_grant_type } = toRefs<any>(proxy?.useDict('sys_grant_type'));
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const clientList = ref<ClientVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const clientFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ClientForm = {
id: undefined,
clientId: undefined,
clientKey: undefined,
clientSecret: undefined,
grantTypeList: undefined,
deviceType: undefined,
activeTimeout: undefined,
timeout: undefined,
status: undefined
};
const data = reactive<PageData<ClientForm, ClientQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
clientId: undefined,
clientKey: undefined,
clientSecret: undefined,
grantType: undefined,
deviceType: undefined,
activeTimeout: undefined,
timeout: undefined,
status: undefined
},
rules: {
id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
clientId: [{ required: true, message: '客户端id不能为空', trigger: 'blur' }],
clientKey: [{ required: true, message: '客户端key不能为空', trigger: 'blur' }],
clientSecret: [{ required: true, message: '客户端秘钥不能为空', trigger: 'blur' }],
grantTypeList: [{ required: true, message: '授权类型不能为空', trigger: 'change' }],
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询客户端管理列表 */
const getList = async () => {
loading.value = true;
const res = await listClient(queryParams.value);
clientList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
clientFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ClientVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加客户端管理';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ClientVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getClient(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改客户端管理';
};
/** 提交按钮 */
const submitForm = () => {
clientFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateClient(form.value).finally(() => (buttonLoading.value = false));
} else {
await addClient(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('修改成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ClientVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delClient(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/client/export',
{
...queryParams.value
},
`client_${new Date().getTime()}.xlsx`
);
};
/** 状态修改 */
const handleStatusChange = async (row: ClientVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '"吗?');
await changeStatus(row.clientId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch (err) {
row.status = row.status === '0' ? '1' : '0';
}
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,261 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="参数名称" prop="configName">
<el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
<el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="configList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
<el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
<el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
<el-table-column label="系统内置" align="center" prop="configType">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.configType" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:config:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:config:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="configFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName">
<el-input v-model="form.configName" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="参数键名" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入参数键名" />
</el-form-item>
<el-form-item label="参数键值" prop="configValue">
<el-input v-model="form.configValue" type="textarea" placeholder="请输入参数键值" />
</el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Config" lang="ts">
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from '@/api/system/config';
import { ConfigForm, ConfigQuery, ConfigVO } from '@/api/system/config/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const configList = ref<ConfigVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const queryFormRef = ref<ElFormInstance>();
const configFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ConfigForm = {
configId: undefined,
configName: '',
configKey: '',
configValue: '',
configType: 'Y',
remark: ''
};
const data = reactive<PageData<ConfigForm, ConfigQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
configName: '',
configKey: '',
configType: ''
},
rules: {
configName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
configKey: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
configValue: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询参数列表 */
const getList = async () => {
loading.value = true;
const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
configList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
configFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ConfigVO[]) => {
ids.value = selection.map((item) => item.configId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加参数';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ConfigVO) => {
reset();
const configId = row?.configId || ids.value[0];
const res = await getConfig(configId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改参数';
};
/** 提交按钮 */
const submitForm = () => {
configFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ConfigVO) => {
const configIds = row?.configId || ids.value;
await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
await delConfig(configIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/config/export',
{
...queryParams.value
},
`config_${new Date().getTime()}.xlsx`
);
};
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
await refreshCache();
proxy?.$modal.msgSuccess('刷新缓存成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,320 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类别编码" prop="deptCategory">
<el-input v-model="queryParams.deptCategory" placeholder="请输入类别编码" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="deptTableRef"
v-loading="loading"
:data="deptList"
row-key="deptId"
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="isExpandAll"
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="deptCategory" align="center" label="类别编码" width="200"></el-table-column>
<el-table-column prop="orderNum" align="center" label="排序" width="200"></el-table-column>
<el-table-column prop="status" align="center" label="状态" width="100">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" align="center" label="操作">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:dept:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dept:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-body width="600px">
<el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col v-if="form.parentId !== 0" :span="24">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select
v-model="form.parentId"
:data="deptOptions"
:props="{ value: 'deptId', label: 'deptName', children: 'children' } as any"
value-key="deptId"
placeholder="选择上级部门"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="类别编码" prop="deptCategory">
<el-input v-model="form.deptCategory" placeholder="请输入类别编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="leader">
<el-select v-model="form.leader" placeholder="请选择负责人">
<el-option v-for="item in deptUserList" :key="item.userId" :label="item.userName" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from '@/api/system/dept';
import { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types';
import { UserVO } from '@/api/system/user/types';
import { listUserByDeptId } from '@/api/system/user';
interface DeptOptionsType {
deptId: number | string;
deptName: string;
children: DeptOptionsType[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const deptList = ref<DeptVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const deptOptions = ref<DeptOptionsType[]>([]);
const isExpandAll = ref(true);
const deptUserList = ref<UserVO[]>([]);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const deptTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const deptFormRef = ref<ElFormInstance>();
const initFormData: DeptForm = {
deptId: undefined,
parentId: undefined,
deptName: undefined,
deptCategory: undefined,
orderNum: 0,
leader: undefined,
phone: undefined,
email: undefined,
status: '0'
};
const initData: PageData<DeptForm, DeptQuery> = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
deptName: undefined,
deptCategory: undefined,
status: undefined
},
rules: {
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
}
};
const data = reactive<PageData<DeptForm, DeptQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data);
/** 查询菜单列表 */
const getList = async () => {
loading.value = true;
const res = await listDept(queryParams.value);
const data = proxy?.handleTree<DeptVO>(res.data, 'deptId');
if (data) {
deptList.value = data;
}
loading.value = false;
};
/** 查询当前部门的所有用户 */
async function getDeptAllUser(deptId: any) {
if (deptId !== null && deptId !== '' && deptId !== undefined) {
const res = await listUserByDeptId(deptId);
deptUserList.value = res.data;
}
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
deptFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(deptList.value, isExpandAll.value);
};
/** 展开/折叠所有 */
const toggleExpandAll = (data: DeptVO[], status: boolean) => {
data.forEach((item) => {
deptTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 新增按钮操作 */
const handleAdd = async (row?: DeptVO) => {
reset();
const res = await listDept();
const data = proxy?.handleTree<DeptOptionsType>(res.data, 'deptId');
if (data) {
deptOptions.value = data;
if (row && row.deptId) {
form.value.parentId = row?.deptId;
}
dialog.visible = true;
dialog.title = '添加部门';
}
};
/** 修改按钮操作 */
const handleUpdate = async (row: DeptVO) => {
reset();
//查询当前部门所有用户
getDeptAllUser(row.deptId);
const res = await getDept(row.deptId);
form.value = res.data;
const response = await listDeptExcludeChild(row.deptId);
const data = proxy?.handleTree<DeptOptionsType>(response.data, 'deptId');
if (data) {
deptOptions.value = data;
if (data.length === 0) {
const noResultsOptions: DeptOptionsType = {
deptId: res.data.parentId,
deptName: res.data.parentName,
children: []
};
deptOptions.value.push(noResultsOptions);
}
}
dialog.visible = true;
dialog.title = '修改部门';
};
/** 提交按钮 */
const submitForm = () => {
deptFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: DeptVO) => {
await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
await delDept(row.deptId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,309 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType">
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="dictLabel">
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel">
<template #default="scope">
<span
v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
>{{ scope.row.dictLabel }}</span
>
<el-tag
v-else
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
:class="scope.row.cssClass"
>{{ scope.row.dictLabel }}</el-tag
>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="form.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="form.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Data" lang="ts">
import { useDictStore } from '@/store/modules/dict';
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
import { DictTypeVO } from '@/api/system/dict/type/types';
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
import { RouteLocationNormalized } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const dataList = ref<DictDataVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const defaultDictType = ref('');
const typeOptions = ref<DictTypeVO[]>([]);
const dataFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 数据标签回显样式
const listClassOptions = ref<Array<{ value: string; label: string }>>([
{ value: 'default', label: '默认' },
{ value: 'primary', label: '主要' },
{ value: 'success', label: '成功' },
{ value: 'info', label: '信息' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '危险' }
]);
const initFormData: DictDataForm = {
dictCode: undefined,
dictLabel: '',
dictValue: '',
cssClass: '',
listClass: 'primary',
dictSort: 0,
remark: ''
};
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: '',
dictLabel: ''
},
rules: {
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询字典类型详细 */
const getTypes = async (dictId: string | number) => {
const { data } = await getType(dictId);
queryParams.value.dictType = data.dictType;
defaultDictType.value = data.dictType;
getList();
};
/** 查询字典类型列表 */
const getTypeList = async () => {
const res = await getDictOptionselect();
typeOptions.value = res.data;
};
/** 查询字典数据列表 */
const getList = async () => {
loading.value = true;
const res = await listData(queryParams.value);
dataList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dataFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 返回按钮操作 */
const handleClose = () => {
const obj: RouteLocationNormalized = {
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined,
path: '/system/dict'
};
proxy?.$tab.closeOpenPage(obj);
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.dictType = defaultDictType.value;
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
form.value.dictType = queryParams.value.dictType;
dialog.visible = true;
dialog.title = '添加字典数据';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictDataVO[]) => {
ids.value = selection.map((item) => item.dictCode);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictDataVO) => {
reset();
const dictCode = row?.dictCode || ids.value[0];
const res = await getData(dictCode);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典数据';
};
/** 提交按钮 */
const submitForm = () => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictCode ? await updateData(form.value) : await addData(form.value);
useDictStore().removeDict(queryParams.value.dictType);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictDataVO) => {
const dictCodes = row?.dictCode || ids.value;
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
await delData(dictCodes);
await getList();
proxy?.$modal.msgSuccess('删除成功');
useDictStore().removeDict(queryParams.value.dictType);
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/dict/data/export',
{
...queryParams.value
},
`dict_data_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTypes(route.params && (route.params.dictId as string));
getTypeList();
});
</script>

View File

@ -0,0 +1,246 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="typeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
<span>{{ scope.row.dictType }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="form.dictType" placeholder="请输入字典类型" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Dict" lang="ts">
import { useDictStore } from '@/store/modules/dict';
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const typeList = ref<DictTypeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const dictFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DictTypeForm = {
dictId: undefined,
dictName: '',
dictType: '',
remark: ''
};
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: ''
},
rules: {
dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询字典类型列表 */
const getList = () => {
loading.value = true;
listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
typeList.value = res.rows;
total.value = res.total;
loading.value = false;
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dictFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加字典类型';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictTypeVO[]) => {
ids.value = selection.map((item) => item.dictId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictTypeVO) => {
reset();
const dictId = row?.dictId || ids.value[0];
const res = await getType(dictId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典类型';
};
/** 提交按钮 */
const submitForm = () => {
dictFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictId ? await updateType(form.value) : await addType(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictTypeVO) => {
const dictIds = row?.dictId || ids.value;
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
await delType(dictIds);
getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/dict/type/export',
{
...queryParams.value
},
`dict_${new Date().getTime()}.xlsx`
);
};
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
await refreshCache();
proxy?.$modal.msgSuccess('刷新成功');
useDictStore().cleanDict();
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,513 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="menuTableRef"
v-loading="loading"
:data="menuList"
row-key="menuId"
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="false"
lazy
:load="getChildrenList"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
<el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="上级菜单">
<el-tree-select
v-model="form.parentId"
:data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
value-key="menuId"
placeholder="选择上级菜单"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="form.menuType">
<el-radio value="M">目录</el-radio>
<el-radio value="C">菜单</el-radio>
<el-radio value="F">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="24">
<el-form-item label="菜单图标" prop="icon">
<!-- 图标选择器 -->
<icon-select v-model="form.icon" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="form.menuName" placeholder="请输入菜单名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon> </el-tooltip
>是否外链
</span>
</template>
<el-radio-group v-model="form.isFrame">
<el-radio value="0"></el-radio>
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item prop="path">
<template #label>
<span>
<el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由地址
</span>
</template>
<el-input v-model="form.path" placeholder="请输入路由地址" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item prop="component">
<template #label>
<span>
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
组件路径
</span>
</template>
<el-input v-model="form.component" placeholder="请输入组件路径" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'M'" :span="12">
<el-form-item>
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
权限字符
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<el-input v-model="form.queryParam" placeholder="请输入路由参数" maxlength="255" />
<template #label>
<span>
<el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由参数
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
是否缓存
</span>
</template>
<el-radio-group v-model="form.isCache">
<el-radio value="0">缓存</el-radio>
<el-radio value="1">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
显示状态
</span>
</template>
<el-radio-group v-model="form.visible">
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
菜单状态
</span>
</template>
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="750px">
<el-tree
ref="menuTreeRef"
class="tree-border"
:data="menuOptions"
show-checkbox
node-key="menuId"
:check-strictly="false"
empty-text="加载中请稍候"
:default-expanded-keys="[0]"
:props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading"> </el-button>
<el-button @click="cancelCascade"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Menu" lang="ts">
import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
interface MenuOptionsType {
menuId: number;
menuName: string;
children: MenuOptionsType[] | undefined;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
const menuList = ref<MenuVO[]>([]);
const menuChildrenListMap = ref({});
const menuExpandMap = ref({});
const loading = ref(true);
const showSearch = ref(true);
const menuOptions = ref<MenuOptionsType[]>([]);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const queryFormRef = ref<ElFormInstance>();
const menuFormRef = ref<ElFormInstance>();
const initFormData = {
path: '',
menuId: undefined,
parentId: 0,
menuName: '',
icon: '',
menuType: MenuTypeEnum.M,
orderNum: 1,
isFrame: '1',
isCache: '0',
visible: '0',
status: '0'
};
const data = reactive<PageData<MenuForm, MenuQuery>>({
form: { ...initFormData },
queryParams: {
menuName: undefined,
status: undefined
},
rules: {
menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
}
});
const menuTableRef = ref<ElTableInstance>();
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data);
/** 获取子菜单列表 */
const getChildrenList = async (row: any, treeNode: unknown, resolve: (data: any[]) => void) => {
menuExpandMap.value[row.menuId] = { row, treeNode, resolve };
const children = menuChildrenListMap.value[row.menuId] || [];
// 菜单的子菜单清空后关闭展开
if (children.length == 0) {
// fix: 处理当菜单只有一个子菜单并被删除,需要将父菜单的展开状态关闭
menuTableRef.value?.updateKeyChildren(row.menuId, children);
}
resolve(children);
};
/** 刷新展开的菜单数据 */
const refreshLoadTree = (parentId: string | number) => {
if (menuExpandMap.value[parentId]) {
const { row, treeNode, resolve } = menuExpandMap.value[parentId];
if (row) {
getChildrenList(row, treeNode, resolve);
if (row.parentId) {
const grandpaMenu = menuExpandMap.value[row.parentId];
getChildrenList(grandpaMenu.row, grandpaMenu.treeNode, grandpaMenu.resolve);
}
}
}
};
/** 重新加载所有已展开的菜单的数据 */
const refreshAllExpandMenuData = () => {
for (const menuId in menuExpandMap.value) {
refreshLoadTree(menuId);
}
};
/** 查询菜单列表 */
const getList = async () => {
loading.value = true;
const res = await listMenu(queryParams.value);
const tempMap = {};
// 存储 父菜单:子菜单列表
for (const menu of res.data) {
const parentId = menu.parentId;
if (!tempMap[parentId]) {
tempMap[parentId] = [];
}
tempMap[parentId].push(menu);
}
// 设置有没有子菜单
for (const menu of res.data) {
menu['hasChildren'] = tempMap[menu.menuId]?.length > 0;
}
menuChildrenListMap.value = tempMap;
menuList.value = tempMap[0] || [];
// 根据新数据重新加载子菜单数据
refreshAllExpandMenuData();
loading.value = false;
};
/** 查询菜单下拉树结构 */
const getTreeselect = async () => {
menuOptions.value = [];
const response = await listMenu();
const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
menuOptions.value.push(menu);
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
menuFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = (row?: MenuVO) => {
reset();
getTreeselect();
row && row.menuId ? (form.value.parentId = row.menuId) : (form.value.parentId = 0);
dialog.visible = true;
dialog.title = '添加菜单';
};
/** 修改按钮操作 */
const handleUpdate = async (row: MenuVO) => {
reset();
await getTreeselect();
if (row.menuId) {
const { data } = await getMenu(row.menuId);
form.value = data;
}
dialog.visible = true;
dialog.title = '修改菜单';
};
/** 提交按钮 */
const submitForm = () => {
menuFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: MenuVO) => {
await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
await delMenu(row.menuId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
const deleteLoading = ref<boolean>(false);
const menuTreeRef = ref<ElTreeInstance>();
const deleteDialog = reactive<DialogOption>({
visible: false,
title: '级联删除菜单'
});
/** 级联删除按钮操作 */
const handleCascadeDelete = () => {
menuTreeRef.value?.setCheckedKeys([]);
getTreeselect();
deleteDialog.visible = true;
};
/** 取消按钮 */
const cancelCascade = () => {
menuTreeRef.value?.setCheckedKeys([]);
deleteDialog.visible = false;
};
/** 删除提交按钮 */
const submitDeleteForm = async () => {
const menuIds = menuTreeRef.value?.getCheckedKeys();
if (menuIds.length < 0) {
proxy?.$modal.msgWarning('请选择要删除的菜单');
return;
}
deleteLoading.value = true;
await cascadeDelMenu(menuIds).finally(() => (deleteLoading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
deleteDialog.visible = false;
};
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.tree-border {
height: 300px;
overflow: auto;
}
</style>

View File

@ -0,0 +1,243 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="操作人员" prop="createByName">
<el-input v-model="queryParams.createByName" placeholder="请输入操作人员" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="noticeType">
<el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable>
<el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="noticeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
<el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
<el-table-column label="公告类型" align="center" prop="noticeType" width="100">
<template #default="scope">
<dict-tag :options="sys_notice_type" :value="scope.row.noticeType" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<dict-tag :options="sys_notice_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createByName" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:notice:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:notice:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改公告对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="780px" append-to-body>
<el-form ref="noticeFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="form.noticeTitle" placeholder="请输入公告标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公告类型" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="请选择">
<el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_notice_status" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="内容">
<editor v-model="form.noticeContent" :min-height="192" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Notice" lang="ts">
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from '@/api/system/notice';
import { NoticeForm, NoticeQuery, NoticeVO } from '@/api/system/notice/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_notice_status, sys_notice_type } = toRefs<any>(proxy?.useDict('sys_notice_status', 'sys_notice_type'));
const noticeList = ref<NoticeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const noticeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: NoticeForm = {
noticeId: undefined,
noticeTitle: '',
noticeType: '',
noticeContent: '',
status: '0',
remark: '',
createByName: ''
};
const data = reactive<PageData<NoticeForm, NoticeQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
noticeTitle: '',
createByName: '',
status: '',
noticeType: ''
},
rules: {
noticeTitle: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
noticeType: [{ required: true, message: '公告类型不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询公告列表 */
const getList = async () => {
loading.value = true;
const res = await listNotice(queryParams.value);
noticeList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
noticeFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: NoticeVO[]) => {
ids.value = selection.map((item) => item.noticeId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加公告';
};
/**修改按钮操作 */
const handleUpdate = async (row?: NoticeVO) => {
reset();
const noticeId = row?.noticeId || ids.value[0];
const { data } = await getNotice(noticeId);
Object.assign(form.value, data);
dialog.visible = true;
dialog.title = '修改公告';
};
/** 提交按钮 */
const submitForm = () => {
noticeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.noticeId ? await updateNotice(form.value) : await addNotice(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: NoticeVO) => {
const noticeIds = row?.noticeId || ids.value;
await proxy?.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?');
await delNotice(noticeIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,344 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="配置key" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="桶名称" prop="bucketName">
<el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="是否默认" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option key="0" label="是" value="0" />
<el-option key="1" label="否" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="ossConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
<el-table-column v-if="columns[2].visible" label="访问站点" align="center" prop="endpoint" width="200" />
<el-table-column v-if="columns[3].visible" label="自定义域名" align="center" prop="domain" width="200" />
<el-table-column v-if="columns[4].visible" label="桶名称" align="center" prop="bucketName" />
<el-table-column v-if="columns[5].visible" label="前缀" align="center" prop="prefix" />
<el-table-column v-if="columns[6].visible" label="域" align="center" prop="region" />
<el-table-column v-if="columns[7].visible" label="桶权限类型" align="center" prop="accessPolicy">
<template #default="scope">
<el-tag v-if="scope.row.accessPolicy === '0'" type="warning">private</el-tag>
<el-tag v-if="scope.row.accessPolicy === '1'" type="success">public</el-tag>
<el-tag v-if="scope.row.accessPolicy === '2'" type="info">custom</el-tag>
</template>
</el-table-column>
<el-table-column v-if="columns[8].visible" label="是否默认" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:ossConfig:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:ossConfig:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改对象存储配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body>
<el-form ref="ossConfigFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="配置key" prop="configKey">
<el-input v-model="form.configKey" placeholder="请输入配置key" />
</el-form-item>
<el-form-item label="访问站点" prop="endpoint">
<el-input v-model="form.endpoint" placeholder="请输入访问站点">
<template #prefix>
<span style="color: #999">{{ protocol }}</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="自定义域名" prop="domain">
<el-input v-model="form.domain" placeholder="请输入自定义域名">
<template #prefix>
<span style="color: #999">{{ protocol }}</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="accessKey" prop="accessKey">
<el-input v-model="form.accessKey" placeholder="请输入accessKey" />
</el-form-item>
<el-form-item label="secretKey" prop="secretKey">
<el-input v-model="form.secretKey" placeholder="请输入秘钥" show-password />
</el-form-item>
<el-form-item label="桶名称" prop="bucketName">
<el-input v-model="form.bucketName" placeholder="请输入桶名称" />
</el-form-item>
<el-form-item label="前缀" prop="prefix">
<el-input v-model="form.prefix" placeholder="请输入前缀" />
</el-form-item>
<el-form-item label="是否HTTPS">
<el-radio-group v-model="form.isHttps">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="桶权限类型">
<el-radio-group v-model="form.accessPolicy">
<el-radio value="0">private</el-radio>
<el-radio value="1">public</el-radio>
<el-radio value="2">custom</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="域" prop="region">
<el-input v-model="form.region" placeholder="请输入域" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="OssConfig" lang="ts">
import { listOssConfig, getOssConfig, delOssConfig, addOssConfig, updateOssConfig, changeOssConfigStatus } from '@/api/system/ossConfig';
import { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/system/ossConfig/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const ossConfigList = ref<OssConfigVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const ossConfigFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 列显隐信息
const columns = ref<FieldOption[]>([
{ key: 0, label: `主建`, visible: false },
{ key: 1, label: `配置key`, visible: true },
{ key: 2, label: `访问站点`, visible: true },
{ key: 3, label: `自定义域名`, visible: true },
{ key: 4, label: `桶名称`, visible: true },
{ key: 5, label: `前缀`, visible: true },
{ key: 6, label: ``, visible: true },
{ key: 7, label: `桶权限类型`, visible: true },
{ key: 8, label: `状态`, visible: true }
]);
const initFormData: OssConfigForm = {
ossConfigId: undefined,
configKey: '',
accessKey: '',
secretKey: '',
bucketName: '',
prefix: '',
endpoint: '',
domain: '',
isHttps: 'N',
accessPolicy: '1',
region: '',
status: '1',
remark: ''
};
const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
form: { ...initFormData },
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
configKey: '',
bucketName: '',
status: ''
},
rules: {
configKey: [{ required: true, message: 'configKey不能为空', trigger: 'blur' }],
accessKey: [
{ required: true, message: 'accessKey不能为空', trigger: 'blur' },
{
min: 2,
max: 200,
message: 'accessKey长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
secretKey: [
{ required: true, message: 'secretKey不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'secretKey长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
bucketName: [
{ required: true, message: 'bucketName不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'bucketName长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
endpoint: [
{ required: true, message: 'endpoint不能为空', trigger: 'blur' },
{
min: 2,
max: 100,
message: 'endpoint名称长度必须介于 2 和 100 之间',
trigger: 'blur'
}
],
accessPolicy: [{ required: true, message: 'accessPolicy不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const protocol = computed(() => (form.value.isHttps === 'Y' ? 'https://' : 'http://'));
/** 查询对象存储配置列表 */
const getList = async () => {
loading.value = true;
const res = await listOssConfig(queryParams.value);
ossConfigList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
ossConfigFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 选择条数 */
const handleSelectionChange = (selection: OssConfigVO[]) => {
ids.value = selection.map((item) => item.ossConfigId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加对象存储配置';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: OssConfigVO) => {
reset();
const ossConfigId = row?.ossConfigId || ids.value[0];
const res = await getOssConfig(ossConfigId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改对象存储配置';
};
/** 提交按钮 */
const submitForm = () => {
ossConfigFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.ossConfigId) {
await updateOssConfig(form.value).finally(() => (buttonLoading.value = false));
} else {
await addOssConfig(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('新增成功');
dialog.visible = false;
await getList();
}
});
};
/** 状态修改 */
const handleStatusChange = async (row: OssConfigVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
await getList();
proxy?.$modal.msgSuccess(text + '成功');
} catch {
return;
} finally {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: OssConfigVO) => {
const ossConfigIds = row?.ossConfigId || ids.value;
await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?');
loading.value = true;
await delOssConfig(ossConfigIds).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,333 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="文件名" prop="fileName">
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="原名" prop="originalName">
<el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="文件后缀" prop="fileSuffix">
<el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRangeCreateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item label="服务商" prop="service">
<el-input v-model="queryParams.service" placeholder="请输入服务商" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleFile">上传文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleImage">上传图片</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:oss:edit']"
:type="previewListResource ? 'danger' : 'warning'"
plain
@click="handlePreviewListResource(!previewListResource)"
>预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
v-if="showTable"
v-loading="loading"
:data="ossList"
border
:header-cell-class-name="handleHeaderClass"
@selection-change="handleSelectionChange"
@header-click="handleHeaderCLick"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" />
<el-table-column label="文件名" align="center" prop="fileName" />
<el-table-column label="原名" align="center" prop="originalName" />
<el-table-column label="文件后缀" align="center" prop="fileSuffix" />
<el-table-column label="文件展示" align="center" prop="url">
<template #default="scope">
<ImagePreview
v-if="previewListResource && checkFileSuffix(scope.row.fileSuffix)"
:width="100"
:height="100"
:src="scope.row.url"
:preview-src-list="[scope.row.url]"
/>
<span v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" v-text="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="上传人" align="center" prop="createByName" />
<el-table-column label="服务商" align="center" prop="service" sortable="custom" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="下载" placement="top">
<el-button v-hasPermi="['system:oss:download']" link type="primary" icon="Download" @click="handleDownload(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:oss:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改OSS对象存储对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="ossFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="文件名">
<fileUpload v-if="type === 0" v-model="form.file" />
<imageUpload v-if="type === 1" v-model="form.file" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Oss" lang="ts">
import { listOss, delOss } from '@/api/system/oss';
import ImagePreview from '@/components/ImagePreview/index.vue';
import { OssForm, OssQuery, OssVO } from '@/api/system/oss/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const ossList = ref<OssVO[]>([]);
const showTable = ref(true);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const type = ref(0);
const previewListResource = ref(true);
const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 默认排序
const defaultSort = ref({ prop: 'createTime', order: 'ascending' });
const ossFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const initFormData = {
file: undefined
};
const data = reactive<PageData<OssForm, OssQuery>>({
form: { ...initFormData },
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
fileName: '',
originalName: '',
fileSuffix: '',
createTime: '',
service: '',
orderByColumn: defaultSort.value.prop,
isAsc: defaultSort.value.order
},
rules: {
file: [{ required: true, message: '文件不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询OSS对象存储列表 */
const getList = async () => {
loading.value = true;
const res = await proxy?.getConfigKey('sys.oss.previewListResource');
previewListResource.value = res?.data === undefined ? true : res.data === 'true';
const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime'));
ossList.value = response.rows;
total.value = response.total;
loading.value = false;
showTable.value = true;
};
function checkFileSuffix(fileSuffix: string | string[]) {
const arr = ['.png', '.jpg', '.jpeg'];
const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix];
return suffixArray.some((suffix) => arr.includes(suffix.toLowerCase()));
}
/** 取消按钮 */
function cancel() {
dialog.visible = false;
reset();
}
/** 表单重置 */
function reset() {
form.value = { ...initFormData };
ossFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
showTable.value = false;
dateRangeCreateTime.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.orderByColumn = defaultSort.value.prop;
queryParams.value.isAsc = defaultSort.value.order;
handleQuery();
}
/** 选择条数 */
function handleSelectionChange(selection: OssVO[]) {
ids.value = selection.map((item) => item.ossId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 设置列的排序为我们自定义的排序 */
const handleHeaderClass = ({ column }: any): any => {
column.order = column.multiOrder;
};
/** 点击表头进行排序 */
const handleHeaderCLick = (column: any) => {
if (column.sortable !== 'custom') {
return;
}
switch (column.multiOrder) {
case 'descending':
column.multiOrder = 'ascending';
break;
case 'ascending':
column.multiOrder = '';
break;
default:
column.multiOrder = 'descending';
break;
}
handleOrderChange(column.property, column.multiOrder);
};
const handleOrderChange = (prop: string, order: string) => {
const orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
const isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
const propIndex = orderByArr.indexOf(prop);
if (propIndex !== -1) {
if (order) {
//排序里已存在 只修改排序
isAscArr[propIndex] = order;
} else {
//如果order为null 则删除排序字段和属性
isAscArr.splice(propIndex, 1); //删除排序
orderByArr.splice(propIndex, 1); //删除属性
}
} else {
//排序里不存在则新增排序
orderByArr.push(prop);
isAscArr.push(order);
}
//合并排序
queryParams.value.orderByColumn = orderByArr.join(',');
queryParams.value.isAsc = isAscArr.join(',');
getList();
};
/** 任务日志列表查询 */
const handleOssConfig = () => {
router.push('/system/oss-config/index');
};
/** 文件按钮操作 */
const handleFile = () => {
reset();
type.value = 0;
dialog.visible = true;
dialog.title = '上传文件';
};
/** 图片按钮操作 */
const handleImage = () => {
reset();
type.value = 1;
dialog.visible = true;
dialog.title = '上传图片';
};
/** 提交按钮 */
const submitForm = () => {
dialog.visible = false;
getList();
};
/** 下载按钮操作 */
const handleDownload = (row: OssVO) => {
proxy?.$download.oss(row.ossId);
};
/** 预览开关按钮 */
const handlePreviewListResource = async (preview: boolean) => {
const text = preview ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
await getList();
proxy?.$modal.msgSuccess(text + '成功');
} catch {
return;
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: OssVO) => {
const ossIds = row?.ossId || ids.value;
await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?');
loading.value = true;
await delOss(ossIds).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,360 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类别编码" prop="postCategory">
<el-input
v-model="queryParams.postCategory"
placeholder="请输入类别编码"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="queryParams.postName" placeholder="请输入岗位名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="部门" prop="deptId">
<el-tree-select
v-model="queryParams.deptId"
:data="deptOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
value-key="id"
placeholder="请选择部门"
check-strictly
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="岗位状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="postList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
<el-table-column label="岗位编码" align="center" prop="postCode" />
<el-table-column label="类别编码" align="center" prop="postCategory" />
<el-table-column label="岗位名称" align="center" prop="postName" />
<el-table-column label="部门" align="center" prop="deptName" />
<el-table-column label="排序" align="center" prop="postSort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:post:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:post:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card>
<!-- 添加或修改岗位对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="postFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="form.postName" placeholder="请输入岗位名称" />
</el-form-item>
<el-form-item label="部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="deptOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
value-key="id"
placeholder="请选择部门"
check-strictly
/>
</el-form-item>
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="form.postCode" placeholder="请输入编码名称" />
</el-form-item>
<el-form-item label="类别编码" prop="postCategory">
<el-input v-model="form.postCategory" placeholder="请输入类别编码" />
</el-form-item>
<el-form-item label="岗位顺序" prop="postSort">
<el-input-number v-model="form.postSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</el-col>
</el-row>
</div>
</template>
<script setup name="Post" lang="ts">
import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
import { DeptVO } from '@/api/system/dept/types';
import api from '@/api/system/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const postList = ref<PostVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const deptTreeRef = ref<ElTreeInstance>();
const postFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: PostForm = {
postId: undefined,
deptId: undefined,
postCode: '',
postName: '',
postCategory: '',
postSort: 0,
status: '0',
remark: ''
};
const data = reactive<PageData<PostForm, PostQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
postCode: '',
postName: '',
postCategory: '',
status: ''
},
rules: {
postName: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs<PageData<PostForm, PostQuery>>(data);
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
deptTreeRef.value?.filter(deptName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await api.deptTreeSelect();
deptOptions.value = res.data;
};
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.belongDeptId = data.id;
queryParams.value.deptId = undefined;
handleQuery();
};
/** 查询岗位列表 */
const getList = async () => {
loading.value = true;
const res = await listPost(queryParams.value);
postList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
postFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (queryParams.value.deptId) {
queryParams.value.belongDeptId = undefined;
}
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
/** 清空左边部门树选中值 */
queryParams.value.belongDeptId = undefined;
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: PostVO[]) => {
ids.value = selection.map((item) => item.postId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加岗位';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: PostVO) => {
reset();
const postId = row?.postId || ids.value[0];
const res = await getPost(postId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改岗位';
};
/** 提交按钮 */
const submitForm = () => {
postFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.postId ? await updatePost(form.value) : await addPost(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: PostVO) => {
const postIds = row?.postId || ids.value;
await proxy?.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?');
await delPost(postIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/post/export',
{
...queryParams.value
},
`post_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTreeSelect(); // 初始化部门数据
getList();
});
</script>

View File

@ -0,0 +1,158 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
批量取消授权
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="取消授权" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
<select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" />
</el-card>
</div>
</template>
<script setup name="AuthUser" lang="ts">
import { allocatedUserList, authUserCancel, authUserCancelAll } from '@/api/system/role';
import { UserQuery } from '@/api/system/user/types';
import { UserVO } from '@/api/system/user/types';
import SelectUser from './selectUser.vue';
import { RouteLocationNormalized } from 'vue-router';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const userList = ref<UserVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const multiple = ref(true);
const total = ref(0);
const userIds = ref<Array<string | number>>([]);
const queryFormRef = ref<ElFormInstance>();
const selectRef = ref<InstanceType<typeof SelectUser>>();
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
roleId: route.params.roleId as string,
userName: undefined,
phonenumber: undefined
});
/** 查询授权用户列表 */
const getList = async () => {
loading.value = true;
const res = await allocatedUserList(queryParams);
userList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 返回按钮
const handleClose = () => {
const obj: RouteLocationNormalized = {
path: '/system/role',
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined
};
proxy?.$tab.closeOpenPage(obj);
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: UserVO[]) => {
userIds.value = selection.map((item) => item.userId);
multiple.value = !selection.length;
};
/** 打开授权用户表弹窗 */
const openSelectUser = () => {
selectRef.value?.show();
};
/** 取消授权按钮操作 */
const cancelAuthUser = async (row: UserVO) => {
await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?');
await authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
await getList();
proxy?.$modal.msgSuccess('取消授权成功');
};
/** 批量取消授权按钮操作 */
const cancelAuthUserAll = async () => {
const roleId = queryParams.roleId;
const uIds = userIds.value.join(',');
await proxy?.$modal.confirm('是否取消选中用户授权数据项?');
await authUserCancelAll({ roleId: roleId, userIds: uIds });
await getList();
proxy?.$modal.msgSuccess('取消授权成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,503 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="角色状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table ref="roleTableRef" border v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
<el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item prop="roleKey">
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
权限字符
</span>
</template>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
<el-tree
ref="menuRef"
class="tree-border"
:data="menuOptions"
show-checkbox
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' } as any"
></el-tree>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog v-model="openDataScope" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dataScopeRef" :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item v-show="form.dataScope === '2'" label="数据权限">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree
ref="deptRef"
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' } as any"
></el-tree>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataScope"> </el-button>
<el-button @click="cancelDataScope"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Role" lang="ts">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from '@/api/system/role';
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const roleList = ref<RoleVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref<DeptTreeOption[]>([]);
const openDataScope = ref(false);
/** 数据范围选项*/
const dataScopeOptions = ref([
{ value: '1', label: '全部数据权限' },
{ value: '2', label: '自定数据权限' },
{ value: '3', label: '本部门数据权限' },
{ value: '4', label: '本部门及以下数据权限' },
{ value: '5', label: '仅本人数据权限' },
{ value: '6', label: '部门及以下或本人数据权限' }
]);
const queryFormRef = ref<ElFormInstance>();
const roleFormRef = ref<ElFormInstance>();
const dataScopeRef = ref<ElFormInstance>();
const menuRef = ref<ElTreeInstance>();
const deptRef = ref<ElTreeInstance>();
const initForm: RoleForm = {
roleId: undefined,
roleSort: 1,
status: '0',
roleName: '',
roleKey: '',
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: '',
dataScope: '1',
menuIds: [],
deptIds: []
};
const data = reactive<PageData<RoleForm, RoleQuery>>({
form: { ...initForm },
queryParams: {
pageNum: 1,
pageSize: 10,
roleName: '',
roleKey: '',
status: ''
},
rules: {
roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }],
roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }]
}
});
const { form, queryParams, rules } = toRefs(data);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
/**
* 查询角色列表
*/
const getList = () => {
loading.value = true;
listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
roleList.value = res.rows;
total.value = res.total;
loading.value = false;
});
};
/**
* 搜索按钮操作
*/
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/**删除按钮操作 */
const handleDelete = async (row?: RoleVO) => {
const roleids = row?.roleId || ids.value;
await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
await delRole(roleids);
getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/role/export',
{
...queryParams.value
},
`role_${new Date().getTime()}.xlsx`
);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: RoleVO[]) => {
ids.value = selection.map((item: RoleVO) => item.roleId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 角色状态修改 */
const handleStatusChange = async (row: RoleVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
await changeRoleStatus(row.roleId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 分配用户 */
const handleAuthUser = (row: RoleVO) => {
router.push('/system/role-auth/user/' + row.roleId);
};
/** 查询菜单树结构 */
const getMenuTreeselect = async () => {
const res = await menuTreeselect();
menuOptions.value = res.data;
};
/** 所有部门节点数据 */
const getDeptAllCheckedKeys = (): any => {
// 目前被选中的部门节点
const checkedKeys = deptRef.value?.getCheckedKeys();
// 半选中的部门节点
const halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift(...halfCheckedKeys);
}
return checkedKeys;
};
/** 重置新增的表单以及其他数据 */
const reset = () => {
menuRef.value?.setCheckedKeys([]);
menuExpand.value = false;
menuNodeAll.value = false;
deptExpand.value = true;
deptNodeAll.value = false;
form.value = { ...initForm };
roleFormRef.value?.resetFields();
};
/** 添加角色 */
const handleAdd = () => {
reset();
getMenuTreeselect();
dialog.visible = true;
dialog.title = '添加角色';
};
/** 修改角色 */
const handleUpdate = async (row?: RoleVO) => {
reset();
const roleId = row?.roleId || ids.value[0];
const { data } = await getRole(roleId);
Object.assign(form.value, data);
form.value.roleSort = Number(form.value.roleSort);
const res = await getRoleMenuTreeselect(roleId);
dialog.title = '修改角色';
dialog.visible = true;
res.checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value?.setChecked(v, true, false);
});
});
};
/** 根据角色ID查询菜单树结构 */
const getRoleMenuTreeselect = (roleId: string | number) => {
return roleMenuTreeselect(roleId).then((res): RoleMenuTree => {
menuOptions.value = res.data.menus;
return res.data;
});
};
/** 根据角色ID查询部门树结构 */
const getRoleDeptTreeSelect = async (roleId: string | number) => {
const res = await deptTreeSelect(roleId);
deptOptions.value = res.data.depts;
return res.data;
};
/** 树权限(展开/折叠)*/
const handleCheckedTreeExpand = (value: boolean, type: string) => {
if (type == 'menu') {
const treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (menuRef.value) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
}
} else if (type == 'dept') {
const treeList = deptOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (deptRef.value) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
}
}
}
};
/** 树权限(全选/全不选) */
const handleCheckedTreeNodeAll = (value: any, type: string) => {
if (type == 'menu') {
menuRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
} else if (type == 'dept') {
deptRef.value?.setCheckedNodes(value ? (deptOptions.value as any) : []);
}
};
/** 树权限(父子联动) */
const handleCheckedTreeConnect = (value: any, type: string) => {
if (type == 'menu') {
form.value.menuCheckStrictly = value;
} else if (type == 'dept') {
form.value.deptCheckStrictly = value;
}
};
/** 所有菜单节点数据 */
const getMenuAllCheckedKeys = (): any => {
// 目前被选中的菜单节点
const checkedKeys = menuRef.value?.getCheckedKeys();
// 半选中的菜单节点
const halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift(...halfCheckedKeys);
}
return checkedKeys;
};
/** 提交按钮 */
const submitForm = () => {
roleFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.menuIds = getMenuAllCheckedKeys();
form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
}
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 选择角色权限范围触发 */
const dataScopeSelectChange = (value: string) => {
if (value !== '2') {
deptRef.value?.setCheckedKeys([]);
}
};
/** 分配数据权限操作 */
const handleDataScope = async (row: RoleVO) => {
const response = await getRole(row.roleId);
Object.assign(form.value, response.data);
const res = await getRoleDeptTreeSelect(row.roleId);
openDataScope.value = true;
dialog.title = '分配数据权限';
await nextTick(() => {
deptRef.value?.setCheckedKeys(res.checkedKeys);
});
};
/** 提交按钮(数据权限) */
const submitDataScope = async () => {
if (form.value.roleId) {
form.value.deptIds = getDeptAllCheckedKeys();
await dataScope(form.value);
proxy?.$modal.msgSuccess('修改成功');
openDataScope.value = false;
getList();
}
};
/** 取消按钮(数据权限)*/
const cancelDataScope = () => {
dataScopeRef.value?.resetFields();
form.value = { ...initForm };
openDataScope.value = false;
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,130 @@
<template>
<el-row>
<el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body>
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table ref="tableRef" border :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</el-row>
</template>
<script setup name="SelectUser" lang="ts">
import { authUserSelectAll, unallocatedUserList } from '@/api/system/role';
import { UserVO } from '@/api/system/user/types';
import { UserQuery } from '@/api/system/user/types';
const props = defineProps({
roleId: {
type: [Number, String],
required: true
}
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const userList = ref<UserVO[]>([]);
const visible = ref(false);
const total = ref(0);
const userIds = ref<Array<string | number>>([]);
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
});
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const show = () => {
queryParams.roleId = props.roleId;
getList();
visible.value = true;
};
/**
* 选择行
*/
const clickRow = (row: any) => {
// ele的bug
tableRef.value?.toggleRowSelection(row, false);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: UserVO[]) => {
userIds.value = selection.map((item: UserVO) => item.userId);
};
/** 查询数据 */
const getList = async () => {
const res = await unallocatedUserList(queryParams);
userList.value = res.rows;
total.value = res.total;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
getList();
};
const emit = defineEmits(['ok']);
/**选择授权用户操作 */
const handleSelectUser = async () => {
const roleId = queryParams.roleId;
const ids = userIds.value.join(',');
if (ids == '') {
proxy?.$modal.msgError('请选择要分配的用户');
return;
}
await authUserSelectAll({ roleId, userIds: ids });
proxy?.$modal.msgSuccess('分配成功');
emit('ok');
visible.value = false;
};
// 暴露
defineExpose({
show
});
</script>

View File

@ -0,0 +1,371 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="租户编号" prop="tenantId">
<el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="联系人" prop="contactUserName">
<el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="企业名称" prop="companyName">
<el-input v-model="queryParams.companyName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="tenantList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="id" align="center" prop="id" />
<el-table-column label="租户编号" align="center" prop="tenantId" />
<el-table-column label="联系人" align="center" prop="contactUserName" />
<el-table-column label="联系电话" align="center" prop="contactPhone" />
<el-table-column label="企业名称" align="center" prop="companyName" />
<el-table-column label="社会信用代码" align="center" prop="licenseNumber" />
<el-table-column label="过期时间" align="center" prop="expireTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="租户状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="同步套餐" placement="top">
<el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)">
</el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:tenant:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改租户对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="tenantFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="企业名称" prop="companyName">
<el-input v-model="form.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="联系人" prop="contactUserName">
<el-input v-model="form.contactUserName" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item v-if="!form.id" label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入系统用户名" maxlength="30" />
</el-form-item>
<el-form-item v-if="!form.id" label="用户密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入系统用户密码" maxlength="20" />
</el-form-item>
<el-form-item label="租户套餐" prop="packageId">
<el-select v-model="form.packageId" :disabled="!!form.tenantId" placeholder="请选择租户套餐" clearable style="width: 100%">
<el-option v-for="item in packageList" :key="item.packageId" :label="item.packageName" :value="item.packageId" />
</el-select>
</el-form-item>
<el-form-item label="过期时间" prop="expireTime">
<el-date-picker v-model="form.expireTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="用户数量" prop="accountCount">
<el-input v-model="form.accountCount" placeholder="请输入用户数量" />
</el-form-item>
<el-form-item label="绑定域名" prop="domain">
<el-input v-model="form.domain" placeholder="请输入绑定域名" />
</el-form-item>
<el-form-item label="企业地址" prop="address">
<el-input v-model="form.address" placeholder="请输入企业地址" />
</el-form-item>
<el-form-item label="企业代码" prop="licenseNumber">
<el-input v-model="form.licenseNumber" placeholder="请输入统一社会信用代码" />
</el-form-item>
<el-form-item label="企业简介" prop="intro">
<el-input v-model="form.intro" type="textarea" placeholder="请输入企业简介" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Tenant" lang="ts">
import {
listTenant,
getTenant,
delTenant,
addTenant,
updateTenant,
changeTenantStatus,
syncTenantPackage,
syncTenantDict
} from '@/api/system/tenant';
import { selectTenantPackage } from '@/api/system/tenantPackage';
import { useUserStore } from '@/store/modules/user';
import { TenantForm, TenantQuery, TenantVO } from '@/api/system/tenant/types';
import { TenantPkgVO } from '@/api/system/tenantPackage/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const userId = ref(userStore.userId);
const tenantList = ref<TenantVO[]>([]);
const packageList = ref<TenantPkgVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const tenantFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: TenantForm = {
id: undefined,
tenantId: undefined,
contactUserName: '',
contactPhone: '',
username: '',
password: '',
companyName: '',
licenseNumber: '',
domain: '',
address: '',
intro: '',
remark: '',
packageId: '',
expireTime: '',
accountCount: 0,
status: '0'
};
const data = reactive<PageData<TenantForm, TenantQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
tenantId: '',
contactUserName: '',
contactPhone: '',
companyName: ''
},
rules: {
id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
tenantId: [{ required: true, message: '租户编号不能为空', trigger: 'blur' }],
contactUserName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
companyName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' },
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询所有租户套餐 */
const getTenantPackage = async () => {
const res = await selectTenantPackage();
packageList.value = res.data;
};
/** 查询租户列表 */
const getList = async () => {
loading.value = true;
const res = await listTenant(queryParams.value);
tenantList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 租户套餐状态修改
const handleStatusChange = async (row: TenantVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
await changeTenantStatus(row.id, row.tenantId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch {
row.status = row.status === '0' ? '1' : '0';
}
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
form.value = { ...initFormData };
tenantFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: TenantVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
getTenantPackage();
dialog.visible = true;
dialog.title = '添加租户';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: TenantVO) => {
reset();
await getTenantPackage();
const _id = row?.id || ids.value[0];
const res = await getTenant(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改租户';
};
/** 提交按钮 */
const submitForm = () => {
tenantFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateTenant(form.value).finally(() => (buttonLoading.value = false));
} else {
await addTenant(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: TenantVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?');
loading.value = true;
await delTenant(_ids).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 同步租户套餐按钮操作 */
const handleSyncTenantPackage = async (row: TenantVO) => {
try {
await proxy?.$modal.confirm('是否确认同步租户套餐租户编号为"' + row.tenantId + '"的数据项?');
loading.value = true;
await syncTenantPackage(row.tenantId, row.packageId);
await getList();
proxy?.$modal.msgSuccess('同步成功');
} catch {
return;
} finally {
loading.value = false;
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/tenant/export',
{
...queryParams.value
},
`tenant_${new Date().getTime()}.xlsx`
);
};
/**同步租户字典*/
const handleSyncTenantDict = async () => {
await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
const res = await syncTenantDict();
proxy?.$modal.msgSuccess(res.msg);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,329 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="套餐名称" prop="packageName">
<el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:add']" type="primary" plain icon="Plus" @click="handleAdd"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:tenantPackage:export']" type="warning" plain icon="Download" @click="handleExport">导出 </el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="tenantPackageList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="租户套餐id" align="center" prop="packageId" />
<el-table-column label="套餐名称" align="center" prop="packageName" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @click="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:tenantPackage:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:tenantPackage:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改租户套餐对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="tenantPackageFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="套餐名称" prop="packageName">
<el-input v-model="form.packageName" placeholder="请输入套餐名称" />
</el-form-item>
<el-form-item label="关联菜单">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选 </el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动 </el-checkbox>
<el-tree
ref="menuTreeRef"
class="tree-border"
:data="menuOptions"
show-checkbox
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中请稍候"
:props="{ label: 'label', children: 'children' } as any"
></el-tree>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="TenantPackage" lang="ts">
import {
listTenantPackage,
getTenantPackage,
delTenantPackage,
addTenantPackage,
updateTenantPackage,
changePackageStatus
} from '@/api/system/tenantPackage';
import { tenantPackageMenuTreeselect } from '@/api/system/menu';
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from '@/api/system/tenantPackage/types';
import { MenuTreeOption } from '@/api/system/menu/types';
import to from 'await-to-js';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tenantPackageList = ref<TenantPkgVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const tenantPackageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: TenantPkgForm = {
packageId: undefined,
packageName: '',
menuIds: '',
remark: '',
menuCheckStrictly: true
};
const data = reactive<PageData<TenantPkgForm, TenantPkgQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
packageName: ''
},
rules: {
packageId: [{ required: true, message: '租户套餐id不能为空', trigger: 'blur' }],
packageName: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
// 所有菜单节点数据
const getMenuAllCheckedKeys = (): any => {
// 目前被选中的菜单节点
const checkedKeys = menuTreeRef.value?.getCheckedKeys();
// 半选中的菜单节点
const halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
if (halfCheckedKeys) {
checkedKeys?.unshift(...halfCheckedKeys);
}
return checkedKeys;
};
/** 根据租户套餐ID查询菜单树结构 */
const getPackageMenuTreeselect = async (packageId: string | number) => {
const res = await tenantPackageMenuTreeselect(packageId);
menuOptions.value = res.data.menus;
return Promise.resolve(res);
};
/** 查询租户套餐列表 */
const getList = async () => {
loading.value = true;
const res = await listTenantPackage(queryParams.value);
tenantPackageList.value = res.rows;
total.value = res.total;
loading.value = false;
};
// 租户套餐状态修改
const handleStatusChange = async (row: TenantPkgVO) => {
const text = row.status === '0' ? '启用' : '停用';
const [err] = await to(proxy?.$modal.confirm('确认要"' + text + '""' + row.packageName + '"套餐吗?') as Promise<any>);
if (err) {
row.status = row.status === '0' ? '1' : '0';
} else {
await changePackageStatus(row.packageId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
}
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
menuTreeRef.value?.setCheckedKeys([]);
menuExpand.value = false;
menuNodeAll.value = false;
form.value = { ...initFormData };
tenantPackageFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: TenantPkgVO[]) => {
ids.value = selection.map((item) => item.packageId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
// 树权限(展开/折叠)
const handleCheckedTreeExpand = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
const treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
if (menuTreeRef.value) {
menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value as boolean;
}
}
}
};
// 树权限(全选/全不选)
const handleCheckedTreeNodeAll = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
menuTreeRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
}
};
// 树权限(父子联动)
const handleCheckedTreeConnect = (value: CheckboxValueType, type: string) => {
if (type == 'menu') {
form.value.menuCheckStrictly = value as boolean;
}
};
/** 新增按钮操作 */
const handleAdd = async () => {
reset();
await getPackageMenuTreeselect(0);
dialog.visible = true;
dialog.title = '添加租户套餐';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: TenantPkgVO) => {
reset();
const _packageId = row?.packageId || ids.value[0];
const response = await getTenantPackage(_packageId);
form.value = response.data;
const res = await getPackageMenuTreeselect(_packageId);
dialog.visible = true;
dialog.title = '修改租户套餐';
res.data.checkedKeys.forEach((v) => {
nextTick(() => {
menuTreeRef.value?.setChecked(v, true, false);
});
});
};
/** 提交按钮 */
const submitForm = () => {
tenantPackageFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
form.value.menuIds = getMenuAllCheckedKeys();
if (form.value.packageId != null) {
await updateTenantPackage(form.value).finally(() => (buttonLoading.value = false));
} else {
await addTenantPackage(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: TenantPkgVO) => {
const _packageIds = row?.packageId || ids.value;
await proxy?.$modal.confirm('是否确认删除租户套餐编号为"' + _packageIds + '"的数据项?').finally(() => {
loading.value = false;
});
await delTenantPackage(_packageIds);
loading.value = true;
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/tenant/package/export',
{
...queryParams.value
},
`tenantPackage_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,146 @@
<template>
<div class="p-2">
<div class="panel">
<h4 class="panel-title">基本信息</h4>
<el-form :model="form" :inline="true">
<el-row :gutter="10">
<el-col :span="2.5">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
</el-col>
<el-col :span="2.5">
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="panel">
<h4 class="panel-title">角色信息</h4>
<div>
<el-table
ref="tableRef"
v-loading="loading"
border
:row-key="getRowKey"
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
@row-click="clickRow"
@selection-change="handleSelectionChange"
>
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
<div style="text-align: center; margin-left: -120px; margin-top: 30px">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
<div></div>
</div>
</div>
</div>
</template>
<script setup name="AuthRole" lang="ts">
import { RoleVO } from '@/api/system/role/types';
import { getAuthRole, updateAuthRole } from '@/api/system/user';
import { UserForm } from '@/api/system/user/types';
import { RouteLocationNormalized } from 'vue-router';
import { parseTime } from '@/utils/ruoyi';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const loading = ref(true);
const total = ref(0);
const pageNum = ref(1);
const pageSize = ref(10);
const roleIds = ref<Array<string | number>>([]);
const roles = ref<RoleVO[]>([]);
const form = ref<Partial<UserForm>>({
nickName: undefined,
userName: '',
userId: undefined
});
const tableRef = ref<ElTableInstance>();
/** 单击选中行数据 */
const clickRow = (row: RoleVO) => {
if (checkSelectable(row)) {
row.flag = !row.flag;
tableRef.value?.toggleRowSelection(row, row.flag);
}
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: RoleVO[]) => {
roleIds.value = selection.map((item) => item.roleId);
};
/** 保存选中的数据编号 */
const getRowKey = (row: RoleVO): string => {
return String(row.roleId);
};
/** 检查角色状态 */
const checkSelectable = (row: RoleVO): boolean => {
return row.status === '0';
};
/** 关闭按钮 */
const close = () => {
const obj: RouteLocationNormalized = {
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined,
path: '/system/user'
};
proxy?.$tab.closeOpenPage(obj);
};
/** 提交按钮 */
const submitForm = async () => {
const userId = form.value.userId;
const rIds = roleIds.value.join(',');
await updateAuthRole({ userId: userId as string, roleIds: rIds });
proxy?.$modal.msgSuccess('授权成功');
close();
};
const getList = async () => {
const userId = route.params && route.params.userId;
if (userId) {
loading.value = true;
const res = await getAuthRole(userId as string);
Object.assign(form.value, res.data.user);
Object.assign(roles.value, res.data.roles);
total.value = roles.value.length;
await nextTick(() => {
roles.value.forEach((row) => {
if (row?.flag) {
tableRef.value?.toggleRowSelection(row, true);
}
});
});
loading.value = false;
}
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,668 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="用户状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
<el-button plain type="info">
更多
<el-icon class="el-icon--right"><arrow-down /></el-icon
></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
<!-- 注意 由于el-dropdown-item标签是延迟加载的 所以v-has-permi自定义标签不生效 需要使用v-if调用方法执行 -->
<el-dropdown-item v-if="checkPermi(['system:user:import'])" icon="Top" @click="handleImport">导入数据</el-dropdown-item>
<el-dropdown-item v-if="checkPermi(['system:user:export'])" icon="Download" @click="handleExport">导出数据</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
<el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
<el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
<template #default="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column v-if="columns[6].visible" label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top">
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top">
<el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
<el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
<!-- 添加或修改用户配置对话框 -->
<el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="归属部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="enabledDeptOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
value-key="id"
placeholder="请选择归属部门"
check-strictly
@change="handleDeptChange"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
<el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="用户性别">
<el-select v-model="form.sex" placeholder="请选择">
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-option
v-for="item in postOptions"
:key="item.postId"
:label="item.postName"
:value="item.postId"
:disabled="item.status == '1'"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="item.roleId"
:disabled="item.status == '1'"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel()"> </el-button>
</div>
</template>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload">
<i-ep-upload-filled />
</el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="text-center el-upload__tip">
<div class="el-upload__tip"><el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据</div>
<span>仅允许导入xlsxlsx格式文件</span>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User" lang="ts">
import api from '@/api/system/user';
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { RoleVO } from '@/api/system/role/types';
import { PostQuery, PostVO } from '@/api/system/post/types';
import { treeselect } from '@/api/system/dept';
import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import { hasPermi } from '@/directive/permission';
import { checkPermi } from '@/utils/permission';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex'));
const userList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptTreeVO[]>([]);
const enabledDeptOptions = ref<DeptTreeVO[]>([]);
const initPassword = ref<string>('');
const postOptions = ref<PostVO[]>([]);
const roleOptions = ref<RoleVO[]>([]);
/*** 用户导入参数 */
const upload = reactive<ImportOption>({
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: '',
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: globalHeaders(),
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
});
// 列显隐信息
const columns = ref<FieldOption[]>([
{ key: 0, label: `用户编号`, visible: false, children: [] },
{ key: 1, label: `用户名称`, visible: true, children: [] },
{ key: 2, label: `用户昵称`, visible: true, children: [] },
{ key: 3, label: `部门`, visible: true, children: [] },
{ key: 4, label: `手机号码`, visible: true, children: [] },
{ key: 5, label: `状态`, visible: true, children: [] },
{ key: 6, label: `创建时间`, visible: true, children: [] }
]);
const deptTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const uploadRef = ref<ElUploadInstance>();
const formDialogRef = ref<ElDialogInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: UserForm = {
userId: undefined,
deptId: undefined,
userName: '',
nickName: undefined,
password: '',
phonenumber: undefined,
email: undefined,
sex: undefined,
status: '0',
remark: '',
postIds: [],
roleIds: []
};
const initData: PageData<UserForm, UserQuery> = {
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
userName: '',
phonenumber: '',
status: '',
deptId: '',
roleId: ''
},
rules: {
userName: [
{ required: true, message: '用户名称不能为空', trigger: 'blur' },
{
min: 2,
max: 20,
message: '用户名称长度必须介于 2 和 20 之间',
trigger: 'blur'
}
],
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
password: [
{ required: true, message: '用户密码不能为空', trigger: 'blur' },
{
min: 5,
max: 20,
message: '用户密码长度必须介于 5 和 20 之间',
trigger: 'blur'
},
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
],
email: [
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
],
phonenumber: [
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
}
],
roleIds: [{ required: true, message: '用户角色不能为空', trigger: 'blur' }]
}
};
const data = reactive<PageData<UserForm, UserQuery>>(initData);
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
deptTreeRef.value?.filter(deptName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询用户列表 */
const getList = async () => {
loading.value = true;
const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
loading.value = false;
userList.value = res.rows;
total.value = res.total;
};
/** 查询部门下拉树结构 */
const getDeptTree = async () => {
const res = await api.deptTreeSelect();
deptOptions.value = res.data;
enabledDeptOptions.value = filterDisabledDept(res.data);
};
/** 过滤禁用的部门 */
const filterDisabledDept = (deptList: DeptTreeVO[]) => {
return deptList.filter((dept) => {
if (dept.disabled) {
return false;
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children);
}
return true;
});
};
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id;
handleQuery();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
deptTreeRef.value?.setCurrentKey(undefined);
handleQuery();
};
/** 删除按钮操作 */
const handleDelete = async (row?: UserVO) => {
const userIds = row?.userId || ids.value;
const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any);
if (!err) {
await api.delUser(userIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
}
};
/** 用户状态修改 */
const handleStatusChange = async (row: UserVO) => {
const text = row.status === '0' ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
await api.changeUserStatus(row.userId, row.status);
proxy?.$modal.msgSuccess(text + '成功');
} catch (err) {
row.status = row.status === '0' ? '1' : '0';
}
};
/** 跳转角色分配 */
const handleAuthRole = (row: UserVO) => {
const userId = row.userId;
router.push('/system/user-auth/role/' + userId);
};
/** 重置密码按钮操作 */
const handleResetPwd = async (row: UserVO) => {
const [err, res] = await to(
ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return '不能包含非法字符:< > " \' \\ |';
}
}
})
);
if (!err && res) {
await api.resetUserPwd(row.userId, res.value);
proxy?.$modal.msgSuccess('修改成功,新密码是:' + res.value);
}
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
ids.value = selection.map((item) => item.userId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 导入按钮操作 */
const handleImport = () => {
upload.title = '用户导入';
upload.open = true;
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/user/export',
{
...queryParams.value
},
`user_${new Date().getTime()}.xlsx`
);
};
/** 下载模板操作 */
const importTemplate = () => {
proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`);
};
/**文件上传中处理 */
const handleFileUploadProgress = () => {
upload.isUploading = true;
};
/** 文件上传成功处理 */
const handleFileSuccess = (response: any, file: UploadFile) => {
upload.open = false;
upload.isUploading = false;
uploadRef.value?.handleRemove(file);
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
dangerouslyUseHTMLString: true
});
getList();
};
/** 提交上传文件 */
function submitFileForm() {
uploadRef.value?.submit();
}
/** 重置操作表单 */
const reset = () => {
form.value = { ...initFormData };
userFormRef.value?.resetFields();
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 新增按钮操作 */
const handleAdd = async () => {
reset();
const { data } = await api.getUser();
dialog.visible = true;
dialog.title = '新增用户';
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.password = initPassword.value.toString();
};
/** 修改按钮操作 */
const handleUpdate = async (row?: UserForm) => {
reset();
const userId = row?.userId || ids.value[0];
const { data } = await api.getUser(userId);
dialog.visible = true;
dialog.title = '修改用户';
Object.assign(form.value, data.user);
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.postIds = data.postIds;
form.value.roleIds = data.roleIds;
form.value.password = '';
};
/** 提交按钮 */
const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/**
* 关闭用户弹窗
*/
const closeDialog = () => {
dialog.visible = false;
resetForm();
};
/**
* 重置表单
*/
const resetForm = () => {
userFormRef.value?.resetFields();
userFormRef.value?.clearValidate();
form.value.id = undefined;
form.value.status = '1';
};
onMounted(() => {
getDeptTree(); // 初始化部门数据
getList(); // 初始化列表数据
proxy?.getConfigKey('sys.user.initPassword').then((response) => {
initPassword.value = response.data;
});
});
async function handleDeptChange(value: number | string) {
const response = await optionselect(value);
postOptions.value = response.data;
form.value.postIds = [];
}
</script>

View File

@ -0,0 +1,122 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<template #header>
<div class="clearfix">
<span>个人信息</span>
</div>
</template>
<div>
<div class="text-center">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ state.user.userName }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ state.user.phonenumber }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ state.roleGroup }}</div>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<template #header>
<div class="clearfix">
<span>基本资料</span>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="userForm" />
</el-tab-pane>
<el-tab-pane label="修改密码" name="resetPwd">
<resetPwd />
</el-tab-pane>
<el-tab-pane label="第三方应用" name="thirdParty">
<thirdParty :auths="state.auths" />
</el-tab-pane>
<el-tab-pane label="在线设备" name="onlineDevice">
<onlineDevice :devices="state.devices" />
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="Profile" lang="ts">
import UserAvatar from './userAvatar.vue';
import UserInfo from './userInfo.vue';
import ResetPwd from './resetPwd.vue';
import ThirdParty from './thirdParty.vue';
import OnlineDevice from './onlineDevice.vue';
import { getAuthList } from '@/api/system/social/auth';
import { getUserProfile } from '@/api/system/user';
import { getOnline } from '@/api/monitor/online';
import { UserVO } from '@/api/system/user/types';
const activeTab = ref('userinfo');
interface State {
user: Partial<UserVO>;
roleGroup: string;
postGroup: string;
auths: any;
devices: any;
}
const state = ref<State>({
user: {},
roleGroup: '',
postGroup: '',
auths: [],
devices: []
});
const userForm = ref({});
const getUser = async () => {
const res = await getUserProfile();
state.value.user = res.data.user;
userForm.value = { ...res.data.user };
state.value.roleGroup = res.data.roleGroup;
state.value.postGroup = res.data.postGroup;
};
const getAuths = async () => {
const res = await getAuthList();
state.value.auths = res.data;
};
const getOnlines = async () => {
const res = await getOnline();
state.value.devices = res.rows;
};
onMounted(() => {
getUser();
getAuths();
getOnlines();
});
</script>

View File

@ -0,0 +1,57 @@
<template>
<div>
<el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px">
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
</template>
</el-table-column>
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handldDelOnline(scope.row)"> </el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup name="Online" lang="ts">
import { delOnline } from '@/api/monitor/online';
import { propTypes } from '@/utils/propTypes';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const props = defineProps({
devices: propTypes.any.isRequired
});
const devices = computed(() => props.devices);
/** 删除按钮操作 */
const handldDelOnline = (row: any) => {
ElMessageBox.confirm('删除设备后,在该设备登录需要重新进行验证')
.then(() => {
return delOnline(row.tokenId);
})
.then((res: any) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess('删除成功');
proxy?.$tab.refreshPage();
} else {
proxy?.$modal.msgError(res.msg);
}
})
.catch(() => {});
};
</script>

View File

@ -0,0 +1,73 @@
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { updateUserPwd } from '@/api/system/user';
import type { ResetPwdForm } from '@/api/system/user/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref<ElFormInstance>();
const user = ref<ResetPwdForm>({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
const equalToPassword = (rule: any, value: string, callback: any) => {
if (user.value.newPassword !== value) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
};
const rules = ref({
oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
newPassword: [
{ required: true, message: '新密码不能为空', trigger: 'blur' },
{
min: 6,
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
},
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{
required: true,
validator: equalToPassword,
trigger: 'blur'
}
]
});
/** 提交按钮 */
const submit = () => {
pwdRef.value?.validate(async (valid: boolean) => {
if (valid) {
await updateUserPwd(user.value.oldPassword, user.value.newPassword);
proxy?.$modal.msgSuccess('修改成功');
}
});
};
/** 关闭按钮 */
const close = () => {
proxy?.$tab.closePage();
};
</script>

View File

@ -0,0 +1,145 @@
<template>
<div>
<el-table :data="auths" border style="width: 100%; height: 100%; font-size: 14px">
<el-table-column label="序号" width="50" type="index" />
<el-table-column label="绑定账号平台" width="140" align="center" prop="source" show-overflow-tooltip />
<el-table-column label="头像" width="120" align="center" prop="avatar">
<template #default="scope">
<img :src="scope.row.avatar" style="width: 45px; height: 45px" />
</template>
</el-table-column>
<el-table-column label="系统账号" width="180" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="绑定时间" width="180" align="center" prop="createTime" />
<el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="text" @click="unlockAuth(scope.row)">解绑</el-button>
</template>
</el-table-column>
</el-table>
<div id="git-user-binding">
<h4 class="provider-desc">你可以绑定以下第三方帐号</h4>
<div id="authlist" class="user-bind">
<a class="third-app" href="#" title="使用 微信 账号授权登录" @click="authUrl('wechat')">
<div class="git-other-login-icon">
<svg-icon icon-class="wechat" />
</div>
<span class="app-name">WeiXin</span>
</a>
<a class="third-app" href="#" title="使用 MaxKey 账号授权登录" @click="authUrl('maxkey')">
<div class="git-other-login-icon">
<svg-icon icon-class="maxkey" />
</div>
<span class="app-name">MaxKey</span>
</a>
<a class="third-app" href="#" title="使用 TopIam 账号授权登录" @click="authUrl('topiam')">
<div class="git-other-login-icon">
<svg-icon icon-class="topiam" />
</div>
<span class="app-name">TopIam</span>
</a>
<a class="third-app" href="#" title="使用 Gitee 账号授权登录" @click="authUrl('gitee')">
<div class="git-other-login-icon">
<svg-icon icon-class="gitee" />
</div>
<span class="app-name">Gitee</span>
</a>
<a class="third-app" href="#" title="使用 GitHub 账号授权登录" @click="authUrl('github')">
<div class="git-other-login-icon">
<svg-icon icon-class="github" />
</div>
<span class="app-name">Github</span>
</a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { authUnlock, authBinding } from '@/api/system/social/auth';
import { propTypes } from '@/utils/propTypes';
import { useUserStore } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
auths: propTypes.any.isRequired
});
const auths = computed(() => props.auths);
const unlockAuth = (row: any) => {
ElMessageBox.confirm('您确定要解除"' + row.source + '"的账号绑定吗?')
.then(() => {
return authUnlock(row.id);
})
.then((res: any) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess('解绑成功');
proxy?.$tab.refreshPage();
} else {
proxy?.$modal.msgError(res.msg);
}
})
.catch(() => {});
};
const authUrl = (source: string) => {
authBinding(source, useUserStore().tenantId).then((res: any) => {
if (res.code === 200) {
window.location.href = res.data;
} else {
proxy?.$modal.msgError(res.msg);
}
});
};
</script>
<style lang="scss" scoped>
.user-bind .third-app {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
min-width: 80px;
float: left;
}
.user-bind {
font-size: 1rem;
text-align: start;
height: 50px;
margin-top: 10px;
}
.git-other-login-icon > img {
height: 32px;
}
a {
text-decoration: none;
cursor: pointer;
color: #005980;
}
.provider-desc {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Liberation Sans',
'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei', 'ST Heiti', SimHei, SimSun,
'WenQuanYi Zen Hei Sharp', sans-serif;
font-size: 1.071rem;
}
td > img {
height: 20px;
width: 20px;
display: inline-block;
border-radius: 50%;
margin-right: 5px;
}
</style>

View File

@ -0,0 +1,182 @@
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog v-model="open" :title="title" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
v-if="visible"
ref="cropper"
:img="options.img"
:info="true"
:auto-crop="options.autoCrop"
:auto-crop-width="options.autoCropWidth"
:auto-crop-height="options.autoCropHeight"
:fixed-box="options.fixedBox"
:output-type="options.outputType"
@real-time="realTime"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<img :src="options.previews.url" :style="options.previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button>
选择
<el-icon class="el-icon--right">
<Upload />
</el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()"> </el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { uploadAvatar } from '@/api/system/user';
import { useUserStore } from '@/store/modules/user';
import { UploadRawFile } from 'element-plus';
interface Options {
img: string | any; // 裁剪图片的地址
autoCrop: boolean; // 是否默认生成截图框
autoCropWidth: number; // 默认生成截图框宽度
autoCropHeight: number; // 默认生成截图框高度
fixedBox: boolean; // 固定截图框大小 不允许改变
fileName: string;
previews: any; // 预览数据
outputType: string;
visible: boolean;
}
const userStore = useUserStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const open = ref(false);
const visible = ref(false);
const title = ref('修改头像');
const cropper = ref<any>({});
//图片裁剪数据
const options = reactive<Options>({
img: userStore.avatar,
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true,
outputType: 'png',
fileName: '',
previews: {},
visible: false
});
/** 编辑头像 */
const editCropper = () => {
open.value = true;
};
/** 打开弹出层结束时的回调 */
const modalOpened = () => {
visible.value = true;
};
/** 覆盖默认上传行为 */
const requestUpload = (): any => {};
/** 向左旋转 */
const rotateLeft = () => {
cropper.value.rotateLeft();
};
/** 向右旋转 */
const rotateRight = () => {
cropper.value.rotateRight();
};
/** 图片缩放 */
const changeScale = (num: number) => {
num = num || 1;
cropper.value.changeScale(num);
};
/** 上传预处理 */
const beforeUpload = (file: UploadRawFile): any => {
if (file.type.indexOf('image/') == -1) {
proxy?.$modal.msgError('文件格式错误,请上传图片类型,如JPGPNG后缀的文件。');
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
options.fileName = file.name;
};
}
};
/** 上传图片 */
const uploadImg = async () => {
cropper.value.getCropBlob(async (data: any) => {
const formData = new FormData();
formData.append('avatarfile', data, options.fileName);
const res = await uploadAvatar(formData);
open.value = false;
options.img = res.data.imgUrl;
userStore.setAvatar(options.img);
proxy?.$modal.msgSuccess('修改成功');
visible.value = false;
});
};
/** 实时预览 */
const realTime = (data: any) => {
options.previews = data;
};
/** 关闭窗口 */
const closeDialog = () => {
options.img = userStore.avatar;
options.visible = false;
};
</script>
<style lang="scss" scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: '+';
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #eee;
background: rgba(0, 0, 0, 0.5);
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
border-radius: 50%;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="userForm.nickName" maxlength="30" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="userForm.phonenumber" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userForm.email" maxlength="50" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userForm.sex">
<el-radio value="0"></el-radio>
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { updateUserProfile } from '@/api/system/user';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
user: propTypes.any.isRequired
});
const userForm = computed(() => props.user);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userRef = ref<ElFormInstance>();
const rule: ElFormRules = {
nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
email: [
{ required: true, message: '邮箱地址不能为空', trigger: 'blur' },
{
type: 'email',
message: '请输入正确的邮箱地址',
trigger: ['blur', 'change']
}
],
phonenumber: [
{
required: true,
message: '手机号码不能为空',
trigger: 'blur'
},
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
};
const rules = ref<ElFormRules>(rule);
/** 提交按钮 */
const submit = () => {
userRef.value?.validate(async (valid: boolean) => {
if (valid) {
await updateUserProfile(props.user);
proxy?.$modal.msgSuccess('修改成功');
}
});
};
/** 关闭按钮 */
const close = () => {
proxy?.$tab.closePage();
};
</script>

View File

@ -0,0 +1,49 @@
<template>
<el-form ref="basicInfoForm" :model="infoForm" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item label="表名称" prop="tableName">
<el-input v-model="infoForm.tableName" placeholder="请输入仓库名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表描述" prop="tableComment">
<el-input v-model="infoForm.tableComment" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实体类名称" prop="className">
<el-input v-model="infoForm.className" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="functionAuthor">
<el-input v-model="infoForm.functionAuthor" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="infoForm.remark" type="textarea" :rows="3"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const prop = defineProps({
info: propTypes.any.def({})
});
const infoForm = computed(() => prop.info);
// 表单校验
const rules = ref({
tableName: [{ required: true, message: '请输入表名称', trigger: 'blur' }],
tableComment: [{ required: true, message: '请输入表描述', trigger: 'blur' }],
className: [{ required: true, message: '请输入实体类名称', trigger: 'blur' }],
functionAuthor: [{ required: true, message: '请输入作者', trigger: 'blur' }]
});
</script>

View File

@ -0,0 +1,198 @@
<template>
<el-card>
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basic">
<basic-info-form ref="basicInfo" :info="info" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="columnInfo">
<el-table ref="dragTable" border :data="columns" row-key="columnId" :max-height="tableHeight">
<el-table-column label="序号" type="index" min-width="5%" />
<el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" />
<el-table-column label="字段描述" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.columnComment"></el-input>
</template>
</el-table-column>
<el-table-column label="物理类型" prop="columnType" min-width="10%" :show-overflow-tooltip="true" />
<el-table-column label="Java类型" min-width="11%">
<template #default="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.javaField"></el-input>
</template>
</el-table-column>
<el-table-column label="插入" min-width="5%">
<template #default="scope">
<el-checkbox v-model="scope.row.isInsert" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="编辑" min-width="5%">
<template #default="scope">
<el-checkbox v-model="scope.row.isEdit" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="列表" min-width="5%">
<template #default="scope">
<el-checkbox v-model="scope.row.isList" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询" min-width="5%">
<template #default="scope">
<el-checkbox v-model="scope.row.isQuery" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template #default="scope">
<el-select v-model="scope.row.queryType">
<el-option label="=" value="EQ" />
<el-option label="!=" value="NE" />
<el-option label=">" value="GT" />
<el-option label=">=" value="GE" />
<el-option label="<" value="LT" />
<el-option label="<=" value="LE" />
<el-option label="LIKE" value="LIKE" />
<el-option label="BETWEEN" value="BETWEEN" />
</el-select>
</template>
</el-table-column>
<el-table-column label="必填" min-width="5%">
<template #default="scope">
<el-checkbox v-model="scope.row.isRequired" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" />
<el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" />
<el-option label="富文本控件" value="editor" />
</el-select>
</template>
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择" value-on-clear="">
<el-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName" :value="dict.dictType">
<span style="float: left">{{ dict.dictName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo">
<gen-info-form ref="genInfo" :info="info" :tables="tables" />
</el-tab-pane>
</el-tabs>
<el-form label-width="100px">
<div style="text-align: center; margin-left: -100px; margin-top: 10px">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
</el-form>
</el-card>
</template>
<script setup name="GenEdit" lang="ts">
import { getGenTable, updateGenTable } from '@/api/tool/gen';
import { DbColumnVO, DbTableVO } from '@/api/tool/gen/types';
import { optionselect as getDictOptionselect } from '@/api/system/dict/type';
import { DictTypeVO } from '@/api/system/dict/type/types';
import BasicInfoForm from './basicInfoForm.vue';
import GenInfoForm from './genInfoForm.vue';
import { RouteLocationNormalized } from 'vue-router';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const activeName = ref('columnInfo');
const tableHeight = ref(document.documentElement.scrollHeight - 245 + 'px');
const tables = ref<DbTableVO[]>([]);
const columns = ref<DbColumnVO[]>([]);
const dictOptions = ref<DictTypeVO[]>([]);
const info = ref<Partial<DbTableVO>>({});
const basicInfo = ref<InstanceType<typeof BasicInfoForm>>();
const genInfo = ref<InstanceType<typeof GenInfoForm>>();
/** 提交按钮 */
const submitForm = () => {
const basicForm = basicInfo.value?.$refs.basicInfoForm;
const genForm = genInfo.value?.$refs.genInfoForm;
Promise.all([basicForm, genForm].map(getFormPromise)).then(async (res) => {
const validateResult = res.every((item) => !!item);
if (validateResult) {
const genTable: any = Object.assign({}, info.value);
genTable.columns = columns.value;
genTable.params = {
treeCode: info.value?.treeCode,
treeName: info.value.treeName,
treeParentCode: info.value.treeParentCode,
parentMenuId: info.value.parentMenuId
};
const response = await updateGenTable(genTable);
proxy?.$modal.msgSuccess(response.msg);
if (response.code === 200) {
close();
}
} else {
proxy?.$modal.msgError('表单校验未通过,请重新检查提交内容');
}
});
};
const getFormPromise = (form: any) => {
return new Promise((resolve) => {
form.validate((res: any) => {
resolve(res);
});
});
};
const close = () => {
const obj: RouteLocationNormalized = {
path: '/tool/gen',
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
redirectedFrom: undefined,
query: { t: Date.now().toString(), pageNum: route.query.pageNum }
};
proxy?.$tab.closeOpenPage(obj);
};
(async () => {
const tableId = route.params && (route.params.tableId as string);
if (tableId) {
// 获取表详细信息
const res = await getGenTable(tableId);
columns.value = res.data.rows;
info.value = res.data.info;
tables.value = res.data.tables;
/** 查询字典下拉列表 */
const response = await getDictOptionselect();
dictOptions.value = response.data;
}
})();
</script>

View File

@ -0,0 +1,294 @@
<template>
<el-form ref="genInfoForm" :model="infoForm" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item prop="tplCategory">
<template #label>生成模板</template>
<el-select v-model="infoForm.tplCategory" @change="tplSelectChange">
<el-option label="单表(增删改查)" value="crud" />
<el-option label="树表(增删改查)" value="tree" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="packageName">
<template #label>
生成包路径
<el-tooltip content="生成在哪个java包下例如 com.ruoyi.system" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.packageName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="moduleName">
<template #label>
生成模块名
<el-tooltip content="可理解为子系统名,例如 system" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.moduleName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="businessName">
<template #label>
生成业务名
<el-tooltip content="可理解为功能英文名,例如 user" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.businessName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="functionName">
<template #label>
生成功能名
<el-tooltip content="用作类描述,例如 用户" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.functionName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
上级菜单
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-tree-select
v-model="infoForm.parentMenuId"
:data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
value-key="menuId"
node-key="menuId"
placeholder="选择上级菜单"
check-strictly
filterable
clearable
highlight-current
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="genType">
<template #label>
生成代码方式
<el-tooltip content="默认为zip压缩包下载也可以自定义生成路径" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-radio v-model="infoForm.genType" value="0">zip压缩包</el-radio>
<el-radio v-model="infoForm.genType" value="1">自定义路径</el-radio>
</el-form-item>
</el-col>
<el-col v-if="infoForm.genType == '1'" :span="24">
<el-form-item prop="genPath">
<template #label>
自定义路径
<el-tooltip content="填写磁盘绝对路径若不填写则生成到当前Web项目下" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.genPath">
<template #append>
<el-dropdown>
<el-button type="primary">
最近路径快速选择
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="infoForm.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<template v-if="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4>
<el-row v-show="info.tplCategory == 'tree'">
<el-col :span="12">
<el-form-item>
<template #label>
树编码字段
<el-tooltip content="树显示的编码字段名, 如dept_id" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.treeCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
树父编码字段
<el-tooltip content="树显示的父编码字段名, 如parent_Id" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.treeParentCode" placeholder="请选择">
<el-option
v-for="(column, index) in infoForm.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
树名称字段
<el-tooltip content="树节点的显示名称字段名, 如dept_name" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.treeName" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</template>
<template v-if="info.tplCategory == 'sub'">
<h4 class="form-header">关联信息</h4>
<el-row>
<el-col :span="12">
<el-form-item>
<template #label>
关联子表的表名
<el-tooltip content="关联子表的表名, 如sys_user" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option v-for="(t, index) in table" :key="index" :label="t.tableName + '' + t.tableComment" :value="t.tableName"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
子表关联的外键名
<el-tooltip content="子表关联的外键名, 如user_id" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.subTableFkName" placeholder="请选择">
<el-option
v-for="(column, index) in subColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</template>
</el-form>
</template>
<script setup lang="ts">
import { listMenu } from '@/api/system/menu';
import { propTypes } from '@/utils/propTypes';
interface MenuOptionsType {
menuId: number | string;
menuName: string;
children?: MenuOptionsType[];
}
const { proxy } = getCurrentInstance();
const subColumns = ref<any>([]);
const menuOptions = ref<Array<MenuOptionsType>>([]);
const props = defineProps({
info: propTypes.any.isRequired,
tables: propTypes.any.isRequired
});
const infoForm = computed(() => props.info);
const table = computed(() => props.tables);
// 表单校验
const rules = ref({
tplCategory: [{ required: true, message: '请选择生成模板', trigger: 'blur' }],
packageName: [{ required: true, message: '请输入生成包路径', trigger: 'blur' }],
moduleName: [{ required: true, message: '请输入生成模块名', trigger: 'blur' }],
businessName: [{ required: true, message: '请输入生成业务名', trigger: 'blur' }],
functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }]
});
const subSelectChange = () => {
infoForm.value.subTableFkName = '';
};
const tplSelectChange = (value: string) => {
if (value !== 'sub') {
infoForm.value.subTableName = '';
infoForm.value.subTableFkName = '';
}
};
const setSubTableColumns = (value: string) => {
table.value.forEach((item: any) => {
const name = item.tableName;
if (value === name) {
subColumns.value = item.columns;
return;
}
});
};
/** 查询菜单下拉树结构 */
const getMenuTreeselect = async () => {
const res = await listMenu();
const data = proxy?.handleTree<MenuOptionsType>(res.data, 'menuId');
if (data) {
menuOptions.value = data;
}
};
watch(
() => props.info.subTableName,
(val) => {
setSubTableColumns(val);
}
);
onMounted(() => {
getMenuTreeselect();
});
</script>

View File

@ -0,0 +1,122 @@
<template>
<!-- 导入表 -->
<el-dialog v-model="visible" title="导入表" width="1100px" top="5vh" append-to-body>
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="数据源" prop="dataName">
<el-select v-model="queryParams.dataName" filterable placeholder="请选择/输入数据源名称">
<el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="表名称" prop="tableName">
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table ref="tableRef" border :data="dbTableList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="createTime" label="创建时间"></el-table-column>
<el-table-column prop="updateTime" label="更新时间"></el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-row>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleImportTable"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { listDbTable, importTable, getDataNames } from '@/api/tool/gen';
import { DbTableQuery, DbTableVO } from '@/api/tool/gen/types';
const total = ref(0);
const visible = ref(false);
const tables = ref<Array<string>>([]);
const dbTableList = ref<Array<DbTableVO>>([]);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const queryParams = reactive<DbTableQuery>({
pageNum: 1,
pageSize: 10,
dataName: '',
tableName: '',
tableComment: ''
});
const dataNameList = ref<Array<string>>([]);
const emit = defineEmits(['ok']);
/** 查询参数列表 */
const show = (dataName: string) => {
getDataNames().then((res) => {
if (res.code == 200) {
dataNameList.value = res.data;
if (dataName) {
queryParams.dataName = dataName;
} else {
queryParams.dataName = dataNameList.value[0];
}
getList();
visible.value = true;
}
});
};
/** 单击选择行 */
const clickRow = (row: DbTableVO) => {
// ele bug
tableRef.value?.toggleRowSelection(row, false);
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DbTableVO[]) => {
tables.value = selection.map((item) => item.tableName);
};
/** 查询表数据 */
const getList = async () => {
const res = await listDbTable(queryParams);
dbTableList.value = res.rows;
total.value = res.total;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 导入按钮操作 */
const handleImportTable = async () => {
const tableNames = tables.value.join(',');
if (tableNames == '') {
proxy?.$modal.msgError('请选择要导入的表');
return;
}
const res = await importTable({ tables: tableNames, dataName: queryParams.dataName });
proxy?.$modal.msgSuccess(res.msg);
if (res.code === 200) {
visible.value = false;
emit('ok');
}
};
defineExpose({
show
});
</script>

View File

@ -0,0 +1,250 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="数据源" prop="dataName">
<el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称">
<el-option key="" label="全部" value="" />
<el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="表名称" prop="tableName">
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:code']" type="primary" plain icon="Download" @click="handleGenTable()">生成</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:import']" type="info" plain icon="Upload" @click="openImportTable">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="tableList" @selection-change="handleSelectionChange">
<el-table-column type="selection" align="center" width="55"></el-table-column>
<el-table-column label="序号" type="index" width="50" align="center">
<template #default="scope">
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="数据源" align="center" prop="dataName" :show-overflow-tooltip="true" />
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" />
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" />
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
<el-table-column label="更新时间" align="center" prop="updateTime" width="160" />
<el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="预览" placement="top">
<el-button v-hasPermi="['tool:gen:preview']" link type="primary" icon="View" @click="handlePreview(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Edit" @click="handleEditTable(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['tool:gen:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="同步" placement="top">
<el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="生成代码" placement="top">
<el-button v-hasPermi="['tool:gen:code']" link type="primary" icon="Download" @click="handleGenTable(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 预览界面 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="80%" top="5vh" append-to-body class="scrollbar">
<el-tabs v-model="preview.activeName">
<el-tab-pane
v-for="(value, key) in preview.data"
:key="value"
:label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
:name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
>
<el-link v-copyText="value" v-copyText:callback="copyTextSuccess" :underline="false" icon="DocumentCopy" style="float: right">
&nbsp;复制
</el-link>
<pre>{{ value }}</pre>
</el-tab-pane>
</el-tabs>
</el-dialog>
<import-table ref="importRef" @ok="handleQuery" />
</div>
</template>
<script setup name="Gen" lang="ts">
import { delTable, genCode, getDataNames, listTable, previewTable, synchDb } from '@/api/tool/gen';
import { TableQuery, TableVO } from '@/api/tool/gen/types';
import router from '@/router';
import ImportTable from './importTable.vue';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tableList = ref<TableVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const uniqueId = ref('');
const dataNameList = ref<Array<string>>([]);
const queryFormRef = ref<ElFormInstance>();
const importRef = ref<InstanceType<typeof ImportTable>>();
const queryParams = ref<TableQuery>({
pageNum: 1,
pageSize: 10,
tableName: '',
tableComment: '',
dataName: ''
});
const preview = ref<{
data: Record<string, string>;
activeName: string;
}>({
data: {},
activeName: 'domain.java'
});
const dialog = reactive<DialogOption>({
visible: false,
title: '代码预览'
});
/** 查询多数据源名称 */
const getDataNameList = async () => {
const res = await getDataNames();
dataNameList.value = res.data;
};
/** 查询表集合 */
const getList = async () => {
loading.value = true;
const res = await listTable(proxy?.addDateRange(queryParams.value, dateRange.value));
tableList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 生成代码操作 */
const handleGenTable = async (row?: TableVO) => {
const tbIds = row?.tableId || ids.value;
if (tbIds == '') {
proxy?.$modal.msgError('请选择要生成的数据');
return;
}
if (row?.genType === '1') {
await genCode(row.tableId);
proxy?.$modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
} else {
proxy?.$download.zip('/tool/gen/batchGenCode?tableIdStr=' + tbIds, 'ruoyi.zip');
}
};
/** 同步数据库操作 */
const handleSynchDb = async (row: TableVO) => {
const tableId = row.tableId;
await proxy?.$modal.confirm('确认要强制同步"' + row.tableName + '"表结构吗?');
await synchDb(tableId);
proxy?.$modal.msgSuccess('同步成功');
};
/** 打开导入表弹窗 */
const openImportTable = () => {
importRef.value?.show(queryParams.value.dataName);
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
};
/** 预览按钮 */
const handlePreview = async (row: TableVO) => {
const res = await previewTable(row.tableId);
preview.value.data = res.data;
dialog.visible = true;
preview.value.activeName = 'domain.java';
};
/** 复制代码成功 */
const copyTextSuccess = () => {
proxy?.$modal.msgSuccess('复制成功');
};
// 多选框选中数据
const handleSelectionChange = (selection: TableVO[]) => {
ids.value = selection.map((item) => item.tableId);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleEditTable = (row?: TableVO) => {
const tableId = row?.tableId || ids.value[0];
router.push({ path: '/tool/gen-edit/index/' + tableId, query: { pageNum: queryParams.value.pageNum } });
};
/** 删除按钮操作 */
const handleDelete = async (row?: TableVO) => {
const tableIds = row?.tableId || ids.value;
await proxy?.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?');
await delTable(tableIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
const time = route.query.t;
if (time != null && time != uniqueId.value) {
uniqueId.value = time as string;
queryParams.value.pageNum = Number(route.query.pageNum);
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
}
getList();
getDataNameList();
});
</script>

View File

@ -0,0 +1,254 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:category:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="categoryTableRef"
v-loading="loading"
:data="categoryList"
row-key="categoryId"
border
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="分类名称" prop="categoryName" width="260" />
<el-table-column label="显示顺序" align="center" prop="orderNum" width="200" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:category:edit']" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['workflow:category:add']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:category:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="上级分类" prop="parentId">
<el-tree-select
v-model="form.parentId"
:data="categoryOptions"
:props="{ value: 'categoryId', label: 'categoryName', children: 'children' } as any"
value-key="categoryId"
placeholder="请选择上级分类"
check-strictly
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="form.categoryName" placeholder="请输入分类名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Category" lang="ts">
import { listCategory, getCategory, delCategory, addCategory, updateCategory } from '@/api/workflow/category';
import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/workflow/category/types';
type CategoryOption = {
categoryId: number;
categoryName: string;
children?: CategoryOption[];
};
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const categoryList = ref<CategoryVO[]>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const buttonLoading = ref(false);
const showSearch = ref(true);
const isExpandAll = ref(true);
const loading = ref(false);
const queryFormRef = ref<ElFormInstance>();
const categoryFormRef = ref<ElFormInstance>();
const categoryTableRef = ref<ElTableInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: CategoryForm = {
categoryId: undefined,
categoryName: '',
parentId: undefined,
orderNum: 0
};
const data = reactive<PageData<CategoryForm, CategoryQuery>>({
form: { ...initFormData },
queryParams: {
categoryName: undefined
},
rules: {
categoryId: [{ required: true, message: '流程分类ID不能为空', trigger: 'blur' }],
parentId: [{ required: true, message: '请选择上级分类', trigger: 'change' }],
categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询流程分类列表 */
const getList = async () => {
loading.value = true;
const res = await listCategory(queryParams.value);
const data = proxy?.handleTree<CategoryVO>(res.data, 'categoryId', 'parentId');
if (data) {
categoryList.value = data;
loading.value = false;
}
};
/** 查询流程分类下拉树结构 */
const getTreeselect = async () => {
const res = await listCategory();
categoryOptions.value = [];
// 处理树形数据
const data = proxy?.handleTree<CategoryOption>(res.data, 'categoryId', 'parentId');
if (data) {
categoryOptions.value = data; // 将处理后的树形数据赋值
}
};
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
};
// 表单重置
const reset = () => {
form.value = { ...initFormData };
categoryFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = (row?: CategoryVO) => {
reset();
getTreeselect();
if (row?.categoryId) {
form.value.parentId = row.categoryId;
} else {
form.value.parentId = undefined;
}
dialog.visible = true;
dialog.title = '添加流程分类';
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(categoryList.value, isExpandAll.value);
};
/** 展开/折叠操作 */
const toggleExpandAll = (data: CategoryVO[], status: boolean) => {
data.forEach((item) => {
categoryTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 修改按钮操作 */
const handleUpdate = async (row: CategoryVO) => {
reset();
await getTreeselect();
if (row != null) {
form.value.parentId = row.parentId;
}
const res = await getCategory(row.categoryId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改流程分类';
};
/** 提交按钮 */
const submitForm = () => {
categoryFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.categoryId) {
await updateCategory(form.value).finally(() => (buttonLoading.value = false));
} else {
await addCategory(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: CategoryVO) => {
await proxy?.$modal.confirm('是否确认删除"' + row.categoryName + '"的分类?');
loading.value = true;
await delCategory(row.categoryId).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,236 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="请假天数" prop="startLeaveDays">
<el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="endLeaveDays"> </el-form-item>
<el-form-item prop="endLeaveDays">
<el-input v-model="queryParams.endLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['workflow:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="leaveList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="主键" align="center" prop="id" />
<el-table-column label="请假类型" align="center">
<template #default="scope">
<el-tag>{{ options.find((e) => e.value === scope.row.leaveType)?.label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startDate">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endDate">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="请假天数" align="center" prop="leaveDays" />
<el-table-column label="请假原因" align="center" prop="remark" />
<el-table-column align="center" label="流程状态" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.status"></dict-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="162">
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
<el-button v-hasPermi="['workflow:leave:edit']" size="small" type="primary" icon="Edit" @click="handleUpdate(scope.row)"
>修改</el-button
>
</el-col>
<el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
<el-button v-hasPermi="['workflow:leave:remove']" size="small" type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</el-col>
<el-col :span="1.5" v-if="scope.row.status === 'waiting'">
<el-button size="small" type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">撤销</el-button>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="Leave" lang="ts">
import { delLeave, listLeave } from '@/api/workflow/leave';
import { cancelProcessApply } from '@/api/workflow/instance';
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const leaveList = ref<LeaveVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const options = [
{
value: '1',
label: '事假'
},
{
value: '2',
label: '调休'
},
{
value: '3',
label: '病假'
},
{
value: '4',
label: '婚假'
}
];
const queryFormRef = ref<ElFormInstance>();
const data = reactive<PageData<LeaveForm, LeaveQuery>>({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
startLeaveDays: undefined,
endLeaveDays: undefined
},
rules: {}
});
const { queryParams } = toRefs(data);
/** 查询请假列表 */
const getList = async () => {
loading.value = true;
const res = await listLeave(queryParams.value);
leaveList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: LeaveVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
query: {
type: 'add'
}
});
};
/** 修改按钮操作 */
const handleUpdate = (row?: LeaveVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
query: {
id: row.id,
type: 'update'
}
});
};
/** 查看按钮操作 */
const handleView = (row?: LeaveVO) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/workflow/leaveEdit/index`,
query: {
id: row.id,
type: 'view'
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: LeaveVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除请假编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delLeave(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'workflow/leave/export',
{
...queryParams.value
},
`leave_${new Date().getTime()}.xlsx`
);
};
/** 撤销按钮操作 */
const handleCancelProcessApply = async (id: string) => {
await proxy?.$modal.confirm('是否确认撤销当前单据?');
loading.value = true;
const data = {
businessId: id,
message: '申请人撤销流程!'
};
await cancelProcessApply(data).finally(() => (loading.value = false));
await getList();
proxy?.$modal.msgSuccess('撤销成功');
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,295 @@
<template>
<div class="p-2">
<el-card shadow="never">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
/>
</el-card>
<el-card shadow="never" style="height: 78vh; overflow-y: auto">
<el-form ref="leaveFormRef" v-loading="loading" :disabled="routeParams.type === 'view'" :model="form" :rules="rules" label-width="80px">
<el-form-item label="请假类型" prop="leaveType">
<el-select v-model="form.leaveType" placeholder="请选择请假类型" style="width: 100%">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="请假时间" required>
<el-date-picker
v-model="leaveTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="To"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
@change="changeLeaveTime()"
/>
</el-form-item>
<el-form-item label="请假天数" prop="leaveDays">
<el-input v-model="form.leaveDays" disabled type="number" placeholder="请输入请假天数" />
</el-form-item>
<el-form-item label="请假原因" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入请假原因" />
</el-form-item>
</el-form>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" :before-close="handleClose" width="500">
<el-select v-model="flowCode" placeholder="Select" style="width: 240px">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="submitFlow()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Leave" lang="ts">
import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { AxiosResponse } from 'axios';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const buttonLoading = ref(false);
const loading = ref(true);
const leaveTime = ref<Array<string>>([]);
//路由参数
const routeParams = ref<Record<string, any>>({});
const options = [
{
value: '1',
label: '事假'
},
{
value: '2',
label: '调休'
},
{
value: '3',
label: '病假'
},
{
value: '4',
label: '婚假'
}
];
const flowCodeOptions = [
{
value: 'leave1',
label: '请假申请-普通'
},
{
value: 'leave2',
label: '请假申请-排他网关'
},
{
value: 'leave3',
label: '请假申请-并行网关'
},
{
value: 'leave4',
label: '请假申请-会签'
},
{
value: 'leave5',
label: '请假申请-并行会签网关'
},
{
value: 'leave6',
label: '请假申请-排他并行会签'
}
];
const flowCode = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
const leaveFormRef = ref<ElFormInstance>();
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
const initFormData: LeaveForm = {
id: undefined,
leaveType: undefined,
startDate: undefined,
endDate: undefined,
leaveDays: undefined,
remark: undefined,
status: undefined
};
const data = reactive<PageData<LeaveForm, LeaveQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
startLeaveDays: undefined,
endLeaveDays: undefined
},
rules: {
id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
leaveTime: [{ required: true, message: '请假时间不能为空', trigger: 'blur' }],
leaveDays: [{ required: true, message: '请假天数不能为空', trigger: 'blur' }]
}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
leaveTime.value = [];
leaveFormRef.value?.resetFields();
};
const changeLeaveTime = () => {
const startDate = new Date(leaveTime.value[0]).getTime();
const endDate = new Date(leaveTime.value[1]).getTime();
const diffInMilliseconds = endDate - startDate;
form.value.leaveDays = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24)) + 1;
};
/** 获取详情 */
const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
const res = await getLeave(routeParams.value.id);
Object.assign(form.value, res.data);
leaveTime.value = [];
leaveTime.value.push(form.value.startDate);
leaveTime.value.push(form.value.endDate);
loading.value = false;
buttonLoading.value = false;
});
};
/** 提交按钮 */
const submitForm = (status: string) => {
if (leaveTime.value.length === 0) {
proxy?.$modal.msgError('请假时间不能为空');
return;
}
try {
leaveFormRef.value?.validate(async (valid: boolean) => {
form.value.startDate = leaveTime.value[0];
form.value.endDate = leaveTime.value[1];
if (valid) {
buttonLoading.value = true;
let res: AxiosResponse<LeaveVO>;
if (form.value.id) {
res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
} else {
res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
}
form.value = res.data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
}
await handleStartWorkFlow(res.data);
}
}
});
} finally {
buttonLoading.value = false;
}
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
//流程变量
taskVariables.value = {
// leave2/6 使用的流程变量
leaveDays: data.leaveDays,
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} finally {
buttonLoading.value = false;
}
};
//审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.value.id);
};
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
//审批
const approvalVerifyOpen = async () => {
submitVerifyRef.value.openDialog(routeParams.value.taskId);
};
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
getInfo();
}
});
});
</script>

View File

@ -0,0 +1,46 @@
<template>
<div ref="container" class="w-full h-[calc(100vh-88px)]">
<iframe ref="iframe" :src="iframeUrl" frameborder="0" height="100%" style="height: 100%; width: inherit"></iframe>
</div>
</template>
<script setup name="WarmFlow" lang="ts">
const { proxy } = getCurrentInstance();
import { onMounted } from 'vue';
import { getToken } from '@/utils/auth';
// definitionId为需要查询的流程定义id
// disabled为是否可编辑, 例如:查看的时候不可编辑,不可保存
const iframeUrl = ref('');
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const iframeLoaded = () => {
// iframe监听组件内设计器保存事件
window.onmessage = (event) => {
switch (event.data.method) {
case 'close':
close();
break;
}
};
};
const open = async (definitionId, disabled) => {
const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&disabled=${disabled}`;
iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
};
/** 关闭按钮 */
function close() {
const obj = { path: '/workflow/processDefinition', query: { activeName: proxy.$route.query.activeName } };
proxy.$tab.closeOpenPage(obj);
}
onMounted(() => {
iframeLoaded();
open(proxy.$route.query.definitionId, proxy.$route.query.disabled);
});
/**
* 对外暴露子组件方法
*/
defineExpose({
open
});
</script>

View File

@ -0,0 +1,524 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
class="mt-2"
node-key="id"
:data="categoryOptions"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
<el-form-item label="流程定义名称" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="已发布" name="0"></el-tab-pane>
<el-tab-pane label="未发布" name="1"></el-tab-pane>
<el-table v-loading="loading" border :data="processDefinitionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" prop="id" label="主键" v-if="false"></el-table-column>
<el-table-column align="center" prop="flowName" label="流程定义名称" :show-overflow-tooltip="true"></el-table-column>
<el-table-column align="center" prop="flowCode" label="标识KEY" :show-overflow-tooltip="true"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类" :show-overflow-tooltip="true"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="80">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
<template #default="scope">
<el-switch
v-model="scope.row.activityStatus"
:active-value="1"
:inactive-value="0"
@change="(status) => handleProcessDefState(scope.row, status)"
/>
</template>
</el-table-column>
<el-table-column align="center" prop="isPublish" label="发布状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.isPublish == 0" type="danger">未发布</el-tag>
<el-tag v-else-if="scope.row.isPublish == 1" type="success">已发布</el-tag>
<el-tag v-else type="danger">失效</el-tag>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" width="170" class-name="small-padding fixed-width">
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
</el-col>
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
>流程设计</el-button
>
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
</el-col>
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
<el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getPageList"
/>
</el-tabs>
</el-card>
</el-col>
</el-row>
<!-- 部署文件 -->
<el-dialog v-if="uploadDialog.visible" v-model="uploadDialog.visible" :title="uploadDialog.title" width="30%">
<div v-loading="uploadDialogLoading">
<div class="mb5">
<el-text class="mx-1" size="large"><span class="text-danger">*</span>请选择部署流程分类</el-text>
<el-tree-select
v-model="selectCategory"
:data="categoryOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
filterable
value-key="id"
:render-after-expand="false"
check-strictly
style="width: 240px"
/>
</div>
<el-upload
class="upload-demo"
drag
multiple
accept="application/json,application/text"
:before-upload="handlerBeforeUpload"
:http-request="handlerImportDefinition"
>
<el-icon class="UploadFilled"><upload-filled /></el-icon>
<div class="el-upload__text"><em>点击上传选择JSON流程文件</em></div>
<div class="el-upload__text">仅支持json格式文件</div>
<div class="el-upload__text">PS:如若部署请部署从本项目模型管理导出的数据</div>
</el-upload>
</div>
</el-dialog>
<!-- 新增/编辑流程定义 -->
<el-dialog v-model="modelDialog.visible" :title="modelDialog.title" width="650px" append-to-body :close-on-click-modal="false">
<template #footer>
<el-form ref="defFormRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="流程类别" prop="category">
<el-tree-select
v-model="form.category"
:data="categoryOptions"
:props="{ value: 'id', label: 'label', children: 'children' } as any"
filterable
value-key="id"
:render-after-expand="false"
check-strictly
style="width: 100%"
/>
</el-form-item>
<el-form-item label="流程编码" prop="flowCode">
<el-input v-model="form.flowCode" placeholder="请输入流程编码" maxlength="40" show-word-limit />
</el-form-item>
<el-form-item label="流程名称" prop="flowName">
<el-input v-model="form.flowName" placeholder="请输入流程名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="表单路径" prop="formPath">
<el-input v-model="form.formPath" placeholder="请输入表单路径" maxlength="100" show-word-limit />
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button @click="modelDialog.visible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">保存</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="processDefinition" lang="ts">
import { listDefinition, deleteDefinition, active, importDef, unPublishList, publish, add, edit, getInfo, copy } from '@/api/workflow/definition';
import { categoryTree } from '@/api/workflow/category';
import { CategoryTreeVO } from '@/api/workflow/category/types';
import { FlowDefinitionQuery, FlowDefinitionVo, FlowDefinitionForm } from '@/api/workflow/definition/types';
import { UploadRequestOptions, TabsPaneContext } from 'element-plus';
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
const loading = ref(true);
const ids = ref<Array<any>>([]);
const flowCodeList = ref<Array<any>>([]);
const single = ref(true);
const multiple = ref(true);
const showSearch = ref(true);
const total = ref(0);
const uploadDialogLoading = ref(false);
const processDefinitionList = ref<FlowDefinitionVo[]>([]);
const categoryOptions = ref<CategoryTreeVO[]>([]);
const categoryName = ref('');
/** 部署文件分类选择 */
const selectCategory = ref();
const defFormRef = ref<ElFormInstance>();
const activeName = ref('0');
const uploadDialog = reactive<DialogOption>({
visible: false,
title: '部署流程文件'
});
const processDefinitionDialog = reactive<DialogOption>({
visible: false,
title: '历史版本'
});
const modelDialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 查询参数
const queryParams = ref<FlowDefinitionQuery>({
pageNum: 1,
pageSize: 10,
flowName: undefined,
flowCode: undefined,
category: undefined
});
const rules = {
category: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
flowName: [{ required: true, message: '流程定义名称不能为空', trigger: 'blur' }],
flowCode: [{ required: true, message: '流程定义编码不能为空', trigger: 'blur' }]
};
const initFormData: FlowDefinitionForm = {
id: '',
flowName: '',
flowCode: '',
category: '',
formPath: ''
};
//流程定义参数
const form = ref<FlowDefinitionForm>({
id: '',
flowName: '',
flowCode: '',
category: '',
formPath: ''
});
onMounted(() => {
getPageList();
getTreeselect();
});
/** 节点单击事件 */
const handleNodeClick = (data: CategoryTreeVO) => {
queryParams.value.category = data.id;
if (data.id === '0') {
queryParams.value.category = '';
}
handleQuery();
};
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.categoryName.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
categoryTreeRef.value.filter(categoryName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询流程分类下拉树结构 */
const getTreeselect = async () => {
const res = await categoryTree();
categoryOptions.value = res.data;
};
const handleClick = (tab: TabsPaneContext, event: Event) => {
// v-model处理有延迟 需要手动处理
activeName.value = tab.index;
handleQuery();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
if (activeName.value === '0') {
getList();
} else {
getUnPublishList();
}
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.category = '';
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.id);
flowCodeList.value = selection.map((item: any) => item.flowCode);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
//分页
const getPageList = async () => {
const query = proxy.$route.query;
if (query.activeName) {
activeName.value = query.activeName;
}
if (activeName.value === '0') {
getList();
} else {
getUnPublishList();
}
};
//分页
const getList = async () => {
loading.value = true;
const resp = await listDefinition(queryParams.value);
processDefinitionList.value = resp.rows;
total.value = resp.total;
loading.value = false;
};
//查询未发布的流程定义列表
const getUnPublishList = async () => {
loading.value = true;
const resp = await unPublishList(queryParams.value);
processDefinitionList.value = resp.rows;
total.value = resp.total;
loading.value = false;
};
/** 删除按钮操作 */
const handleDelete = async (row?: FlowDefinitionVo) => {
const id = row?.id || ids.value;
const defList = processDefinitionList.value.filter((x) => id.indexOf(x.id) != -1).map((x) => x.flowCode);
await proxy?.$modal.confirm('是否确认删除流程定义编码为【' + defList + '】的数据项?');
loading.value = true;
await deleteDefinition(id).finally(() => (loading.value = false));
await handleQuery();
proxy?.$modal.msgSuccess('删除成功');
};
/** 发布流程定义 */
const handlePublish = async (row?: FlowDefinitionVo) => {
await proxy?.$modal.confirm(
'是否确认发布流程定义编码为【' + row.flowCode + '】版本为【' + row.version + '】的数据项?,发布后会将已发布流程定义改为失效!'
);
loading.value = true;
await publish(row.id).finally(() => (loading.value = false));
processDefinitionDialog.visible = false;
activeName.value = '0';
await handleQuery();
proxy?.$modal.msgSuccess('发布成功');
};
/** 挂起/激活 */
const handleProcessDefState = async (row: FlowDefinitionVo, status: number | string | boolean) => {
let msg: string;
if (status === 0) {
msg = `暂停后,此流程下的所有任务都不允许往后流转,您确定挂起【${row.flowName || row.flowCode}】吗?`;
} else {
msg = `启动后,此流程下的所有任务都允许往后流转,您确定激活【${row.flowName || row.flowCode}】吗?`;
}
try {
loading.value = true;
await proxy?.$modal.confirm(msg);
await active(row.id, !!status);
await handleQuery();
proxy?.$modal.msgSuccess('操作成功');
} catch (error) {
row.activityStatus = status === 0 ? 1 : 0;
console.error(error);
} finally {
loading.value = false;
}
};
//上传文件前的钩子
const handlerBeforeUpload = () => {
if (selectCategory.value === 'ALL') {
proxy?.$modal.msgError('顶级节点不可作为分类!');
return false;
}
if (!selectCategory.value) {
proxy?.$modal.msgError('请选择左侧要上传的分类!');
return false;
}
};
//部署文件
const handlerImportDefinition = (data: UploadRequestOptions): XMLHttpRequest => {
const formData = new FormData();
uploadDialogLoading.value = true;
formData.append('file', data.file);
formData.append('category', selectCategory.value);
importDef(formData)
.then(() => {
uploadDialog.visible = false;
proxy?.$modal.msgSuccess('部署成功');
activeName.value = '1';
handleQuery();
})
.finally(() => {
uploadDialogLoading.value = false;
});
return;
};
/**
* 设计流程
* @param row
*/
const design = async (row: FlowDefinitionVo) => {
proxy.$router.push({
path: `/workflow/design/index`,
query: {
definitionId: row.id,
disabled: false,
activeName: activeName.value
}
});
};
/**
* 查看流程
* @param row
*/
const designView = async (row: FlowDefinitionVo) => {
proxy.$router.push({
path: `/workflow/design/index`,
query: {
definitionId: row.id,
disabled: true,
activeName: activeName.value
}
});
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
defFormRef.value?.resetFields();
};
/**
* 新增
*/
const handleAdd = async () => {
reset();
if (queryParams.value.category != '') {
form.value.category = queryParams.value.category;
}
modelDialog.visible = true;
modelDialog.title = '新增流程';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: FlowDefinitionVo) => {
reset();
const id = row?.id || ids.value[0];
const res = await getInfo(id);
Object.assign(form.value, res.data);
modelDialog.visible = true;
modelDialog.title = '修改流程';
};
const handleSubmit = async () => {
defFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true;
if (form.value.id) {
await edit(form.value).finally(() => (loading.value = false));
} else {
await add(form.value).finally(() => (loading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
modelDialog.visible = false;
handleQuery();
}
});
};
//复制
const handleCopyDef = async (row: FlowDefinitionVo) => {
ElMessageBox.confirm(`是否确认复制【${row.flowCode}】版本为【${row.version}】的流程定义!`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
} as ElMessageBoxOptions).then(() => {
loading.value = true;
copy(row.id)
.then((resp) => {
if (resp.code === 200) {
proxy?.$modal.msgSuccess('操作成功');
activeName.value = '1';
handleQuery();
}
})
.finally(() => (loading.value = false));
});
};
/** 导出按钮操作 */
const handleExportDef = () => {
proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`);
};
</script>

View File

@ -0,0 +1,421 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
class="mt-2"
node-key="id"
:data="categoryOptions"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<!-- <div class="mb-[10px]">
<el-card shadow="hover" class="text-center">
<el-radio-group v-model="tab" @change="changeTab(tab)">
<el-radio-button value="running">运行中</el-radio-button>
<el-radio-button value="finish">已完成</el-radio-button>
</el-radio-group>
</el-card>
</div>-->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
</el-badge>
</el-form-item>
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义名称" label-width="100" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" label-width="100" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="running" label="运行中"></el-tab-pane>
<el-tab-pane name="finish" label="已完成"></el-tab-pane>
<el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" label="流程定义名称">
<template #default="scope">
<span>{{ scope.row.flowName }}v{{ scope.row.version }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
<el-tag v-else type="danger">挂起</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="启动时间" width="160"></el-table-column>
<el-table-column v-if="tab === 'finish'" align="center" prop="updateTime" label="结束时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" :width="165">
<template #default="scope">
<el-row v-if="tab === 'running'" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-popover :ref="`popoverRef${scope.$index}`" trigger="click" placement="left" :width="300">
<el-input v-model="deleteReason" resize="none" :rows="3" type="textarea" placeholder="请输入作废原因" />
<div style="text-align: right; margin: 5px 0px 0px 0px">
<el-button size="small" text @click="cancelPopover(scope.$index)">取消</el-button>
<el-button size="small" type="primary" @click="handleInvalid(scope.row)">确认</el-button>
</div>
<template #reference>
<el-button type="danger" size="small" icon="CircleClose">作废</el-button>
</template>
</el-popover>
</el-col>
<el-col :span="1.5">
<el-button type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handleQuery"
/>
</el-tabs>
</el-card>
</el-col>
</el-row>
<el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%">
<el-table v-loading="loading" :data="processDefinitionHistoryList">
<el-table-column fixed align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column fixed align="center" prop="name" label="流程定义名称"></el-table-column>
<el-table-column fixed align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="key" label="标识Key"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="suspensionState" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
<el-tag v-else type="danger">挂起</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="deploymentTime" label="部署时间" :show-overflow-tooltip="true"></el-table-column>
</el-table>
</el-dialog>
<!-- 流程变量开始 -->
<el-dialog v-model="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
<el-card v-loading="variableLoading" class="box-card">
<template #header>
<div class="clearfix">
<span
>流程定义名称<el-tag>{{ processDefinitionName }}</el-tag></span
>
</div>
</template>
<div class="max-h-500px overflow-y-auto">
<VueJsonPretty :data="formatToJsonObject(variables)" />
</div>
</el-card>
</el-dialog>
<!-- 流程变量结束 -->
<!-- 申请人 -->
<UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
</div>
</template>
<script setup lang="ts">
import { pageByRunning, pageByFinish, deleteByInstanceIds, instanceVariable, invalid } from '@/api/workflow/instance';
import { categoryTree } from '@/api/workflow/category';
import { CategoryTreeVO } from '@/api/workflow/category/types';
import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
import UserSelect from '@/components/UserSelect/index.vue';
//审批记录组件
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
// 遮罩层
const loading = ref(true);
// 选中数组
const ids = ref<Array<any>>([]);
// 选中实例id数组
const instanceIds = ref<Array<number | string>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 流程变量是否显示
const variableVisible = ref(false);
const variableLoading = ref(true);
const variables = ref<string>('');
//流程定义名称
const processDefinitionName = ref();
// 模型定义表格数据
const processInstanceList = ref<FlowInstanceVO[]>([]);
const processDefinitionHistoryList = ref<Array<any>>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const categoryName = ref('');
const processDefinitionDialog = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
type CategoryOption = {
id: string;
categoryName: string;
children?: CategoryOption[];
};
const tab = ref('running');
// 作废原因
const deleteReason = ref('');
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// 查询参数
const queryParams = ref<FlowInstanceQuery>({
pageNum: 1,
pageSize: 10,
nodeName: undefined,
flowName: undefined,
flowCode: undefined,
createByIds: [],
category: undefined
});
/** 节点单击事件 */
const handleNodeClick = (data: CategoryTreeVO) => {
queryParams.value.category = data.id;
if (data.id === '0') {
queryParams.value.category = '';
}
handleQuery();
};
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.categoryName.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
categoryTreeRef.value.filter(categoryName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询流程分类下拉树结构 */
const getTreeselect = async () => {
const res = await categoryTree();
categoryOptions.value = res.data;
};
/** 搜索按钮操作 */
const handleQuery = () => {
if ('running' === tab.value) {
getProcessInstanceRunningList();
} else {
getProcessInstanceFinishList();
}
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.category = '';
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
queryParams.value.createByIds = [];
userSelectCount.value = 0;
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: FlowInstanceVO[]) => {
ids.value = selection.map((item: any) => item.id);
instanceIds.value = selection.map((item: FlowInstanceVO) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
//分页
const getProcessInstanceRunningList = () => {
loading.value = true;
pageByRunning(queryParams.value).then((resp) => {
processInstanceList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
//分页
const getProcessInstanceFinishList = () => {
loading.value = true;
pageByFinish(queryParams.value).then((resp) => {
processInstanceList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
/** 删除按钮操作 */
const handleDelete = async (row: FlowInstanceVO) => {
const instanceIdList = row.id || instanceIds.value;
await proxy?.$modal.confirm('是否确认删除?');
loading.value = true;
if ('running' === tab.value) {
await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
getProcessInstanceRunningList();
} else {
await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
getProcessInstanceFinishList();
}
proxy?.$modal.msgSuccess('删除成功');
};
const changeTab = async (data: string) => {
processInstanceList.value = [];
queryParams.value.pageNum = 1;
if ('running' === data.paneName) {
getProcessInstanceRunningList();
} else {
getProcessInstanceFinishList();
}
};
/** 作废按钮操作 */
const handleInvalid = async (row: FlowInstanceVO) => {
await proxy?.$modal.confirm('是否确认作废?');
loading.value = true;
if ('running' === tab.value) {
const param = {
id: row.id,
comment: deleteReason.value
};
await invalid(param).finally(() => (loading.value = false));
getProcessInstanceRunningList();
proxy?.$modal.msgSuccess('操作成功');
}
};
const cancelPopover = async (index: any) => {
(proxy?.$refs[`popoverRef${index}`] as any).hide(); //关闭弹窗
};
/** 查看按钮操作 */
const handleView = (row) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: 'view',
formCustom: row.formCustom,
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
//查询流程变量
const handleInstanceVariable = async (row: FlowInstanceVO) => {
variableLoading.value = true;
variableVisible.value = true;
processDefinitionName.value = row.flowName;
const data = await instanceVariable(row.id);
variables.value = data.data.variable;
variableLoading.value = false;
};
/**
* json转为对象
* @param data 原始数据
*/
function formatToJsonObject(data: string) {
try {
return JSON.parse(data);
} catch (error) {
return data;
}
}
//打开申请人选择
const openUserSelect = () => {
userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
userSelectCount.value = 0;
selectUserIds.value = [];
queryParams.value.createByIds = [];
if (data && data.length > 0) {
userSelectCount.value = data.length;
selectUserIds.value = data.map((item) => item.userId);
queryParams.value.createByIds = selectUserIds.value;
}
};
onMounted(() => {
getProcessInstanceRunningList();
getTreeselect();
});
</script>

View File

@ -0,0 +1,257 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
</el-badge>
</el-form-item>
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义名称" label-width="100" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="tab === 'waiting'">
<el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUpdate">修改办理人 </el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="waiting" label="待办任务"> </el-tab-pane>
<el-tab-pane name="finish" label="已办任务"> </el-tab-pane>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" label="办理人">
<template #default="scope">
<template v-if="tab === 'waiting'">
<template v-if="scope.row.assigneeNames">
<el-tag v-for="(name, index) in scope.row.assigneeNames.split(',')" :key="index" type="success">
{{ name }}
</el-tag>
</template>
<template v-else>
<el-tag type="success"> </el-tag>
</template>
</template>
<template v-else>
<el-tag type="success"> {{ scope.row.approveName }}</el-tag>
</template>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column v-if="tab === 'finish'" align="center" label="任务状态" prop="flowTaskStatus" min-width="70">
<template #default="scope">
<dict-tag :options="wf_task_status" :value="scope.row.flowTaskStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" :width="tab === 'finish' ? '88' : '188'">
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="tab === 'waiting' || tab === 'finish'">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</el-col>
<el-col :span="1.5" v-if="tab === 'waiting'">
<el-button type="primary" size="small" icon="Setting" @click="handleMeddle(scope.row)">流程干预 </el-button>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handleQuery"
/>
</el-tabs>
</el-card>
<!-- 选人组件 -->
<UserSelect ref="userSelectRef" :multiple="false" @confirm-call-back="submitCallback"></UserSelect>
<!-- 流程干预组件 -->
<processMeddle ref="processMeddleRef" @submitCallback="getWaitingList"></processMeddle>
<!-- 申请人 -->
<UserSelect ref="applyUserSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
</div>
</template>
<script setup lang="ts">
import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect';
import { TaskQuery } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
import processMeddle from '@/components/Process/processMeddle';
import { UserVO } from '@/api/system/user/types';
import { TabsPaneContext } from 'element-plus';
//选人组件
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
//流程干预组件
const processMeddleRef = ref<InstanceType<typeof processMeddle>>();
//选人组件
const applyUserSelectRef = ref<InstanceType<typeof UserSelect>>();
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
// 遮罩层
const loading = ref(true);
// 选中数组
const ids = ref<Array<any>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const taskList = ref([]);
const title = ref('');
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// 查询参数
const queryParams = ref<TaskQuery>({
pageNum: 1,
pageSize: 10,
nodeName: undefined,
flowName: undefined,
flowCode: undefined,
createByIds: []
});
const tab = ref('waiting');
/** 搜索按钮操作 */
const handleQuery = () => {
if ('waiting' === tab.value) {
getWaitingList();
} else {
getFinishList();
}
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
queryParams.value.createByIds = [];
userSelectCount.value = 0;
selectUserIds.value = [];
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const changeTab = async (data: TabsPaneContext) => {
taskList.value = [];
queryParams.value.pageNum = 1;
if ('waiting' === data.paneName) {
getWaitingList();
} else {
getFinishList();
}
};
//分页
const getWaitingList = () => {
loading.value = true;
pageByAllTaskWait(queryParams.value).then((resp) => {
taskList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
const getFinishList = () => {
loading.value = true;
pageByAllTaskFinish(queryParams.value).then((resp) => {
taskList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
//打开修改选人
const handleUpdate = () => {
userSelectRef.value.open();
};
//修改办理人
const submitCallback = async (data) => {
if (data && data.length > 0) {
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
await updateAssignee(ids.value, data[0].userId);
handleQuery();
proxy?.$modal.msgSuccess('操作成功');
} else {
proxy?.$modal.msgWarning('请选择用户!');
}
};
/** 查看按钮操作 */
const handleView = (row) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: 'view',
formCustom: row.formCustom,
formPath: row.formPath,
instanceId: row.instanceId
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
const handleMeddle = (row) => {
processMeddleRef.value.open(row.id);
};
//打开申请人选择
const openUserSelect = () => {
applyUserSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
userSelectCount.value = 0;
selectUserIds.value = [];
queryParams.value.createByIds = [];
if (data && data.length > 0) {
userSelectCount.value = data.length;
selectUserIds.value = data.map((item) => item.userId);
queryParams.value.createByIds = selectUserIds.value;
}
};
onMounted(() => {
getWaitingList();
});
</script>

View File

@ -0,0 +1,246 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
class="mt-2"
node-key="id"
:data="categoryOptions"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
<el-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column v-if="false" align="center" prop="id" label="id"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"> </el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
<el-tag v-else type="danger">挂起</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="启动时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" width="162">
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="scope.row.flowStatus === 'draft' || scope.row.flowStatus === 'cancel' || scope.row.flowStatus === 'back'">
<el-button type="primary" size="small" icon="Edit" @click="handleOpen(scope.row, 'update')">编辑</el-button>
</el-col>
<el-col :span="1.5" v-if="scope.row.flowStatus === 'draft' || scope.row.flowStatus === 'cancel' || scope.row.flowStatus === 'back'">
<el-button type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" size="small" icon="View" @click="handleOpen(scope.row, 'view')">查看</el-button>
</el-col>
<el-col :span="1.5" v-if="scope.row.flowStatus === 'waiting'">
<el-button type="primary" size="small" icon="Notification" @click="handleCancelProcessApply(scope.row.businessId)"
>撤销</el-button
>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" @submit-callback="getList" />
</div>
</template>
<script setup lang="ts">
import { pageByCurrent, deleteByInstanceIds, cancelProcessApply } from '@/api/workflow/instance';
import { categoryTree } from '@/api/workflow/category';
import { CategoryTreeVO } from '@/api/workflow/category/types';
import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
// 遮罩层
const loading = ref(true);
// 选中数组
const businessIds = ref<Array<number | string>>([]);
const instanceIds = ref<Array<number | string>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const processInstanceList = ref<FlowInstanceVO[]>([]);
const categoryOptions = ref<CategoryTreeVO[]>([]);
const categoryName = ref('');
const tab = ref('running');
// 查询参数
const queryParams = ref<FlowInstanceQuery>({
pageNum: 1,
pageSize: 10,
flowCode: undefined,
category: undefined
});
onMounted(() => {
getList();
getTreeselect();
});
/** 节点单击事件 */
const handleNodeClick = (data: CategoryTreeVO) => {
queryParams.value.category = data.id;
if (data.id === '0') {
queryParams.value.category = '';
}
handleQuery();
};
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.categoryName.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
categoryTreeRef.value.filter(categoryName.value);
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 查询流程分类下拉树结构 */
const getTreeselect = async () => {
const res = await categoryTree();
categoryOptions.value = res.data;
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.category = '';
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: FlowInstanceVO[]) => {
businessIds.value = selection.map((item: any) => item.businessId);
instanceIds.value = selection.map((item: FlowInstanceVO) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
//分页
const getList = () => {
loading.value = true;
pageByCurrent(queryParams.value).then((resp) => {
processInstanceList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
/** 删除按钮操作 */
const handleDelete = async (row: FlowInstanceVO) => {
const instanceIdList = row.id || instanceIds.value;
await proxy?.$modal.confirm('是否确认删除?');
loading.value = true;
if ('running' === tab.value) {
await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
getList();
}
proxy?.$modal.msgSuccess('删除成功');
};
/** 撤销按钮操作 */
const handleCancelProcessApply = async (businessId: string) => {
await proxy?.$modal.confirm('是否确认撤销当前单据?');
loading.value = true;
if ('running' === tab.value) {
const data = {
businessId: businessId,
message: '申请人撤销流程!'
};
await cancelProcessApply(data).finally(() => (loading.value = false));
getList();
}
proxy?.$modal.msgSuccess('撤销成功');
};
//办理
const handleOpen = async (row, type) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: type,
formCustom: row.formCustom,
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
</script>

View File

@ -0,0 +1,136 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义名称" label-width="100" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" label-width="100" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" label="流程状态" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handleQuery"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { pageByTaskCopy } from '@/api/workflow/task';
import { TaskQuery } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
//审批记录组件
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
// 遮罩层
const loading = ref(true);
// 选中数组
const ids = ref<Array<any>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const taskList = ref([]);
// 查询参数
const queryParams = ref<TaskQuery>({
pageNum: 1,
pageSize: 10,
nodeName: undefined,
flowName: undefined,
flowCode: undefined
});
/** 搜索按钮操作 */
const handleQuery = () => {
getTaskCopyList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
//分页
const getTaskCopyList = () => {
loading.value = true;
pageByTaskCopy(queryParams.value).then((resp) => {
taskList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
/** 查看按钮操作 */
const handleView = (row) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: 'view',
formCustom: row.formCustom,
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
onMounted(() => {
getTaskCopyList();
});
</script>

View File

@ -0,0 +1,184 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
</el-badge>
</el-form-item>
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义名称" label-width="100" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" label-width="100" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column align="center" prop="flowName" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" prop="approverName" label="办理人">
<template #default="scope">
<el-tag type="success">
{{ scope.row.approveName || '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" label="任务状态" prop="flowTaskStatus" min-width="70">
<template #default="scope">
<dict-tag :options="wf_task_status" :value="scope.row.flowTaskStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handleQuery"
/>
</el-card>
<!-- 申请人 -->
<UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
</div>
</template>
<script setup lang="ts">
import { pageByTaskFinish } from '@/api/workflow/task';
import { TaskQuery, FlowTaskVO } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
//审批记录组件
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
import UserSelect from '@/components/UserSelect';
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
// 遮罩层
const loading = ref(true);
// 选中数组
const ids = ref<Array<any>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const taskList = ref([]);
// 查询参数
const queryParams = ref<TaskQuery>({
pageNum: 1,
pageSize: 10,
nodeName: undefined,
flowName: undefined,
flowCode: undefined,
createByIds: []
});
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
/** 搜索按钮操作 */
const handleQuery = () => {
getFinishList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
queryParams.value.createByIds = [];
userSelectCount.value = 0;
selectUserIds.value = [];
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const getFinishList = () => {
loading.value = true;
pageByTaskFinish(queryParams.value).then((resp) => {
taskList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
/** 查看按钮操作 */
const handleView = (row: FlowTaskVO) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: 'view',
formCustom: row.formCustom,
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
//打开申请人选择
const openUserSelect = () => {
userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
userSelectCount.value = 0;
selectUserIds.value = [];
queryParams.value.createByIds = [];
if (data && data.length > 0) {
userSelectCount.value = data.length;
selectUserIds.value = data.map((item) => item.userId);
queryParams.value.createByIds = selectUserIds.value;
}
};
onMounted(() => {
getFinishList();
});
</script>

View File

@ -0,0 +1,183 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
</el-badge>
</el-form-item>
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义名称" label-width="100" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" label-width="100" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" label="办理人">
<template #default="scope">
<template v-if="scope.row.assigneeNames">
<el-tag v-for="(name, index) in scope.row.assigneeNames.split(',')" :key="index" type="success">
{{ name }}
</el-tag>
</template>
<template v-else>
<el-tag type="success"> </el-tag>
</template>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" prop="flowStatusName" min-width="70">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" size="small" icon="Edit" @click="handleOpen(scope.row)">办理</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="handleQuery"
/>
</el-card>
<!-- 申请人 -->
<UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
</div>
</template>
<script setup lang="ts">
import { pageByTaskWait } from '@/api/workflow/task';
import { TaskQuery, FlowTaskVO } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
import UserSelect from '@/components/UserSelect';
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
//提交组件
const queryFormRef = ref<ElFormInstance>();
// 遮罩层
const loading = ref(true);
// 选中数组
const ids = ref<Array<any>>([]);
// 非单个禁用
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const taskList = ref([]);
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// 查询参数
const queryParams = ref<TaskQuery>({
pageNum: 1,
pageSize: 10,
nodeName: undefined,
flowName: undefined,
flowCode: undefined,
createByIds: []
});
onMounted(() => {
getWaitingList();
});
/** 搜索按钮操作 */
const handleQuery = () => {
getWaitingList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.pageNum = 1;
queryParams.value.pageSize = 10;
queryParams.value.createByIds = [];
userSelectCount.value = 0;
selectUserIds.value = [];
handleQuery();
};
// 多选框选中数据
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
//分页
const getWaitingList = () => {
loading.value = true;
pageByTaskWait(queryParams.value).then((resp) => {
taskList.value = resp.rows;
total.value = resp.total;
loading.value = false;
});
};
//办理
const handleOpen = async (row: FlowTaskVO) => {
const routerJumpVo = reactive<RouterJumpVo>({
businessId: row.businessId,
taskId: row.id,
type: 'approval',
formCustom: row.formCustom,
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
//打开申请人选择
const openUserSelect = () => {
userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
userSelectCount.value = 0;
selectUserIds.value = [];
queryParams.value.createByIds = [];
if (data && data.length > 0) {
userSelectCount.value = data.length;
selectUserIds.value = data.map((item) => item.userId);
queryParams.value.createByIds = selectUserIds.value;
}
};
</script>