mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-01-03 03:45:28 +08:00
前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题
This commit is contained in:
19
jeecgboot-vue3/src/views/system/address/address.api.ts
Normal file
19
jeecgboot-vue3/src/views/system/address/address.api.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
export enum Api {
|
||||
list = '/sys/user/queryByOrgCodeForAddressList',
|
||||
positionList = '/sys/position/list',
|
||||
queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync',
|
||||
}
|
||||
/**
|
||||
* 获取部门树列表
|
||||
*/
|
||||
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params });
|
||||
/**
|
||||
* 部门用户信息
|
||||
*/
|
||||
export const list = (params?) => defHttp.get({ url: Api.list, params });
|
||||
/**
|
||||
* 职务list
|
||||
*/
|
||||
export const positionList = (params?) => defHttp.get({ url: Api.positionList, params });
|
||||
51
jeecgboot-vue3/src/views/system/address/address.data.ts
Normal file
51
jeecgboot-vue3/src/views/system/address/address.data.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '工号',
|
||||
dataIndex: 'workNo',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'post',
|
||||
width: 150,
|
||||
slots: { customRender: 'post' },
|
||||
},
|
||||
{
|
||||
title: '手机',
|
||||
width: 150,
|
||||
dataIndex: 'telephone',
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
width: 150,
|
||||
dataIndex: 'email',
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '姓名',
|
||||
field: 'realname',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '工号',
|
||||
field: 'workNo',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<a-spin :spinning="loading">
|
||||
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" allowClear />
|
||||
<!--组织机构树-->
|
||||
<template v-if="treeData.length > 0">
|
||||
<a-tree
|
||||
v-if="!treeReloading"
|
||||
showLine
|
||||
:clickRowToExpand="false"
|
||||
:treeData="treeData"
|
||||
:selectedKeys="selectedKeys"
|
||||
:load-data="loadChildrenTreeData"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@select="onSelect"
|
||||
></a-tree>
|
||||
</template>
|
||||
<a-empty v-else description="暂无数据" />
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, unref } from 'vue';
|
||||
import { queryDepartTreeSync } from '../address.api';
|
||||
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select', 'rootTreeData']);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 部门树列表数据
|
||||
const treeData = ref<any[]>([]);
|
||||
// 当前展开的项
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
// 当前选中的项
|
||||
const selectedKeys = ref<any[]>([]);
|
||||
// 树组件重新加载
|
||||
const treeReloading = ref<boolean>(false);
|
||||
// 当前选中的部门
|
||||
const currentDepart = ref<any>(null);
|
||||
// 搜索关键字
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 加载顶级部门信息
|
||||
async function loadRootTreeData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
const result = await queryDepartTreeSync();
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
if (expandedKeys.value.length === 0) {
|
||||
autoExpandParentNode();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
loadRootTreeData();
|
||||
|
||||
// 加载子级部门信息
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
pid: treeNode.dataRef.id,
|
||||
});
|
||||
if (result.length == 0) {
|
||||
treeNode.dataRef.isLeaf = true;
|
||||
} else {
|
||||
treeNode.dataRef.children = result;
|
||||
if (expandedKeys.value.length > 0) {
|
||||
// 判断获取的子级是否有当前展开的项
|
||||
let subKeys: any[] = [];
|
||||
for (let key of expandedKeys.value) {
|
||||
if (result.findIndex((item) => item.id === key) !== -1) {
|
||||
subKeys.push(key);
|
||||
}
|
||||
}
|
||||
if (subKeys.length > 0) {
|
||||
expandedKeys.value = [...expandedKeys.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
treeData.value = [...treeData.value];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 自动展开父节点,只展开一级
|
||||
function autoExpandParentNode() {
|
||||
let item = treeData.value[0];
|
||||
if (item) {
|
||||
if (!item.isLeaf) {
|
||||
expandedKeys.value = [item.key];
|
||||
}
|
||||
reloadTree();
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载树组件,防止无法默认展开数据
|
||||
async function reloadTree() {
|
||||
await nextTick();
|
||||
treeReloading.value = true;
|
||||
await nextTick();
|
||||
treeReloading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中的行
|
||||
*/
|
||||
function setSelectedKey(key: string, data?: object) {
|
||||
selectedKeys.value = [key];
|
||||
if (data) {
|
||||
currentDepart.value = data;
|
||||
emit('select', data);
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索事件
|
||||
async function onSearch(value: string) {
|
||||
if (value) {
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result = await searchByKeywords({ keyWord: value });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
autoExpandParentNode();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
loadRootTreeData();
|
||||
}
|
||||
searchKeyword.value = value;
|
||||
}
|
||||
|
||||
// 树选择事件
|
||||
function onSelect(selKeys, event) {
|
||||
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
|
||||
setSelectedKey(selKeys[0], event.selectedNodes[0]);
|
||||
} else {
|
||||
// 这样可以防止用户取消选择
|
||||
setSelectedKey(selectedKeys.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadRootTreeData,
|
||||
});
|
||||
</script>
|
||||
13
jeecgboot-vue3/src/views/system/address/index.less
Normal file
13
jeecgboot-vue3/src/views/system/address/index.less
Normal file
@ -0,0 +1,13 @@
|
||||
//noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-address-list';
|
||||
|
||||
.@{prefix-cls} {
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
background-color: @component-background;
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
&--box {
|
||||
.ant-tabs-nav {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
89
jeecgboot-vue3/src/views/system/address/index.vue
Normal file
89
jeecgboot-vue3/src/views/system/address/index.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10" style="max-height: 800px">
|
||||
<a-col :xl="6" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree ref="leftTree" @select="onTreeSelect" />
|
||||
</a-col>
|
||||
<a-col :xl="18" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%;" class="address-book">
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable">
|
||||
<template #post="{ text }">
|
||||
{{
|
||||
(text || '')
|
||||
.split(',')
|
||||
.map((t) => (positionInfo[t] ? positionInfo[t] : t))
|
||||
.join(',')
|
||||
}}
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide, ref, unref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DepartLeftTree from './components/DepartLeftTree.vue';
|
||||
import { BasicTable } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, searchFormSchema } from './address.data';
|
||||
import { list, positionList } from './address.api';
|
||||
|
||||
const { prefixCls } = useDesign('address-list');
|
||||
provide('prefixCls', prefixCls);
|
||||
|
||||
// 给子组件定义一个ref变量
|
||||
const leftTree = ref();
|
||||
|
||||
// 当前选中的部门code
|
||||
const orgCode = ref('');
|
||||
const positionInfo = ref({});
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { tableContext } = useListPage({
|
||||
tableProps: {
|
||||
api: list,
|
||||
columns,
|
||||
//update-begin---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错------------
|
||||
rowKey: 'userId',
|
||||
//update-end---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错--------------
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
canResize: false,
|
||||
actionColumn: null,
|
||||
showTableSetting: false,
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
params.orgCode = orgCode.value;
|
||||
},
|
||||
},
|
||||
});
|
||||
//注册table数据
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
orgCode.value = data.orgCode;
|
||||
reload();
|
||||
}
|
||||
|
||||
// 查询职务信息
|
||||
async function queryPositionInfo() {
|
||||
const result = await positionList({ pageSize: 99999 });
|
||||
if (result) {
|
||||
let obj = {};
|
||||
result.records.forEach((position) => {
|
||||
obj[position['id']] = position['name'];
|
||||
});
|
||||
positionInfo.value = obj;
|
||||
}
|
||||
}
|
||||
queryPositionInfo();
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
69
jeecgboot-vue3/src/views/system/appconfig/ThirdApp.api.ts
Normal file
69
jeecgboot-vue3/src/views/system/appconfig/ThirdApp.api.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
//第三方登录配置
|
||||
addThirdAppConfig = '/sys/thirdApp/addThirdAppConfig',
|
||||
editThirdAppConfig = '/sys/thirdApp/editThirdAppConfig',
|
||||
getThirdConfigByTenantId = '/sys/thirdApp/getThirdConfigByTenantId',
|
||||
syncDingTalkDepartUserToLocal = '/sys/thirdApp/sync/dingtalk/departAndUser/toLocal',
|
||||
getThirdUserByWechat = '/sys/thirdApp/getThirdUserByWechat',
|
||||
wechatEnterpriseToLocal = '/sys/thirdApp/sync/wechatEnterprise/departAndUser/toLocal',
|
||||
getThirdUserBindByWechat = '/sys/thirdApp/getThirdUserBindByWechat',
|
||||
deleteThirdAccount = '/sys/thirdApp/deleteThirdAccount',
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方配置保存或者更新
|
||||
*/
|
||||
export const saveOrUpdateThirdConfig = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.editThirdAppConfig : Api.addThirdAppConfig;
|
||||
return defHttp.post({ url: url, params }, { joinParamsToUrl: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取第三方配置
|
||||
* @param params
|
||||
*/
|
||||
export const getThirdConfigByTenantId = (params) => {
|
||||
return defHttp.get({ url: Api.getThirdConfigByTenantId, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 同步钉钉部门用户到本地
|
||||
* @param params
|
||||
*/
|
||||
export const syncDingTalkDepartUserToLocal = () => {
|
||||
return defHttp.get({ url: Api.syncDingTalkDepartUserToLocal, timeout: 60000 }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取企业微信绑定的用户信息
|
||||
* @param params
|
||||
*/
|
||||
export const getThirdUserByWechat = () => {
|
||||
return defHttp.get({ url: Api.getThirdUserByWechat }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 同步企业微信用户部门到本地
|
||||
* @param params
|
||||
*/
|
||||
export const wechatEnterpriseToLocal = (params) => {
|
||||
return defHttp.get({ url: Api.wechatEnterpriseToLocal, params }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取绑定企业微信的用户
|
||||
* @param params
|
||||
*/
|
||||
export const getThirdUserBindByWechat = () => {
|
||||
return defHttp.get({ url: Api.getThirdUserBindByWechat }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据第三方账号表的id解绑账号
|
||||
* @param params
|
||||
*/
|
||||
export const deleteThirdAccount = (params) => {
|
||||
return defHttp.delete({ url: Api.deleteThirdAccount, params }, { isTransformResponse:false, joinParamsToUrl: true });
|
||||
};
|
||||
58
jeecgboot-vue3/src/views/system/appconfig/ThirdApp.data.ts
Normal file
58
jeecgboot-vue3/src/views/system/appconfig/ThirdApp.data.ts
Normal file
@ -0,0 +1,58 @@
|
||||
//第三方app配置表单
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
|
||||
//第三方app表单
|
||||
export const thirdAppFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: 'id',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: 'thirdType',
|
||||
field: 'thirdType',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: 'Agentld',
|
||||
field: 'agentId',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'AppKey',
|
||||
field: 'clientId',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'AppSecret',
|
||||
field: 'clientSecret',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: 'agentAppSecret',
|
||||
field: 'agentAppSecret',
|
||||
component: 'Input',
|
||||
ifShow: false,
|
||||
},{
|
||||
label: '启用',
|
||||
field: 'status',
|
||||
component: 'Switch',
|
||||
componentProps:{
|
||||
checkedChildren:'关闭',
|
||||
checkedValue:1,
|
||||
unCheckedChildren:'开启',
|
||||
unCheckedValue: 0
|
||||
},
|
||||
defaultValue: 1
|
||||
},{
|
||||
label: '租户id',
|
||||
field: 'tenantId',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,316 @@
|
||||
<!--弹窗绑定企业微信页面-->
|
||||
<template>
|
||||
<BasicModal @register="registerModal" :width="800" :title="title" destroyOnClose>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="we-bind">
|
||||
<a-row :span="24" class="we-title-background">
|
||||
<a-col :span="12" class="border-right">
|
||||
<span>组织用户</span>
|
||||
</a-col>
|
||||
<a-col :span="12" class="padding-left">
|
||||
<span>企业微信用户</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :span="24">
|
||||
<template v-for="(item, index) in bindData.jwUserDepartVos">
|
||||
<a-col :span="12" class="border-right padding-left border-bottom">
|
||||
<div class="we-account">
|
||||
<a-avatar v-if="item.avatar" :src="getFileAccessHttpUrl(item.avatar)" :size="28"></a-avatar>
|
||||
<a-avatar v-else :size="28">
|
||||
{{ item.realName.length > 2 ? item.realName.substr(0, 2) : item.realName }}
|
||||
</a-avatar>
|
||||
<a-input style="margin-left: 20px" :value="item.realName" readonly />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" class="padding-left border-bottom">
|
||||
<div class="we-account">
|
||||
<span v-if="item.wechatUserId || izBind" class="we-remove"
|
||||
>{{ item.wechatRealName }} <span style="margin-right: 20px" @click="handleRemoveClick(index, item)">移出</span></span
|
||||
>
|
||||
<a-select
|
||||
v-else
|
||||
v-model:value="item.wechatUserId"
|
||||
:options="userList"
|
||||
:fieldNames="{ label: 'wechatRealName', value: 'wechatUserId' }"
|
||||
style="width: 200px"
|
||||
showSearch
|
||||
@select="(val, option) => handleSelect(val, option, index)"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-spin>
|
||||
<template #footer>
|
||||
<a-button v-if="!izBind" type="primary" @click="handleSubmit">同步</a-button>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { getThirdUserByWechat, wechatEnterpriseToLocal, getThirdUserBindByWechat, deleteThirdAccount } from './ThirdApp.api';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ThirdAppBindWeEnterpriseModal',
|
||||
components: { BasicModal },
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('企业微信绑定');
|
||||
//企业微信的绑定数据
|
||||
const bindData = ref<any>({});
|
||||
const loading = ref<boolean>(false);
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const { createMessage } = useMessage();
|
||||
const userList = ref<any>([]);
|
||||
//同步文本信息展示
|
||||
const syncText = ref<string>('');
|
||||
//是否已绑定数据,展示不同的列表
|
||||
const izBind = ref<boolean>(false);
|
||||
const userStore = useUserStore();
|
||||
//表单赋值
|
||||
const [registerModal, { closeModal }] = useModalInner(async (data) => {
|
||||
loading.value = true;
|
||||
console.log('izBind::', izBind);
|
||||
if (!data.izBind) {
|
||||
await getUnboundData();
|
||||
} else {
|
||||
await getBoundData();
|
||||
}
|
||||
izBind.value = data.izBind;
|
||||
});
|
||||
|
||||
/**
|
||||
* 未绑定的数据
|
||||
*/
|
||||
async function getUnboundData() {
|
||||
await getThirdUserByWechat().then((res) => {
|
||||
if (res.success) {
|
||||
let userLists = res.result.userList;
|
||||
bindData.value = res.result;
|
||||
userList.value = res.result.userList;
|
||||
/* if (userLists && userLists.length > 0) {
|
||||
syncText.value = "";
|
||||
} else {
|
||||
syncText.value = "企业微信用户均已同步";
|
||||
}*/
|
||||
loading.value = false;
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 已绑定的数据
|
||||
*/
|
||||
async function getBoundData() {
|
||||
await getThirdUserBindByWechat().then((res) => {
|
||||
if (res.success) {
|
||||
bindData.value.jwUserDepartVos = res.result;
|
||||
loading.value = false;
|
||||
} else {
|
||||
createMessage.warn(res.message);
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方配置点击事件
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
btnLoading.value = true;
|
||||
let userList = bindData.value.userList;
|
||||
//重新封装数据,只留用户id和企业微信id即可,还需要把没绑定的用户传给后台
|
||||
let params: any = [];
|
||||
//查询用户绑定的企业微信用户
|
||||
for (const item of bindData.value.jwUserDepartVos) {
|
||||
if (item.wechatUserId) {
|
||||
userList = userList.filter((a) => a.wechatUserId != item.wechatUserId);
|
||||
params.push({
|
||||
wechatUserId: item.wechatUserId,
|
||||
wechatDepartId: item.wechatDepartId,
|
||||
wechatRealName: item.wechatRealName,
|
||||
userId: item.userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
let text: string = '';
|
||||
//查询未被绑定的租户
|
||||
if (userList && userList.length > 0) {
|
||||
for (const item of userList) {
|
||||
params.push({ wechatUserId: item.wechatUserId, wechatDepartId: item.wechatDepartId, wechatRealName: item.wechatRealName });
|
||||
}
|
||||
text = '检测到未绑定的企业微信用户 ' + userList.length + ' 位,平台将会为这 ' + userList.length + ' 位用户创建新的账号';
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认同步',
|
||||
content: text,
|
||||
okText: '确认',
|
||||
onOk: () => {
|
||||
let json = JSON.stringify(params);
|
||||
console.log('json::', json);
|
||||
wechatEnterpriseToLocal({ jwUserDepartJson: json })
|
||||
.then((res) => {
|
||||
let options = {};
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
options = {
|
||||
width: 600,
|
||||
title: res.message,
|
||||
content: () => {
|
||||
let nodes;
|
||||
let successInfo = [`成功信息如下:`, renderTextarea(h, res.result.successInfo.map((v, i) => `${i + 1}. ${v}`).join('\n'))];
|
||||
if (res.success) {
|
||||
nodes = [...successInfo, h('br'), `无失败信息!`];
|
||||
} else {
|
||||
nodes = [
|
||||
`失败信息如下:`,
|
||||
renderTextarea(h, res.result.failInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')),
|
||||
h('br'),
|
||||
...successInfo,
|
||||
];
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
};
|
||||
}
|
||||
closeModal();
|
||||
emit('success', options, res);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
btnLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉框选择事件
|
||||
*/
|
||||
function handleSelect(val, option, index) {
|
||||
bindData.value.jwUserDepartVos[index].wechatUserId = option.wechatUserId;
|
||||
bindData.value.jwUserDepartVos[index].wechatRealName = option.wechatRealName;
|
||||
bindData.value.jwUserDepartVos[index].wechatDepartId = option.wechatDepartId;
|
||||
userList.value = userList.value.filter((item) => item.wechatUserId != option.wechatUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移出事件
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
function handleRemoveClick(index, item) {
|
||||
if (!izBind.value) {
|
||||
userList.value.push({
|
||||
wechatUserId: item.wechatUserId,
|
||||
wechatRealName: item.wechatRealName,
|
||||
wechatDepartId: item.wechatDepartId,
|
||||
});
|
||||
bindData.value.jwUserDepartVos[index].wechatUserId = '';
|
||||
bindData.value.jwUserDepartVos[index].wechatRealName = '';
|
||||
bindData.value.jwUserDepartVos[index].wechatDepartId = '';
|
||||
} else {
|
||||
Modal.confirm({
|
||||
title: '确认取消绑定吗',
|
||||
okText: '确认',
|
||||
onOk: async () => {
|
||||
await deleteThirdAccount({ id: item.thirdId, sysUserId: userStore.getUserInfo.id }).then((res) => {
|
||||
if (res.success) {
|
||||
createMessage.success('取消绑定成功!');
|
||||
getBoundData();
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderTextarea(h, value) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
id: 'box',
|
||||
style: {
|
||||
minHeight: '100px',
|
||||
border: '1px solid #d9d9d9',
|
||||
fontSize: '14px',
|
||||
maxHeight: '250px',
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'auto',
|
||||
padding: '10px',
|
||||
},
|
||||
},
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
registerModal,
|
||||
handleSubmit,
|
||||
bindData,
|
||||
getFileAccessHttpUrl,
|
||||
loading,
|
||||
userList,
|
||||
handleSelect,
|
||||
handleRemoveClick,
|
||||
btnLoading,
|
||||
izBind,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.we-bind {
|
||||
overflow-y: auto;
|
||||
border: 1px @border-color-base solid;
|
||||
border-bottom: none;
|
||||
.we-title-background {
|
||||
background: @component-background;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.we-account {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input) {
|
||||
border: none;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.we-remove {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.border-right {
|
||||
border-right: 1px @border-color-base solid;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px @border-color-base solid;
|
||||
}
|
||||
.padding-left {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
140
jeecgboot-vue3/src/views/system/appconfig/ThirdAppConfigList.vue
Normal file
140
jeecgboot-vue3/src/views/system/appconfig/ThirdAppConfigList.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="ding-ding-container" :class="[`${prefixCls}`]">
|
||||
<div class="ding-header">
|
||||
<ul class="ding-menu-tab">
|
||||
<li :class="activeKey === 'ding' ? 'active' : ''" @click="dingLiClick('ding')"><a>钉钉集成</a></li>
|
||||
<li :class="activeKey === 'wechat' ? 'active' : ''" @click="dingLiClick('wechat')"><a>企业微信集成</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-show="activeKey === 'ding'" class="base-collapse">
|
||||
<ThirdAppDingTalkConfigForm />
|
||||
</div>
|
||||
<div v-show="activeKey === 'wechat'" class="base-collapse">
|
||||
<ThirdAppWeEnterpriseConfigForm />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import ThirdAppDingTalkConfigForm from './ThirdAppDingTalkConfigForm.vue';
|
||||
import ThirdAppWeEnterpriseConfigForm from './ThirdAppWeEnterpriseConfigForm.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ThirdAppConfigList',
|
||||
components: {
|
||||
ThirdAppDingTalkConfigForm,
|
||||
ThirdAppWeEnterpriseConfigForm,
|
||||
},
|
||||
setup() {
|
||||
const { prefixCls } = useDesign('j-dd-container');
|
||||
|
||||
//选中的key
|
||||
const activeKey = ref<string>('ding');
|
||||
|
||||
/**
|
||||
* tab点击事件
|
||||
* @param key
|
||||
*/
|
||||
function dingLiClick(key) {
|
||||
activeKey.value = key;
|
||||
}
|
||||
|
||||
return {
|
||||
activeKey,
|
||||
dingLiClick,
|
||||
prefixCls,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ding-ding-container {
|
||||
border-radius: 4px;
|
||||
height: calc(100% - 80px);
|
||||
margin: 16px;
|
||||
}
|
||||
.ding-header {
|
||||
align-items: center;
|
||||
/*begin 兼容暗夜模式*/
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
/*end 兼容暗夜模式*/
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.ding-menu-tab {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
border-bottom: 2px solid transparent;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-right: 38px;
|
||||
|
||||
a {
|
||||
/*begin 兼容暗夜模式*/
|
||||
color: @text-color !important;
|
||||
/*end 兼容暗夜模式*/
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
.active {
|
||||
border-bottom-color: #2196f3 !important;
|
||||
|
||||
a {
|
||||
color: #333 !important;
|
||||
}
|
||||
}
|
||||
.empty-image{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 50px);
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
/* update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效*/
|
||||
@prefix-cls: ~'@{namespace}-j-dd-container';
|
||||
/*begin 兼容暗夜模式*/
|
||||
.@{prefix-cls} {
|
||||
background: @component-background;
|
||||
|
||||
.ding-header {
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
}
|
||||
|
||||
.ding-menu-tab {
|
||||
li {
|
||||
a {
|
||||
color: @text-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-borderless {
|
||||
background-color: @component-background;
|
||||
}
|
||||
|
||||
.ant-collapse{
|
||||
background-color: @component-background;
|
||||
}
|
||||
}
|
||||
/*end 兼容暗夜模式*/
|
||||
/* update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效*/
|
||||
</style>
|
||||
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<BasicModal @register="registerModal" :width="800" :title="title" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useForm, BasicForm } from '/@/components/Form';
|
||||
import { thirdAppFormSchema } from './ThirdApp.data';
|
||||
import { getThirdConfigByTenantId, saveOrUpdateThirdConfig } from './ThirdApp.api';
|
||||
export default defineComponent({
|
||||
name: 'ThirdAppConfigModal',
|
||||
components: { BasicModal, BasicForm },
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('钉钉配置');
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: thirdAppFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
labelCol: { span: 24 },
|
||||
wrapperCol: { span: 24 },
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
setModalProps({ confirmLoading: true });
|
||||
if (data.thirdType == 'dingtalk') {
|
||||
title.value = '钉钉配置';
|
||||
} else {
|
||||
title.value = '企业微信配置';
|
||||
}
|
||||
//重置表单
|
||||
await resetFields();
|
||||
let values = await getThirdConfigByTenantId({ tenantId: data.tenantId, thirdType: data.thirdType });
|
||||
setModalProps({ confirmLoading: false });
|
||||
//表单赋值
|
||||
if (values) {
|
||||
await setFieldsValue(values);
|
||||
} else {
|
||||
await setFieldsValue(data);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 第三方配置点击事件
|
||||
*/
|
||||
async function handleSubmit() {
|
||||
let values = await validate();
|
||||
let isUpdate = false;
|
||||
if (values.id) {
|
||||
isUpdate = true;
|
||||
}
|
||||
await saveOrUpdateThirdConfig(values, isUpdate);
|
||||
emit('success');
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
registerForm,
|
||||
registerModal,
|
||||
handleSubmit,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div class="base-collapse">
|
||||
<div class="header"> 钉钉集成 </div>
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="1">
|
||||
<template #header>
|
||||
<div style="font-size: 16px"> 1.获取对接信息</div>
|
||||
</template>
|
||||
<div class="base-desc">从钉钉开放平台获取对接信息,即可开始集成以及同步通讯录</div>
|
||||
<div style="margin-top: 5px">
|
||||
<a href='https://help.qiaoqiaoyun.com/expand/dingding.html' target='_blank'>如何获取对接信息?</a>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="sync-padding">
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="2">
|
||||
<template #header>
|
||||
<div style="width: 100%; justify-content: space-between; display: flex">
|
||||
<div style="font-size: 16px"> 2.对接信息录入</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="base-desc">完成步骤1后,填入Agentld、 AppKey、AppSecret后 可对接应用与同步通讯录</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">Agentld</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.agentId" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">AppKey</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.clientId" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">AppSecret</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.clientSecret" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px; width: 100%; text-align: right">
|
||||
<a-button @click="dingEditClick">编辑</a-button>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="sync-padding">
|
||||
<div style="font-size: 16px; width: 100%"> 3.数据同步</div>
|
||||
<div style="margin-top: 20px" class="base-desc">
|
||||
从钉钉同步到本地
|
||||
<ul style='list-style-type: disc;margin-left: 20px;'>
|
||||
<li>同步部门到本地</li>
|
||||
<li>
|
||||
同步部门下的用户到本地
|
||||
<a-tooltip title='同步用户与部门文档'>
|
||||
<a-icon @click='handleIconClick' type="question-circle" class="sync-text"/>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<div style="float: right">
|
||||
<a-button :loading="btnLoading" @click="syncDingTalk">{{ !btnLoading ? '同步' : '同步中' }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ThirdAppConfigModal @register="registerAppConfigModal" @success="handleSuccess" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, inject, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { getThirdConfigByTenantId, syncDingTalkDepartUserToLocal } from './ThirdApp.api';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import ThirdAppConfigModal from './ThirdAppConfigModal.vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { getTenantId } from '/@/utils/auth';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OrganDingConfigForm',
|
||||
components: {
|
||||
ThirdAppConfigModal,
|
||||
},
|
||||
setup() {
|
||||
const { createMessage } = useMessage();
|
||||
//折叠面板选中key
|
||||
const collapseActiveKey = ref<string>('');
|
||||
//按钮加载事件
|
||||
const btnLoading = ref<boolean>(false);
|
||||
//第三方配置数据
|
||||
const appConfigData = ref<any>({
|
||||
agentId: undefined,
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
});
|
||||
|
||||
//企业微信钉钉配置modal
|
||||
const [registerAppConfigModal, { openModal }] = useModal();
|
||||
|
||||
/**
|
||||
* 钉钉编辑
|
||||
*/
|
||||
async function dingEditClick() {
|
||||
let tenantId = getTenantId();
|
||||
openModal(true, {
|
||||
tenantId: tenantId,
|
||||
thirdType: 'dingtalk',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化第三方数据
|
||||
*/
|
||||
async function initThirdAppConfigData(params) {
|
||||
let values = await getThirdConfigByTenantId(params);
|
||||
if (values) {
|
||||
appConfigData.value = values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
let tenantId = getTenantId();
|
||||
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'dingtalk' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步钉钉
|
||||
*/
|
||||
async function syncDingTalk() {
|
||||
btnLoading.value = true;
|
||||
await syncDingTalkDepartUserToLocal()
|
||||
.then((res) => {
|
||||
let options = {};
|
||||
if (res.result) {
|
||||
options = {
|
||||
width: 600,
|
||||
title: res.message,
|
||||
content: () => {
|
||||
let nodes;
|
||||
let successInfo = [`成功信息如下:`, renderTextarea(h, res.result.successInfo.map((v, i) => `${i + 1}. ${v}`).join('\n'))];
|
||||
if (res.success) {
|
||||
nodes = [...successInfo, h('br'), `无失败信息!`];
|
||||
} else {
|
||||
nodes = [
|
||||
`失败信息如下:`,
|
||||
renderTextarea(h, res.result.failInfo.map((v, i) => `${i + 1}. ${v}`).join('\n')),
|
||||
h('br'),
|
||||
...successInfo,
|
||||
];
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
};
|
||||
}
|
||||
if (res.success) {
|
||||
if (options != null) {
|
||||
Modal.success(options);
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
} else {
|
||||
if (options && options.title) {
|
||||
Modal.warning(options)
|
||||
} else {
|
||||
createMessage.warning({
|
||||
content: "同步失败,请检查对接信息录入中是否填写正确,并确认是否已开启钉钉配置!",
|
||||
duration: 5
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
btnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染文本
|
||||
* @param h
|
||||
* @param value
|
||||
*/
|
||||
function renderTextarea(h, value) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
id: 'box',
|
||||
style: {
|
||||
minHeight: '100px',
|
||||
border: '1px solid #d9d9d9',
|
||||
fontSize: '14px',
|
||||
maxHeight: '250px',
|
||||
whiteSpace: 'pre',
|
||||
overflow: 'auto',
|
||||
padding: '10px',
|
||||
},
|
||||
},
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 钉钉同步文档
|
||||
*/
|
||||
function handleIconClick(){
|
||||
window.open("https://help.qiaoqiaoyun.com/expand/dingdingsyn.html","_target")
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let tenantId = getTenantId();
|
||||
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'dingtalk' });
|
||||
});
|
||||
|
||||
return {
|
||||
appConfigData,
|
||||
collapseActiveKey,
|
||||
registerAppConfigModal,
|
||||
dingEditClick,
|
||||
handleSuccess,
|
||||
syncDingTalk,
|
||||
btnLoading,
|
||||
handleIconClick,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.header {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.flex-flow {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sync-padding {
|
||||
padding: 12px 0 16px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.base-collapse {
|
||||
margin-top: 20px;
|
||||
padding: 0 24px;
|
||||
font-size: 20px;
|
||||
|
||||
.base-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.base-title {
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.base-message {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
padding: 12px 0 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-content-box) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
/*begin 兼容暗夜模式*/
|
||||
//暗黑模式下卡片的边框设置成none
|
||||
[data-theme='dark'] .base-collapse .ant-collapse{
|
||||
border: none !important;
|
||||
}
|
||||
/*end 兼容暗夜模式*/
|
||||
/*文档按钮问号样式*/
|
||||
.sync-text{
|
||||
margin-left: 2px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px
|
||||
}
|
||||
:deep(.ant-collapse-borderless >.ant-collapse-item:last-child) {border-bottom-width:1px;}
|
||||
</style>
|
||||
@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="base-collapse">
|
||||
<div class="header"> 企业微信集成 </div>
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="1">
|
||||
<template #header>
|
||||
<div style="font-size: 16px"> 1.获取对接信息</div>
|
||||
</template>
|
||||
<div class="base-desc">从企业微信平台获取对接信息,即可开始集成以及同步通讯录</div>
|
||||
<div style="margin-top: 5px">
|
||||
<a href="https://help.qiaoqiaoyun.com/expand/dingding.html" target="_blank">如何获取对接信息?</a>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div>
|
||||
<a-collapse expand-icon-position="right" :bordered="false">
|
||||
<a-collapse-panel key="2">
|
||||
<template #header>
|
||||
<div style="width: 100%; justify-content: space-between; display: flex">
|
||||
<div style="font-size: 16px"> 2.对接信息录入</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">Agentld</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.agentId" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">AppKey</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.clientId" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-flow">
|
||||
<div class="base-title">AppSecret</div>
|
||||
<div class="base-message">
|
||||
<a-input-password v-model:value="appConfigData.clientSecret" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px; width: 100%; text-align: right">
|
||||
<a-button @click="weEnterpriseEditClick">编辑</a-button>
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<div class="sync-padding">
|
||||
<div style="font-size: 16px; width: 100%"> 3.数据同步</div>
|
||||
<div style="margin-top: 20px" class="base-desc">
|
||||
从企业微信同步到敲敲云
|
||||
<a style="margin-left: 10px" @click="seeBindWeChat">查看已绑定的企业微信用户</a>
|
||||
<div style="float: right">
|
||||
<a-button @loading="btnLoading" @click="thirdUserByWechat">同步</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ThirdAppConfigModal @register="registerAppConfigModal" @success="handleSuccess" />
|
||||
<ThirdAppBindWeEnterpriseModal @register="registerBindAppConfigModal" @success="handleBindSuccess" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { getThirdConfigByTenantId } from './ThirdApp.api';
|
||||
import ThirdAppConfigModal from './ThirdAppConfigModal.vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getTenantId } from '@/utils/auth';
|
||||
import ThirdAppBindWeEnterpriseModal from './ThirdAppBindWeEnterpriseModal.vue';
|
||||
import { Modal } from "ant-design-vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ThirdAppWeEnterpriseConfigForm',
|
||||
components: {
|
||||
ThirdAppConfigModal,
|
||||
ThirdAppBindWeEnterpriseModal,
|
||||
},
|
||||
setup() {
|
||||
const btnLoading = ref<boolean>(false);
|
||||
//第三方配置数据
|
||||
const appConfigData = ref<any>({
|
||||
agentId: '',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
agentAppSecret: '',
|
||||
});
|
||||
//企业微信钉钉配置modal
|
||||
const [registerAppConfigModal, { openModal }] = useModal();
|
||||
const [registerBindAppConfigModal, { openModal: openBindModal }] = useModal();
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
async function initThirdAppConfigData(params) {
|
||||
let values = await getThirdConfigByTenantId(params);
|
||||
if (values) {
|
||||
appConfigData.value = values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 企业微信编辑
|
||||
*/
|
||||
async function weEnterpriseEditClick() {
|
||||
let tenantId = getTenantId();
|
||||
openModal(true, {
|
||||
tenantId: tenantId,
|
||||
thirdType: 'wechat_enterprise',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业微信绑定的用户
|
||||
*/
|
||||
async function thirdUserByWechat() {
|
||||
openBindModal(true, { izBind: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
let tenantId = getTenantId();
|
||||
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'wechat_enterprise' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定成功返回值
|
||||
*
|
||||
* @param options
|
||||
* @param item
|
||||
*/
|
||||
function handleBindSuccess(options, item) {
|
||||
console.log("options:::",options)
|
||||
console.log("item:::",item)
|
||||
if (item.success) {
|
||||
if (options != null) {
|
||||
Modal.success(options);
|
||||
} else {
|
||||
createMessage.warning(item.message);
|
||||
}
|
||||
} else {
|
||||
if (options && options.title) {
|
||||
Modal.warning(options);
|
||||
} else {
|
||||
createMessage.warning({
|
||||
content: '同步失败,请检查对接信息录入中是否填写正确,并确认是否已开启企业微信配置!',
|
||||
duration: 5,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看已绑定的企业微信
|
||||
*/
|
||||
function seeBindWeChat() {
|
||||
openBindModal(true,{ izBind: true })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let tenantId = getTenantId();
|
||||
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'wechat_enterprise' });
|
||||
});
|
||||
|
||||
return {
|
||||
appConfigData,
|
||||
weEnterpriseEditClick,
|
||||
registerAppConfigModal,
|
||||
registerBindAppConfigModal,
|
||||
handleSuccess,
|
||||
btnLoading,
|
||||
thirdUserByWechat,
|
||||
handleBindSuccess,
|
||||
seeBindWeChat,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.header {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 50px;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.flex-flow {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sync-padding {
|
||||
padding: 12px 0 16px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.base-collapse {
|
||||
margin-top: 20px;
|
||||
padding: 0 24px;
|
||||
font-size: 20px;
|
||||
|
||||
.base-desc {
|
||||
font-size: 14px;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.base-title {
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.base-message {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
padding: 12px 0 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-content-box) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
/*begin 兼容暗夜模式*/
|
||||
//暗黑模式下卡片的边框设置成none
|
||||
[data-theme='dark'] .base-collapse .ant-collapse {
|
||||
border: none !important;
|
||||
}
|
||||
/*end 兼容暗夜模式*/
|
||||
/*文档按钮问号样式*/
|
||||
.sync-text {
|
||||
margin-left: 2px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
</style>
|
||||
78
jeecgboot-vue3/src/views/system/category/category.api.ts
Normal file
78
jeecgboot-vue3/src/views/system/category/category.api.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/category/rootList',
|
||||
save = '/sys/category/add',
|
||||
edit = '/sys/category/edit',
|
||||
deleteCategory = '/sys/category/delete',
|
||||
deleteBatch = '/sys/category/deleteBatch',
|
||||
importExcel = '/sys/category/importExcel',
|
||||
exportXls = '/sys/category/exportXls',
|
||||
loadTreeData = '/sys/category/loadTreeRoot',
|
||||
getChildList = '/sys/category/childList',
|
||||
getChildListBatch = '/sys/category/getChildListBatch',
|
||||
}
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入api
|
||||
* @param params
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export const deleteCategory = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteCategory, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteCategory = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateDict = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
/**
|
||||
* 查询全部树形节点数据
|
||||
* @param params
|
||||
*/
|
||||
export const loadTreeData = (params) => defHttp.get({ url: Api.loadTreeData, params });
|
||||
/**
|
||||
* 查询子节点数据
|
||||
* @param params
|
||||
*/
|
||||
export const getChildList = (params) => defHttp.get({ url: Api.getChildList, params });
|
||||
/**
|
||||
* 批量查询子节点数据
|
||||
* @param params
|
||||
*/
|
||||
export const getChildListBatch = (params) => defHttp.get({ url: Api.getChildListBatch, params }, { isTransformResponse: false });
|
||||
67
jeecgboot-vue3/src/views/system/category/category.data.ts
Normal file
67
jeecgboot-vue3/src/views/system/category/category.data.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '分类名称',
|
||||
dataIndex: 'name',
|
||||
width: 350,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '分类编码',
|
||||
dataIndex: 'code',
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '名称',
|
||||
field: 'name',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '编码',
|
||||
field: 'code',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '父级节点',
|
||||
field: 'pid',
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
//update-begin---author:wangshuai ---date:20230829 for:replaceFields已过期,使用fieldNames代替------------
|
||||
fieldNames: {
|
||||
//update-end---author:wangshuai ---date:20230829 for:replaceFields已过期,使用fieldNames代替------------
|
||||
value: 'key',
|
||||
},
|
||||
dropdownStyle: {
|
||||
maxHeight: '50vh',
|
||||
},
|
||||
getPopupContainer: () => document.body,
|
||||
},
|
||||
show: ({ values }) => {
|
||||
return values.pid !== '0';
|
||||
},
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '分类名称',
|
||||
field: 'name',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose width="550px" :title="getTitle" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { BasicForm, useForm } from '/src/components/Form';
|
||||
import { formSchema } from '../category.data';
|
||||
import { loadTreeData, saveOrUpdateDict } from '../category.api';
|
||||
// 获取emit
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
const expandedRowKeys = ref([]);
|
||||
const treeData = ref([]);
|
||||
const isSubAdd = ref(false);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 4 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 18 },
|
||||
},
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
expandedRowKeys.value = [];
|
||||
setModalProps({ confirmLoading: false, minHeight: 80 });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
//update-begin---author:wangshuai ---date: 20230829 for:分类字典data.record为空报错------------
|
||||
isSubAdd.value = !data?.isUpdate && data.record && data.record.id;
|
||||
//update-end---author:wangshuai ---date: 20230829 for:分类字典data.record为空报错------------
|
||||
if (data?.record) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
//父级节点树信息
|
||||
treeData.value = await loadTreeData({ async: false, pcode: '' });
|
||||
updateSchema({
|
||||
field: 'pid',
|
||||
componentProps: { treeData },
|
||||
});
|
||||
});
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增字典' : '编辑字典'));
|
||||
|
||||
/**
|
||||
* 根据pid获取展开的节点
|
||||
* @param pid
|
||||
* @param arr
|
||||
*/
|
||||
function getExpandKeysByPid(pid, arr) {
|
||||
if (pid && arr && arr.length > 0) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].key == pid && unref(expandedRowKeys).indexOf(pid) < 0) {
|
||||
//需要获取同一级的key
|
||||
getSameLevelExpandKeysByPid(arr[i]);
|
||||
expandedRowKeys.value.push(arr[i].key);
|
||||
getExpandKeysByPid(arr[i]['parentId'], unref(treeData));
|
||||
} else {
|
||||
getExpandKeysByPid(pid, arr[i].children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateDict(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//展开的节点信息
|
||||
await getExpandKeysByPid(values['pid'], unref(treeData));
|
||||
//刷新列表(isUpdate:是否编辑;values:表单信息;expandedArr:展开的节点信息)
|
||||
emit('success', { isUpdate: unref(isUpdate), isSubAdd:unref(isSubAdd), values: { ...values }, expandedArr: unref(expandedRowKeys).reverse() });
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同一级的id和同一级的子级id
|
||||
*/
|
||||
function getSameLevelExpandKeysByPid(arr) {
|
||||
if (arr.children && arr.children.length > 0) {
|
||||
for (const children of arr.children) {
|
||||
if (unref(expandedRowKeys).indexOf(children.key) < 0 && children.children && children.children.length > 0) {
|
||||
getSameLevelExpandKeysByPid(children);
|
||||
expandedRowKeys.value.push(children.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
298
jeecgboot-vue3/src/views/system/category/index.vue
Normal file
298
jeecgboot-vue3/src/views/system/category/index.vue
Normal file
@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--引用表格-->
|
||||
<BasicTable
|
||||
@register="registerTable"
|
||||
:rowSelection="rowSelection"
|
||||
:expandedRowKeys="expandedRowKeys"
|
||||
@expand="handleExpand"
|
||||
@fetch-success="onFetchSuccess"
|
||||
>
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!--字典弹窗-->
|
||||
<CategoryModal @register="registerModal" @success="handleSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="system-category" setup>
|
||||
//ts语法
|
||||
import { ref, computed, unref, toRaw, nextTick } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/src/components/Table';
|
||||
import { useDrawer } from '/src/components/Drawer';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
import { useModal } from '/src/components/Modal';
|
||||
import { useMethods } from '/src/hooks/system/useMethods';
|
||||
import { columns, searchFormSchema } from './category.data';
|
||||
import { list, deleteCategory, batchDeleteCategory, getExportUrl, getImportUrl, getChildList, getChildListBatch } from './category.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
const expandedRowKeys = ref([]);
|
||||
const { handleExportXls, handleImportXls } = useMethods();
|
||||
//字典model
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, onExportXls, onImportXls, tableContext } = useListPage({
|
||||
designScope: 'category-template',
|
||||
tableProps: {
|
||||
title: '分类字典',
|
||||
api: list,
|
||||
columns: columns,
|
||||
actionColumn: {
|
||||
width: 180,
|
||||
},
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
isTreeTable: true,
|
||||
},
|
||||
exportConfig: {
|
||||
name: '分类字典列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
//注册table数据
|
||||
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] =
|
||||
tableContext;
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleCreate() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
async function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
async function handleDetail(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
hideFooter: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteCategory({ id: record.id }, importSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
const ids = selectedRowKeys.value.filter((item) => !item.includes('loading'));
|
||||
await batchDeleteCategory({ ids: ids }, importSuccess);
|
||||
}
|
||||
/**
|
||||
* 导入
|
||||
*/
|
||||
function importSuccess() {
|
||||
//update-begin---author:wangshuai ---date:20220530 for:[issues/54]树字典,勾选,然后批量删除,系统错误------------
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
//update-end---author:wangshuai ---date:20220530 for:[issues/54]树字典,勾选,然后批量删除,系统错误--------------
|
||||
}
|
||||
/**
|
||||
* 添加下级
|
||||
*/
|
||||
function handleAddSub(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
async function handleSuccess({ isUpdate,isSubAdd, values, expandedArr }) {
|
||||
if (isUpdate) {
|
||||
//编辑回调
|
||||
updateTableDataRecord(values.id, values);
|
||||
} else {
|
||||
if (!values['pid']) {
|
||||
//新增根节点
|
||||
reload();
|
||||
} else {
|
||||
//新增子集
|
||||
//update-begin-author:liusq---date:20230411--for: [issue/4550]分类字典数据量过多会造成数据查询时间过长---
|
||||
if(isSubAdd){
|
||||
await expandTreeNode(values.pid);
|
||||
//update-end-author:liusq---date:20230411--for: [issue/4550]分类字典数据量过多会造成数据查询时间过长---
|
||||
}else{
|
||||
//update-begin-author:wangshuai---date:20240319--for: 字典树删除之后其他节点出现loading---
|
||||
//expandedRowKeys.value = [];
|
||||
//update-end-author:wangshuai---date:20240319--for: 字典树删除之后其他节点出现loading---
|
||||
for (let key of unref(expandedArr)) {
|
||||
await expandTreeNode(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口请求成功后回调
|
||||
*/
|
||||
function onFetchSuccess(result) {
|
||||
getDataByResult(result.items) && loadDataByExpandedRows();
|
||||
}
|
||||
/**
|
||||
* 根据已展开的行查询数据(用于保存后刷新时异步加载子级的数据)
|
||||
*/
|
||||
async function loadDataByExpandedRows() {
|
||||
if (unref(expandedRowKeys).length > 0) {
|
||||
const res = await getChildListBatch({ parentIds: unref(expandedRowKeys).join(',') });
|
||||
if (res.success && res.result.records.length > 0) {
|
||||
//已展开的数据批量子节点
|
||||
let records = res.result.records;
|
||||
const listMap = new Map();
|
||||
for (let item of records) {
|
||||
let pid = item['pid'];
|
||||
if (unref(expandedRowKeys).includes(pid)) {
|
||||
let mapList = listMap.get(pid);
|
||||
if (mapList == null) {
|
||||
mapList = [];
|
||||
}
|
||||
mapList.push(item);
|
||||
listMap.set(pid, mapList);
|
||||
}
|
||||
}
|
||||
let childrenMap = listMap;
|
||||
let fn = (list) => {
|
||||
if (list) {
|
||||
list.forEach((data) => {
|
||||
if (unref(expandedRowKeys).includes(data.id)) {
|
||||
data.children = getDataByResult(childrenMap.get(data.id));
|
||||
fn(data.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
fn(getDataSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理数据集
|
||||
*/
|
||||
function getDataByResult(result) {
|
||||
if (result && result.length > 0) {
|
||||
return result.map((item) => {
|
||||
//判断是否标记了带有子节点
|
||||
if (item['hasChild'] == '1') {
|
||||
let loadChild = { id: item.id + '_loadChild', name: 'loading...', isLoading: true };
|
||||
item.children = [loadChild];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
*树节点展开合并
|
||||
* */
|
||||
async function handleExpand(expanded, record) {
|
||||
// 判断是否是展开状态,展开状态(expanded)并且存在子集(children)并且未加载过(isLoading)的就去查询子节点数据
|
||||
if (expanded) {
|
||||
expandedRowKeys.value.push(record.id);
|
||||
if (record.children.length > 0 && !!record.children[0].isLoading) {
|
||||
let result = await getChildList({ pid: record.id });
|
||||
if (result && result.length > 0) {
|
||||
record.children = getDataByResult(result);
|
||||
} else {
|
||||
record.children = null;
|
||||
record.hasChild = '0';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let keyIndex = expandedRowKeys.value.indexOf(record.id);
|
||||
if (keyIndex >= 0) {
|
||||
expandedRowKeys.value.splice(keyIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
*操作表格后处理树节点展开合并
|
||||
* */
|
||||
async function expandTreeNode(key) {
|
||||
let record:any = findTableDataRecord(key);
|
||||
//update-begin-author:liusq---date:20230411--for: [issue/4550]分类字典数据量过多会造成数据查询时间过长,显示“接口请求超时,请刷新页面重试!”---
|
||||
if(!expandedRowKeys.value.includes(key)){
|
||||
expandedRowKeys.value.push(key);
|
||||
}
|
||||
//update-end-author:liusq---date:20230411--for: [issue/4550]分类字典数据量过多会造成数据查询时间过长,显示“接口请求超时,请刷新页面重试!”---
|
||||
let result = await getChildList({ pid: key });
|
||||
if (result && result.length > 0) {
|
||||
record.children = getDataByResult(result);
|
||||
} else {
|
||||
record.children = null;
|
||||
record.hasChild = '0';
|
||||
}
|
||||
updateTableDataRecord(key, record);
|
||||
}
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '确定删除吗?',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '添加下级',
|
||||
onClick: handleAddSub.bind(null, { pid: record.id }),
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
247
jeecgboot-vue3/src/views/system/checkRule/CheckRuleModal.vue
Normal file
247
jeecgboot-vue3/src/views/system/checkRule/CheckRuleModal.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" @ok="handleSubmit" :title="title" :width="1200" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
|
||||
<a-tabs v-model:activeKey="activeKey" animated>
|
||||
<a-tab-pane tab="局部规则" key="1" :forceRender="true">
|
||||
<JVxeTable ref="vTable1" toolbar rowNumber dragSort rowSelection :maxHeight="580" :dataSource="dataSource1" :columns="columns1">
|
||||
<template #toolbarAfter>
|
||||
<a-alert type="info" showIcon message="局部规则按照你输入的位数有序的校验" style="margin-bottom: 8px" />
|
||||
</template>
|
||||
</JVxeTable>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="全局规则" key="2" :forceRender="true">
|
||||
<JVxeTable
|
||||
ref="vTable2"
|
||||
toolbar
|
||||
rowNumber
|
||||
dragSort
|
||||
rowSelection
|
||||
:maxHeight="580"
|
||||
:dataSource="dataSource2"
|
||||
:addSetActive="false"
|
||||
:columns="columns2"
|
||||
>
|
||||
<template #toolbarAfter>
|
||||
<a-alert type="info" showIcon message="全局规则可校验用户输入的所有字符;全局规则的优先级比局部规则的要高。" style="margin-bottom: 8px" />
|
||||
</template>
|
||||
</JVxeTable>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { computed, ref, unref } from 'vue';
|
||||
import { formSchema } from './check.rule.data';
|
||||
import { saveCheckRule, updateCheckRule } from './check.rule.api';
|
||||
import { JVxeTypes, JVxeColumn, JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
const activeKey = ref('1');
|
||||
let arr1: any[] = [];
|
||||
let dataSource1 = ref(arr1);
|
||||
let arr2: any[] = [];
|
||||
let dataSource2 = ref(arr2);
|
||||
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
activeKey.value = '1';
|
||||
dataSource1.value = [];
|
||||
dataSource2.value = [];
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
|
||||
let ruleJson = data.record.ruleJson;
|
||||
if (ruleJson) {
|
||||
let ruleList = JSON.parse(ruleJson);
|
||||
// 筛选出全局规则和局部规则
|
||||
let global: any[] = [],
|
||||
design: any[] = [],
|
||||
priority = '1';
|
||||
ruleList.forEach((rule) => {
|
||||
if (rule.digits === '*') {
|
||||
global.push(Object.assign(rule, { priority }));
|
||||
} else {
|
||||
priority = '0';
|
||||
design.push(rule);
|
||||
}
|
||||
});
|
||||
dataSource1.value = design;
|
||||
dataSource2.value = global;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const vTable1 = ref<JVxeTableInstance>();
|
||||
const vTable2 = ref<JVxeTableInstance>();
|
||||
|
||||
// 验证表格 返回表格数据
|
||||
function validateMyTable(tableRef, key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
tableRef.value!.validateTable().then((errMap) => {
|
||||
if (errMap) {
|
||||
activeKey.value = key;
|
||||
reject();
|
||||
} else {
|
||||
const values = tableRef.value!.getTableData();
|
||||
resolve(values);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
let mainData;
|
||||
let globalValues = [];
|
||||
let designValues = [];
|
||||
validate()
|
||||
.then((formValue) => {
|
||||
mainData = formValue;
|
||||
return validateMyTable(vTable1, '1');
|
||||
})
|
||||
.then((tableData1: []) => {
|
||||
if (tableData1 && tableData1.length > 0) {
|
||||
designValues = tableData1;
|
||||
}
|
||||
return validateMyTable(vTable2, '2');
|
||||
})
|
||||
.then((tableData2: []) => {
|
||||
if (tableData2 && tableData2.length > 0) {
|
||||
globalValues = tableData2;
|
||||
}
|
||||
// 整合两个子表的数据
|
||||
let firstGlobal: any[] = [],
|
||||
afterGlobal: any[] = [];
|
||||
for (let i = 0; i < globalValues.length; i++) {
|
||||
let v: any = globalValues[i];
|
||||
v.digits = '*';
|
||||
if (v.priority === '1') {
|
||||
firstGlobal.push(v);
|
||||
} else {
|
||||
afterGlobal.push(v);
|
||||
}
|
||||
}
|
||||
let concatValues = firstGlobal.concat(designValues).concat(afterGlobal);
|
||||
let subValues = concatValues.map((i) => pick(i, 'digits', 'pattern', 'message'));
|
||||
// 生成 formData,用于传入后台
|
||||
let ruleJson = JSON.stringify(subValues);
|
||||
let formData = Object.assign({}, mainData, { ruleJson });
|
||||
saveOrUpdateFormData(formData);
|
||||
})
|
||||
.catch(() => {
|
||||
setModalProps({ confirmLoading: false });
|
||||
console.error('验证未通过!');
|
||||
});
|
||||
}
|
||||
|
||||
// 表单提交请求
|
||||
async function saveOrUpdateFormData(formData) {
|
||||
try {
|
||||
console.log('表单提交数据', formData);
|
||||
setModalProps({ confirmLoading: true });
|
||||
if (isUpdate.value) {
|
||||
await updateCheckRule(formData);
|
||||
} else {
|
||||
await saveCheckRule(formData);
|
||||
}
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验
|
||||
* @param cellValue
|
||||
* @param callback
|
||||
*/
|
||||
const validatePatternHandler = ({ cellValue }, callback) => {
|
||||
try {
|
||||
new RegExp(cellValue);
|
||||
callback(true);
|
||||
} catch (e) {
|
||||
callback(false, '请输入正确的正则表达式');
|
||||
}
|
||||
};
|
||||
|
||||
const columns1 = ref<JVxeColumn[]>([
|
||||
{
|
||||
title: '位数',
|
||||
key: 'digits',
|
||||
type: JVxeTypes.inputNumber,
|
||||
minWidth: 180,
|
||||
validateRules: [
|
||||
{ required: true, message: '${title}不能为空' },
|
||||
{ pattern: /^[1-9]\d*$/, message: '请输入零以上的正整数' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '规则(正则表达式)',
|
||||
key: 'pattern',
|
||||
minWidth: 320,
|
||||
type: JVxeTypes.input,
|
||||
validateRules: [{ required: true, message: '规则不能为空' }, { handler: validatePatternHandler }],
|
||||
},
|
||||
{
|
||||
title: '提示文本',
|
||||
key: 'message',
|
||||
minWidth: 180,
|
||||
type: JVxeTypes.input,
|
||||
validateRules: [{ required: true, message: '${title}不能为空' }],
|
||||
},
|
||||
]);
|
||||
|
||||
const columns2 = ref<JVxeColumn[]>([
|
||||
{
|
||||
title: '优先级',
|
||||
key: 'priority',
|
||||
type: JVxeTypes.select,
|
||||
defaultValue: '1',
|
||||
options: [
|
||||
{ title: '优先运行', value: '1' },
|
||||
{ title: '最后运行', value: '0' },
|
||||
],
|
||||
validateRules: [],
|
||||
},
|
||||
{
|
||||
title: '规则(正则表达式)',
|
||||
key: 'pattern',
|
||||
width: '40%',
|
||||
type: JVxeTypes.input,
|
||||
validateRules: [{ required: true, message: '规则不能为空' }, { handler: validatePatternHandler }],
|
||||
},
|
||||
{
|
||||
title: '提示文本',
|
||||
key: 'message',
|
||||
width: '20%',
|
||||
type: JVxeTypes.input,
|
||||
validateRules: [{ required: true, message: '${title}不能为空' }],
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" :okButtonProps="okButtonProps" @register="registerModal" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
<div style="display: flex; flex-flow: row wrap">
|
||||
<div style="padding: 0 4px" v-for="(str, index) of realTestValue" :key="index">
|
||||
<a-row>
|
||||
<a-col style="text-align: center">
|
||||
<a-input :value="str" style="text-align: center; width: 40px" />
|
||||
</a-col>
|
||||
<a-col style="text-align: center">{{ index + 1 }}</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { checkRuleInput } from '/@/views/system/checkRule/check.rule.data';
|
||||
import { ref } from 'vue';
|
||||
let realTestValue = ref('');
|
||||
const okButtonProps = {
|
||||
style: { display: 'none' },
|
||||
};
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
|
||||
schemas: checkRuleInput,
|
||||
showActionButtonGroup: false,
|
||||
labelCol: {
|
||||
span: 24,
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 24,
|
||||
},
|
||||
});
|
||||
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
realTestValue.value = '';
|
||||
setModalProps({
|
||||
confirmLoading: false,
|
||||
cancelText: '关闭',
|
||||
title: '功能测试',
|
||||
width: '1000px',
|
||||
});
|
||||
await setFieldsValue({
|
||||
ruleCode: data.ruleCode,
|
||||
testValue: realTestValue,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
86
jeecgboot-vue3/src/views/system/checkRule/check.rule.api.ts
Normal file
86
jeecgboot-vue3/src/views/system/checkRule/check.rule.api.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/checkRule/list',
|
||||
delete = '/sys/checkRule/delete',
|
||||
deleteBatch = '/sys/checkRule/deleteBatch',
|
||||
exportXls = 'sys/checkRule/exportXls',
|
||||
importXls = 'sys/checkRule/importExcel',
|
||||
checkByCode = '/sys/checkRule/checkByCode',
|
||||
save = '/sys/checkRule/add',
|
||||
edit = '/sys/checkRule/edit',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出地址
|
||||
*/
|
||||
export const exportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入地址
|
||||
*/
|
||||
export const importUrl = Api.importXls;
|
||||
|
||||
/**
|
||||
* 列表查询
|
||||
* @param params
|
||||
*/
|
||||
export const getCheckRuleList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteCheckRule = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteCheckRule = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据编码校验规则code,校验传入的值是否合法
|
||||
* @param ruleCode
|
||||
* @param value
|
||||
*/
|
||||
export const validateCheckRule = (ruleCode, value) => {
|
||||
value = encodeURIComponent(value);
|
||||
let params = { ruleCode, value };
|
||||
return defHttp.get({ url: Api.checkByCode, params }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @param params
|
||||
*/
|
||||
export const saveCheckRule = (params) => {
|
||||
return defHttp.post({ url: Api.save, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param params
|
||||
*/
|
||||
export const updateCheckRule = (params) => {
|
||||
return defHttp.put({ url: Api.edit, params });
|
||||
};
|
||||
152
jeecgboot-vue3/src/views/system/checkRule/check.rule.data.ts
Normal file
152
jeecgboot-vue3/src/views/system/checkRule/check.rule.data.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
import { duplicateCheckDelay } from '/@/views/system/user/user.api';
|
||||
import { validateCheckRule } from '/@/views/system/checkRule/check.rule.api';
|
||||
import { array } from 'vue-types';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '规则名称',
|
||||
dataIndex: 'ruleName',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '规则编码',
|
||||
dataIndex: 'ruleCode',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '规则描述',
|
||||
dataIndex: 'ruleDescription',
|
||||
width: 300,
|
||||
align: 'center',
|
||||
customRender: function ({ text }) {
|
||||
return render.renderTip(text, 30);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
field: 'ruleCode',
|
||||
label: '规则编码',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
field: 'ruleCode',
|
||||
label: '规则编码',
|
||||
component: 'Input',
|
||||
colProps: { span: 24 },
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
dynamicRules: ({ model }) => {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!value) {
|
||||
return reject('请输入规则编码!');
|
||||
}
|
||||
let params = {
|
||||
tableName: 'sys_check_rule',
|
||||
fieldName: 'rule_code',
|
||||
fieldVal: value,
|
||||
dataId: model.id,
|
||||
};
|
||||
duplicateCheckDelay(params)
|
||||
.then((res) => {
|
||||
res.success ? resolve() : reject('规则编码已存在!');
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || '校验失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ruleDescription',
|
||||
label: '规则描述',
|
||||
colProps: { span: 24 },
|
||||
component: 'InputTextArea',
|
||||
componentProps: {
|
||||
placeholder: '请输入规则描述',
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const checkRuleInput: FormSchema[] = [
|
||||
{
|
||||
label: '123',
|
||||
field: 'ruleCode',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'testValue',
|
||||
label: '需要测试的值:',
|
||||
component: 'Input',
|
||||
componentProps: ({ formModel }) => {
|
||||
return {
|
||||
onChange: (e) => {
|
||||
formModel.testValue = e.target.value;
|
||||
},
|
||||
};
|
||||
},
|
||||
dynamicRules: ({ model }) => {
|
||||
const { ruleCode } = model;
|
||||
return [
|
||||
{
|
||||
required: false,
|
||||
validator: (_, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (ruleCode && value) {
|
||||
/*console.log({ruleCode,value})*/
|
||||
validateCheckRule(ruleCode, value)
|
||||
.then((res) => {
|
||||
//console.log(1233, res)
|
||||
res['success'] ? resolve() : reject(res['message']);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || err);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
150
jeecgboot-vue3/src/views/system/checkRule/index.vue
Normal file
150
jeecgboot-vue3/src/views/system/checkRule/index.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作</span>
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<CheckRuleTestModal @register="testModal"></CheckRuleTestModal>
|
||||
<CheckRuleModal @register="registerModal" @success="reload"></CheckRuleModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script name="system-fillrule" lang="ts" setup>
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import {
|
||||
getCheckRuleList,
|
||||
exportUrl,
|
||||
importUrl,
|
||||
deleteCheckRule,
|
||||
batchDeleteCheckRule,
|
||||
validateCheckRule,
|
||||
} from '/@/views/system/checkRule/check.rule.api';
|
||||
import { columns, searchFormSchema } from '/@/views/system/checkRule/check.rule.data';
|
||||
import { ActionItem } from '/@/components/Table';
|
||||
|
||||
import CheckRuleTestModal from '/@/views/system/checkRule/CheckRuleTestModal.vue';
|
||||
const [testModal, { openModal: openTestModal }] = useModal();
|
||||
|
||||
import CheckRuleModal from '/@/views/system/checkRule/CheckRuleModal.vue';
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, createMessage, onExportXls, onImportXls, createSuccessModal } = useListPage({
|
||||
designScope: 'check-rule',
|
||||
tableProps: {
|
||||
title: '编码校验规则管理页面',
|
||||
api: getCheckRuleList,
|
||||
columns: columns,
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
url: exportUrl,
|
||||
name: '编码校验规则列表',
|
||||
},
|
||||
importConfig: {
|
||||
url: importUrl,
|
||||
success: () => reload(),
|
||||
},
|
||||
});
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
console.log('record....', record);
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
console.log(12345, record);
|
||||
await deleteCheckRule({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteCheckRule({ ids: selectedRowKeys.value }, () => {
|
||||
selectedRowKeys.value = [];
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能测试
|
||||
*/
|
||||
function testRule(record) {
|
||||
openTestModal(true, { ruleCode: record.ruleCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [{ label: '编辑', onClick: handleEdit.bind(null, record) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '功能测试', onClick: testRule.bind(null, record) },
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认要删除吗?',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<BasicDrawer title="数据规则/按钮权限配置" :width="365" @close="onClose" @register="registerDrawer">
|
||||
<a-spin :spinning="loading">
|
||||
<a-tabs defaultActiveKey="1">
|
||||
<a-tab-pane tab="数据规则" key="1">
|
||||
<a-checkbox-group v-model:value="dataRuleChecked" v-if="dataRuleList.length > 0">
|
||||
<a-row>
|
||||
<a-col :span="24" v-for="(item, index) in dataRuleList" :key="'dr' + index">
|
||||
<a-checkbox :value="item.id">{{ item.ruleName }}</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="saveDataRuleForRole">
|
||||
<span>点击保存</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<a-empty v-else description="无配置信息" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
|
||||
import { queryDepartDataRule, saveDepartDataRule } from '../depart.api';
|
||||
|
||||
defineEmits(['register']);
|
||||
const loading = ref<boolean>(false);
|
||||
const departId = ref('');
|
||||
const functionId = ref('');
|
||||
const dataRuleList = ref<Array<any>>([]);
|
||||
const dataRuleChecked = ref<Array<any>>([]);
|
||||
|
||||
// 注册抽屉组件
|
||||
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
|
||||
departId.value = unref(data.departId);
|
||||
functionId.value = unref(data.functionId);
|
||||
loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { datarule, drChecked } = await queryDepartDataRule(functionId, departId);
|
||||
dataRuleList.value = datarule;
|
||||
if (drChecked) {
|
||||
dataRuleChecked.value = drChecked.split(',');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveDataRuleForRole() {
|
||||
let params = {
|
||||
departId: departId.value,
|
||||
permissionId: functionId.value,
|
||||
dataRuleIds: dataRuleChecked.value.join(','),
|
||||
};
|
||||
saveDepartDataRule(params);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
doReset();
|
||||
}
|
||||
|
||||
function doReset() {
|
||||
functionId.value = '';
|
||||
dataRuleList.value = [];
|
||||
dataRuleChecked.value = [];
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
|
||||
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({
|
||||
rootTreeData: { type: Array, default: () => [] },
|
||||
});
|
||||
const prefixCls = inject('prefixCls');
|
||||
// 当前是否是更新模式
|
||||
const isUpdate = ref<boolean>(false);
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
// 注册弹窗
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
isUpdate.value = unref(data?.isUpdate);
|
||||
// 当前是否为添加子级
|
||||
let isChild = unref(data?.isChild);
|
||||
let categoryOptions = isChild ? orgCategoryOptions.child : orgCategoryOptions.root;
|
||||
// 隐藏不需要展示的字段
|
||||
updateSchema([
|
||||
{
|
||||
field: 'parentId',
|
||||
show: isChild,
|
||||
componentProps: {
|
||||
// 如果是添加子部门,就禁用该字段
|
||||
disabled: isChild,
|
||||
treeData: props.rootTreeData,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orgCode',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'orgCategory',
|
||||
componentProps: { options: categoryOptions },
|
||||
},
|
||||
]);
|
||||
|
||||
let record = unref(data?.record);
|
||||
if (typeof record !== 'object') {
|
||||
record = {};
|
||||
}
|
||||
// 赋默认值
|
||||
record = Object.assign(
|
||||
{
|
||||
departOrder: 0,
|
||||
orgCategory: categoryOptions[0].value,
|
||||
},
|
||||
record
|
||||
);
|
||||
model.value = record;
|
||||
await setFieldsValue({ ...record });
|
||||
});
|
||||
|
||||
// 提交事件
|
||||
async function handleOk() {
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
let values = await validate();
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<BasicForm @register="registerForm" />
|
||||
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
|
||||
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]">
|
||||
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
const { prefixCls } = useDesign('j-depart-form-content');
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
rootTreeData: { type: Array, default: () => [] },
|
||||
});
|
||||
const loading = ref<boolean>(false);
|
||||
// 当前是否是更新模式
|
||||
const isUpdate = ref<boolean>(true);
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
const categoryOptions = computed(() => {
|
||||
if (!!props?.data?.parentId) {
|
||||
return orgCategoryOptions.child;
|
||||
} else {
|
||||
return orgCategoryOptions.root;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 禁用字段
|
||||
updateSchema([
|
||||
{ field: 'parentId', componentProps: { disabled: true } },
|
||||
{ field: 'orgCode', componentProps: { disabled: true } },
|
||||
]);
|
||||
// data 变化,重填表单
|
||||
watch(
|
||||
() => props.data,
|
||||
async () => {
|
||||
let record = unref(props.data);
|
||||
if (typeof record !== 'object') {
|
||||
record = {};
|
||||
}
|
||||
model.value = record;
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...record });
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
// 更新 父部门 选项
|
||||
watch(
|
||||
() => props.rootTreeData,
|
||||
async () => {
|
||||
updateSchema([
|
||||
{
|
||||
field: 'parentId',
|
||||
componentProps: { treeData: props.rootTreeData },
|
||||
},
|
||||
]);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
// 监听并更改 orgCategory options
|
||||
watch(
|
||||
categoryOptions,
|
||||
async () => {
|
||||
updateSchema([
|
||||
{
|
||||
field: 'orgCategory',
|
||||
componentProps: { options: categoryOptions.value },
|
||||
},
|
||||
]);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
|
||||
// 重置表单
|
||||
async function onReset() {
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...model.value });
|
||||
}
|
||||
|
||||
// 提交事件
|
||||
async function onSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
let values = await validate();
|
||||
values = Object.assign({}, model.value, values);
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//刷新列表
|
||||
emit('success');
|
||||
Object.assign(model.value, values);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
|
||||
@prefix-cls: ~'@{namespace}-j-depart-form-content';
|
||||
/*begin 兼容暗夜模式*/
|
||||
.@{prefix-cls} {
|
||||
background: @component-background;
|
||||
border-top: 1px solid @border-color-base;
|
||||
}
|
||||
/*end 兼容暗夜模式*/
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
</style>
|
||||
@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<div class="j-table-operator" style="width: 100%">
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddDepart">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddChildDepart()">添加下级</a-button>
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls">
|
||||
<a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button>
|
||||
</a-upload>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步企微?</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步钉钉?</a-button>
|
||||
<template v-if="checkedKeys.length > 0">
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="onDeleteBatch">
|
||||
<icon icon="ant-design:delete-outlined" />
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作 </span>
|
||||
<icon icon="akar-icons:chevron-down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
<a-alert type="info" show-icon class="alert" style="margin-bottom: 8px">
|
||||
<template #message>
|
||||
<template v-if="checkedKeys.length > 0">
|
||||
<span>已选中 {{ checkedKeys.length }} 条记录</span>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="checkedKeys = []">清空</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>未选中任何数据</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-spin :spinning="loading">
|
||||
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" />
|
||||
<!--组织机构树-->
|
||||
<template v-if="treeData.length > 0">
|
||||
<a-tree
|
||||
v-if="!treeReloading"
|
||||
checkable
|
||||
:clickRowToExpand="false"
|
||||
:treeData="treeData"
|
||||
:selectedKeys="selectedKeys"
|
||||
:checkStrictly="checkStrictly"
|
||||
:load-data="loadChildrenTreeData"
|
||||
:checkedKeys="checkedKeys"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@check="onCheck"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, dataRef }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<Popconfirm
|
||||
:open="visibleTreeKey === treeKey"
|
||||
title="确定要删除吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
placement="rightTop"
|
||||
@confirm="onDelete(dataRef)"
|
||||
@openChange="onVisibleChange"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
</Popconfirm>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="">
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</a-menu-item>
|
||||
<a-menu-item key="2" @click="visibleTreeKey = treeKey">
|
||||
<span style="color: red">删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
<a-empty v-else description="暂无数据" />
|
||||
</a-spin>
|
||||
<DepartFormModal :rootTreeData="treeData" @register="registerModal" @success="loadRootTreeData" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, unref } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useMethods } from '/@/hooks/system/useMethods';
|
||||
import { Api, deleteBatchDepart, queryDepartTreeSync } from '../depart.api';
|
||||
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select', 'rootTreeData']);
|
||||
const { createMessage } = useMessage();
|
||||
const { handleImportXls, handleExportXls } = useMethods();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 部门树列表数据
|
||||
const treeData = ref<any[]>([]);
|
||||
// 当前选中的项
|
||||
const checkedKeys = ref<any[]>([]);
|
||||
// 当前展开的项
|
||||
const expandedKeys = ref<any[]>([]);
|
||||
// 当前选中的项
|
||||
const selectedKeys = ref<any[]>([]);
|
||||
// 树组件重新加载
|
||||
const treeReloading = ref<boolean>(false);
|
||||
// 树父子是否关联
|
||||
const checkStrictly = ref<boolean>(true);
|
||||
// 当前选中的部门
|
||||
const currentDepart = ref<any>(null);
|
||||
// 控制确认删除提示框是否显示
|
||||
const visibleTreeKey = ref<any>(null);
|
||||
// 搜索关键字
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 注册 modal
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 加载顶级部门信息
|
||||
async function loadRootTreeData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
const result = await queryDepartTreeSync();
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
if (expandedKeys.value.length === 0) {
|
||||
autoExpandParentNode();
|
||||
} else {
|
||||
if (selectedKeys.value.length === 0) {
|
||||
let item = treeData.value[0];
|
||||
if (item) {
|
||||
// 默认选中第一个
|
||||
setSelectedKey(item.id, item);
|
||||
}
|
||||
} else {
|
||||
emit('select', currentDepart.value);
|
||||
}
|
||||
}
|
||||
emit('rootTreeData', treeData.value);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
loadRootTreeData();
|
||||
|
||||
// 加载子级部门信息
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
pid: treeNode.dataRef.id,
|
||||
});
|
||||
if (result.length == 0) {
|
||||
treeNode.dataRef.isLeaf = true;
|
||||
} else {
|
||||
treeNode.dataRef.children = result;
|
||||
if (expandedKeys.value.length > 0) {
|
||||
// 判断获取的子级是否有当前展开的项
|
||||
let subKeys: any[] = [];
|
||||
for (let key of expandedKeys.value) {
|
||||
if (result.findIndex((item) => item.id === key) !== -1) {
|
||||
subKeys.push(key);
|
||||
}
|
||||
}
|
||||
if (subKeys.length > 0) {
|
||||
expandedKeys.value = [...expandedKeys.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
treeData.value = [...treeData.value];
|
||||
emit('rootTreeData', treeData.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 自动展开父节点,只展开一级
|
||||
function autoExpandParentNode() {
|
||||
let item = treeData.value[0];
|
||||
if (item) {
|
||||
if (!item.isLeaf) {
|
||||
expandedKeys.value = [item.key];
|
||||
}
|
||||
// 默认选中第一个
|
||||
setSelectedKey(item.id, item);
|
||||
reloadTree();
|
||||
} else {
|
||||
emit('select', null);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载树组件,防止无法默认展开数据
|
||||
async function reloadTree() {
|
||||
await nextTick();
|
||||
treeReloading.value = true;
|
||||
await nextTick();
|
||||
treeReloading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中的行
|
||||
*/
|
||||
function setSelectedKey(key: string, data?: object) {
|
||||
selectedKeys.value = [key];
|
||||
if (data) {
|
||||
currentDepart.value = data;
|
||||
emit('select', data);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一级部门
|
||||
function onAddDepart() {
|
||||
openModal(true, { isUpdate: false, isChild: false });
|
||||
}
|
||||
|
||||
// 添加子级部门
|
||||
function onAddChildDepart(data = currentDepart.value) {
|
||||
if (data == null) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
return;
|
||||
}
|
||||
const record = { parentId: data.id };
|
||||
openModal(true, { isUpdate: false, isChild: true, record });
|
||||
}
|
||||
|
||||
// 搜索事件
|
||||
async function onSearch(value: string) {
|
||||
if (value) {
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result = await searchByKeywords({ keyWord: value });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
autoExpandParentNode();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
loadRootTreeData();
|
||||
}
|
||||
searchKeyword.value = value;
|
||||
}
|
||||
|
||||
// 树复选框选择事件
|
||||
function onCheck(e) {
|
||||
if (Array.isArray(e)) {
|
||||
checkedKeys.value = e;
|
||||
} else {
|
||||
checkedKeys.value = e.checked;
|
||||
}
|
||||
}
|
||||
|
||||
// 树选择事件
|
||||
function onSelect(selKeys, event) {
|
||||
console.log('select: ', selKeys, event);
|
||||
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
|
||||
setSelectedKey(selKeys[0], event.selectedNodes[0]);
|
||||
} else {
|
||||
// 这样可以防止用户取消选择
|
||||
setSelectedKey(selectedKeys.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ids 删除部门
|
||||
* @param idListRef array
|
||||
* @param confirm 是否显示确认提示框
|
||||
*/
|
||||
async function doDeleteDepart(idListRef, confirm = true) {
|
||||
const idList = unref(idListRef);
|
||||
if (idList.length > 0) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await deleteBatchDepart({ ids: idList.join(',') }, confirm);
|
||||
await loadRootTreeData();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除单个部门
|
||||
async function onDelete(data) {
|
||||
if (data) {
|
||||
onVisibleChange(false);
|
||||
doDeleteDepart([data.id], false);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除部门
|
||||
async function onDeleteBatch() {
|
||||
try {
|
||||
await doDeleteDepart(checkedKeys);
|
||||
checkedKeys.value = [];
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
function onVisibleChange(visible) {
|
||||
if (!visible) {
|
||||
visibleTreeKey.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onImportXls(d) {
|
||||
handleImportXls(d, Api.importExcelUrl, () => {
|
||||
loadRootTreeData();
|
||||
});
|
||||
}
|
||||
|
||||
function onExportXls() {
|
||||
handleExportXls('部门信息', Api.exportXlsUrl);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadRootTreeData,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="treeData.length > 0">
|
||||
<BasicTree
|
||||
ref="basicTree"
|
||||
class="depart-rule-tree"
|
||||
checkable
|
||||
:treeData="treeData"
|
||||
:checkedKeys="checkedKeys"
|
||||
:selectedKeys="selectedKeys"
|
||||
:expandedKeys="expandedKeys"
|
||||
:checkStrictly="checkStrictly"
|
||||
style="height: 500px; overflow: auto"
|
||||
@check="onCheck"
|
||||
@expand="onExpand"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #title="{ slotTitle, ruleFlag }">
|
||||
<span>{{ slotTitle }}</span>
|
||||
<Icon v-if="ruleFlag" icon="ant-design:align-left-outlined" style="margin-left: 5px; color: red" />
|
||||
</template>
|
||||
</BasicTree>
|
||||
</template>
|
||||
<a-empty v-else description="无可配置部门权限" />
|
||||
|
||||
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
|
||||
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]">
|
||||
<a-dropdown :trigger="['click']" placement="top">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="3" @click="toggleCheckALL(true)">全部勾选</a-menu-item>
|
||||
<a-menu-item key="4" @click="toggleCheckALL(false)">取消全选</a-menu-item>
|
||||
<a-menu-item key="5" @click="toggleExpandAll(true)">展开所有</a-menu-item>
|
||||
<a-menu-item key="6" @click="toggleExpandAll(false)">收起所有</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button style="float: left">
|
||||
树操作
|
||||
<Icon icon="ant-design:up-outlined" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
<DepartDataRuleDrawer @register="registerDataRuleDrawer" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch, computed, inject, ref, nextTick } from 'vue';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { BasicTree } from '/@/components/Tree/index';
|
||||
import DepartDataRuleDrawer from './DepartDataRuleDrawer.vue';
|
||||
import { queryRoleTreeList, queryDepartPermission, saveDepartPermission } from '../depart.api';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { translateTitle } from '/@/utils/common/compUtils';
|
||||
|
||||
const { prefixCls } = useDesign('j-depart-form-content');
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
});
|
||||
// 当前选中的部门ID,可能会为空,代表未选择部门
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
const basicTree = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
const treeData = ref<any[]>([]);
|
||||
const expandedKeys = ref<Array<any>>([]);
|
||||
const selectedKeys = ref<Array<any>>([]);
|
||||
const checkedKeys = ref<Array<any>>([]);
|
||||
const lastCheckedKeys = ref<Array<any>>([]);
|
||||
const checkStrictly = ref(true);
|
||||
|
||||
// 注册数据规则授权弹窗抽屉
|
||||
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
|
||||
|
||||
// onCreated
|
||||
loadData();
|
||||
watch(departId, () => loadDepartPermission(), { immediate: true });
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
let { treeList } = await queryRoleTreeList();
|
||||
//update-begin---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
treeData.value = translateTitle(treeList);
|
||||
//update-end---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
await nextTick();
|
||||
toggleExpandAll(true);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDepartPermission() {
|
||||
if (departId.value) {
|
||||
try {
|
||||
loading.value = true;
|
||||
let keys = await queryDepartPermission({ departId: departId.value });
|
||||
checkedKeys.value = keys;
|
||||
lastCheckedKeys.value = [...keys];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
await saveDepartPermission({
|
||||
departId: departId.value,
|
||||
permissionIds: checkedKeys.value.join(','),
|
||||
lastpermissionIds: lastCheckedKeys.value.join(','),
|
||||
});
|
||||
await loadData();
|
||||
await loadDepartPermission();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// tree勾选复选框事件
|
||||
function onCheck(event) {
|
||||
if (!Array.isArray(event)) {
|
||||
checkedKeys.value = event.checked;
|
||||
} else {
|
||||
checkedKeys.value = event;
|
||||
}
|
||||
}
|
||||
|
||||
// tree展开事件
|
||||
function onExpand($expandedKeys) {
|
||||
expandedKeys.value = $expandedKeys;
|
||||
}
|
||||
|
||||
// tree选中事件
|
||||
function onSelect($selectedKeys, { selectedNodes }) {
|
||||
if (selectedNodes[0]?.ruleFlag) {
|
||||
let functionId = $selectedKeys[0];
|
||||
dataRuleDrawer.openDrawer(true, { departId, functionId });
|
||||
}
|
||||
selectedKeys.value = [];
|
||||
}
|
||||
|
||||
// 切换父子关联
|
||||
async function toggleCheckStrictly(flag) {
|
||||
checkStrictly.value = flag;
|
||||
await nextTick();
|
||||
checkedKeys.value = basicTree.value.getCheckedKeys();
|
||||
}
|
||||
|
||||
// 切换展开收起
|
||||
async function toggleExpandAll(flag) {
|
||||
basicTree.value.expandAll(flag);
|
||||
await nextTick();
|
||||
expandedKeys.value = basicTree.value.getExpandedKeys();
|
||||
}
|
||||
|
||||
// 切换全选
|
||||
async function toggleCheckALL(flag) {
|
||||
basicTree.value.checkAll(flag);
|
||||
await nextTick();
|
||||
checkedKeys.value = basicTree.value.getCheckedKeys();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 【VUEN-188】解决滚动条不灵敏的问题
|
||||
.depart-rule-tree :deep(.scrollbar__bar) {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
122
jeecgboot-vue3/src/views/system/depart/depart.api.ts
Normal file
122
jeecgboot-vue3/src/views/system/depart/depart.api.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { unref } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
export enum Api {
|
||||
queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync',
|
||||
save = '/sys/sysDepart/add',
|
||||
edit = '/sys/sysDepart/edit',
|
||||
delete = '/sys/sysDepart/delete',
|
||||
deleteBatch = '/sys/sysDepart/deleteBatch',
|
||||
exportXlsUrl = '/sys/sysDepart/exportXls',
|
||||
importExcelUrl = '/sys/sysDepart/importExcel',
|
||||
|
||||
roleQueryTreeList = '/sys/role/queryTreeList',
|
||||
queryDepartPermission = '/sys/permission/queryDepartPermission',
|
||||
saveDepartPermission = '/sys/permission/saveDepartPermission',
|
||||
|
||||
dataRule = '/sys/sysDepartPermission/datarule',
|
||||
|
||||
getCurrentUserDeparts = '/sys/user/getCurrentUserDeparts',
|
||||
selectDepart = '/sys/selectDepart',
|
||||
getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo',
|
||||
doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo',
|
||||
changeDepartChargePerson = '/sys/user/changeDepartChargePerson',
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门树列表
|
||||
*/
|
||||
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params });
|
||||
|
||||
/**
|
||||
* 保存或者更新部门角色
|
||||
*/
|
||||
export const saveOrUpdateDepart = (params, isUpdate) => {
|
||||
if (isUpdate) {
|
||||
return defHttp.put({ url: Api.edit, params });
|
||||
} else {
|
||||
return defHttp.post({ url: Api.save, params });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除部门角色
|
||||
*/
|
||||
export const deleteBatchDepart = (params, confirm = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const doDelete = () => {
|
||||
resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }));
|
||||
};
|
||||
if (confirm) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '删除',
|
||||
content: '确定要删除吗?',
|
||||
onOk: () => doDelete(),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
doDelete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取权限树列表
|
||||
*/
|
||||
export const queryRoleTreeList = (params?) => defHttp.get({ url: Api.roleQueryTreeList, params });
|
||||
/**
|
||||
* 查询部门权限
|
||||
*/
|
||||
export const queryDepartPermission = (params?) => defHttp.get({ url: Api.queryDepartPermission, params });
|
||||
/**
|
||||
* 保存部门权限
|
||||
*/
|
||||
export const saveDepartPermission = (params) => defHttp.post({ url: Api.saveDepartPermission, params });
|
||||
|
||||
/**
|
||||
* 查询部门数据权限列表
|
||||
*/
|
||||
export const queryDepartDataRule = (functionId, departId, params?) => {
|
||||
let url = `${Api.dataRule}/${unref(functionId)}/${unref(departId)}`;
|
||||
return defHttp.get({ url, params });
|
||||
};
|
||||
/**
|
||||
* 保存部门数据权限
|
||||
*/
|
||||
export const saveDepartDataRule = (params) => defHttp.post({ url: Api.dataRule, params });
|
||||
/**
|
||||
* 获取登录用户部门信息
|
||||
*/
|
||||
export const getUserDeparts = (params?) => defHttp.get({ url: Api.getCurrentUserDeparts, params });
|
||||
/**
|
||||
* 切换选择部门
|
||||
*/
|
||||
export const selectDepart = (params?) => defHttp.put({ url: Api.selectDepart, params });
|
||||
|
||||
/**
|
||||
* 编辑部门前获取部门相关信息
|
||||
* @param id
|
||||
*/
|
||||
export const getUpdateDepartInfo = (id) => defHttp.get({ url: Api.getUpdateDepartInfo, params: {id} });
|
||||
|
||||
/**
|
||||
* 编辑部门
|
||||
* @param params
|
||||
*/
|
||||
export const doUpdateDepartInfo = (params) => defHttp.put({ url: Api.doUpdateDepartInfo, params });
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param id
|
||||
*/
|
||||
export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ id } }, { joinParamsToUrl: true });
|
||||
|
||||
/**
|
||||
* 设置负责人 取消负责人
|
||||
* @param params
|
||||
*/
|
||||
export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params });
|
||||
90
jeecgboot-vue3/src/views/system/depart/depart.data.ts
Normal file
90
jeecgboot-vue3/src/views/system/depart/depart.data.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
|
||||
// 部门基础表单
|
||||
export function useBasicFormSchema() {
|
||||
const basicFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'departName',
|
||||
label: '机构名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入机构/部门名称',
|
||||
},
|
||||
rules: [{ required: true, message: '机构名称不能为空' }],
|
||||
},
|
||||
{
|
||||
field: 'parentId',
|
||||
label: '上级部门',
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
treeData: [],
|
||||
placeholder: '无',
|
||||
dropdownStyle: { maxHeight: '200px', overflow: 'auto' },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orgCode',
|
||||
label: '机构编码',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入机构编码',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orgCategory',
|
||||
label: '机构类型',
|
||||
component: 'RadioButtonGroup',
|
||||
componentProps: { options: [] },
|
||||
},
|
||||
{
|
||||
field: 'departOrder',
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
componentProps: {},
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
label: '电话',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入电话',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'fax',
|
||||
label: '传真',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入传真',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'address',
|
||||
label: '地址',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'memo',
|
||||
label: '备注',
|
||||
component: 'InputTextArea',
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
return { basicFormSchema };
|
||||
}
|
||||
|
||||
// 机构类型选项
|
||||
export const orgCategoryOptions = {
|
||||
// 一级部门
|
||||
root: [{ value: '1', label: '公司' }],
|
||||
// 子级部门
|
||||
child: [
|
||||
{ value: '2', label: '部门' },
|
||||
{ value: '3', label: '岗位' },
|
||||
],
|
||||
};
|
||||
14
jeecgboot-vue3/src/views/system/depart/index.less
Normal file
14
jeecgboot-vue3/src/views/system/depart/index.less
Normal file
@ -0,0 +1,14 @@
|
||||
//noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-depart-manage';
|
||||
|
||||
.@{prefix-cls} {
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
background: @component-background;
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
|
||||
&--box {
|
||||
.ant-tabs-nav {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
jeecgboot-vue3/src/views/system/depart/index.vue
Normal file
63
jeecgboot-vue3/src/views/system/depart/index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
|
||||
</a-col>
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%;" :class="[`${prefixCls}`]">
|
||||
<a-tabs v-show="departData != null" defaultActiveKey="base-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
|
||||
<div style="padding: 20px">
|
||||
<DepartFormTab :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="部门权限" key="role-info">
|
||||
<div style="padding: 0 20px 20px">
|
||||
<DepartRuleTab :data="departData" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-show="departData == null" style="padding-top: 40px">
|
||||
<a-empty description="尚未选择部门" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="system-depart">
|
||||
import { provide, ref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DepartLeftTree from './components/DepartLeftTree.vue';
|
||||
import DepartFormTab from './components/DepartFormTab.vue';
|
||||
import DepartRuleTab from './components/DepartRuleTab.vue';
|
||||
|
||||
const { prefixCls } = useDesign('depart-manage');
|
||||
provide('prefixCls', prefixCls);
|
||||
|
||||
// 给子组件定义一个ref变量
|
||||
const leftTree = ref();
|
||||
|
||||
// 当前选中的部门信息
|
||||
const departData = ref({});
|
||||
const rootTreeData = ref<any[]>([]);
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
console.log('onTreeSelect: ', data);
|
||||
departData.value = data;
|
||||
}
|
||||
|
||||
// 左侧树rootTreeData触发
|
||||
function onRootTreeData(data) {
|
||||
rootTreeData.value = data;
|
||||
}
|
||||
|
||||
function onSuccess() {
|
||||
leftTree.value.loadRootTreeData();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<Description @register="registerDesc" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, inject, onMounted, watch } from 'vue';
|
||||
import { queryIdTree } from '../depart.user.api';
|
||||
import { useBaseInfoForm } from '../depart.user.data';
|
||||
import { Description, useDescription } from '/@/components/Description/index';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const props = defineProps({
|
||||
data: { require: true, type: Object },
|
||||
});
|
||||
const treeData = ref([]);
|
||||
const { descItems } = useBaseInfoForm(treeData);
|
||||
|
||||
const [registerDesc, { setDescProps }] = useDescription({
|
||||
data: props.data,
|
||||
schema: descItems,
|
||||
column: 1,
|
||||
labelStyle: {
|
||||
width: '180px',
|
||||
},
|
||||
});
|
||||
|
||||
function setData(data) {
|
||||
setDescProps({ data });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.data,
|
||||
() => setData(props.data),
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
// 动态查询 parentId 组件的 treeData
|
||||
queryIdTree().then((data) => (treeData.value = data));
|
||||
</script>
|
||||
@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<BasicDrawer
|
||||
title="部门角色权限配置"
|
||||
:width="650"
|
||||
:loading="loading"
|
||||
showFooter
|
||||
okText="保存并关闭"
|
||||
@ok="onSubmit(true)"
|
||||
@close="onClose"
|
||||
@register="registerDrawer"
|
||||
>
|
||||
<div>
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="treeData.length > 0">
|
||||
<BasicTree
|
||||
title="所拥有的部门权限"
|
||||
toolbar
|
||||
checkable
|
||||
:treeData="treeData"
|
||||
:checkedKeys="checkedKeys"
|
||||
:selectedKeys="selectedKeys"
|
||||
:expandedKeys="expandedKeys"
|
||||
:checkStrictly="checkStrictly"
|
||||
:clickRowToExpand="false"
|
||||
@check="onCheck"
|
||||
@expand="onExpand"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #title="{ slotTitle, ruleFlag }">
|
||||
<span>{{ slotTitle }}</span>
|
||||
<Icon v-if="ruleFlag" icon="ant-design:align-left-outlined" style="margin-left: 5px; color: red" />
|
||||
</template>
|
||||
</BasicTree>
|
||||
</template>
|
||||
<a-empty v-else description="无可配置部门权限" />
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
<template #centerFooter>
|
||||
<a-button type="primary" :loading="loading" ghost @click="onSubmit(false)">仅保存</a-button>
|
||||
</template>
|
||||
</BasicDrawer>
|
||||
<DepartRoleDataRuleDrawer @register="registerDataRuleDrawer" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { BasicTree } from '/@/components/Tree/index';
|
||||
import { BasicDrawer, useDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
import DepartRoleDataRuleDrawer from './DepartRoleDataRuleDrawer.vue';
|
||||
import { queryTreeListForDeptRole, queryDeptRolePermission, saveDeptRolePermission } from '../depart.user.api';
|
||||
import { translateTitle } from "@/utils/common/compUtils";
|
||||
|
||||
defineEmits(['register']);
|
||||
const { createMessage } = useMessage();
|
||||
const loading = ref(false);
|
||||
const departId = ref('');
|
||||
const roleId = ref('');
|
||||
const treeData = ref<Array<any>>([]);
|
||||
const checkedKeys = ref<Array<any>>([]);
|
||||
const lastCheckedKeys = ref<Array<any>>([]);
|
||||
const expandedKeys = ref<Array<any>>([]);
|
||||
const selectedKeys = ref<Array<any>>([]);
|
||||
const allTreeKeys = ref<Array<any>>([]);
|
||||
const checkStrictly = ref(true);
|
||||
|
||||
// 注册抽屉组件
|
||||
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
|
||||
roleId.value = data.record.id;
|
||||
departId.value = data.record.departId;
|
||||
loadData();
|
||||
});
|
||||
// 注册数据规则授权弹窗抽屉
|
||||
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
// 用户角色授权功能,查询菜单权限树
|
||||
const { ids, treeList } = await queryTreeListForDeptRole({ departId: departId.value });
|
||||
if (ids.length > 0) {
|
||||
allTreeKeys.value = ids;
|
||||
expandedKeys.value = ids;
|
||||
//update-begin---author:wangshuai---date:2024-04-08---for:【issues/1169】我的部门功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
treeData.value = translateTitle(treeList);
|
||||
//update-end---author:wangshuai---date:2024-04-08---for:【issues/1169】我的部门功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
// 查询角色授权
|
||||
checkedKeys.value = await queryDeptRolePermission({ roleId: roleId.value });
|
||||
lastCheckedKeys.value = [checkedKeys.value];
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置页面
|
||||
function reset() {
|
||||
treeData.value = [];
|
||||
expandedKeys.value = [];
|
||||
checkedKeys.value = [];
|
||||
lastCheckedKeys.value = [];
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// tree勾选复选框事件
|
||||
function onCheck(event) {
|
||||
if (checkStrictly.value) {
|
||||
checkedKeys.value = event.checked;
|
||||
} else {
|
||||
checkedKeys.value = event;
|
||||
}
|
||||
}
|
||||
|
||||
// tree展开事件
|
||||
function onExpand($expandedKeys) {
|
||||
expandedKeys.value = $expandedKeys;
|
||||
}
|
||||
|
||||
// tree选中事件
|
||||
function onSelect($selectedKeys, { selectedNodes }) {
|
||||
if (selectedNodes[0]?.ruleFlag) {
|
||||
let functionId = $selectedKeys[0];
|
||||
dataRuleDrawer.openDrawer(true, { roleId, departId, functionId });
|
||||
}
|
||||
selectedKeys.value = [];
|
||||
}
|
||||
|
||||
function doClose() {
|
||||
reset();
|
||||
closeDrawer();
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
reset();
|
||||
}
|
||||
|
||||
async function onSubmit(exit) {
|
||||
try {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
roleId: roleId.value,
|
||||
permissionIds: checkedKeys.value.join(','),
|
||||
lastpermissionIds: lastCheckedKeys.value.join(','),
|
||||
};
|
||||
await saveDeptRolePermission(params);
|
||||
if (exit) {
|
||||
doClose();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
if (!exit) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<BasicDrawer title="数据规则/按钮权限配置" :width="365" @close="onClose" @register="registerDrawer">
|
||||
<a-spin :spinning="loading">
|
||||
<a-tabs defaultActiveKey="1">
|
||||
<a-tab-pane tab="数据规则" key="1">
|
||||
<a-checkbox-group v-model:value="dataRuleChecked" v-if="dataRuleList.length > 0">
|
||||
<a-row>
|
||||
<a-col :span="24" v-for="(item, index) in dataRuleList" :key="'dr' + index">
|
||||
<a-checkbox :value="item.id">{{ item.ruleName }}</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="saveDataRuleForRole">
|
||||
<span>点击保存</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<a-empty v-else description="无配置信息" />
|
||||
</a-tab-pane>
|
||||
<!--<a-tab-pane tab="按钮权限" key="2">敬请期待!!!</a-tab-pane>-->
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
|
||||
import { queryDepartRoleDataRule, saveDepartRoleDataRule } from '../depart.user.api';
|
||||
|
||||
defineEmits(['register']);
|
||||
const loading = ref<boolean>(false);
|
||||
const departId = ref('');
|
||||
const functionId = ref('');
|
||||
const roleId = ref('');
|
||||
const dataRuleList = ref<Array<any>>([]);
|
||||
const dataRuleChecked = ref<Array<any>>([]);
|
||||
|
||||
// 注册抽屉组件
|
||||
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
|
||||
roleId.value = unref(data.roleId);
|
||||
departId.value = unref(data.departId);
|
||||
functionId.value = unref(data.functionId);
|
||||
loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { datarule, drChecked } = await queryDepartRoleDataRule(functionId, departId, roleId);
|
||||
dataRuleList.value = datarule;
|
||||
if (drChecked) {
|
||||
dataRuleChecked.value = drChecked.split(',');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveDataRuleForRole() {
|
||||
let params = {
|
||||
permissionId: functionId.value,
|
||||
roleId: roleId.value,
|
||||
dataRuleIds: dataRuleChecked.value.join(','),
|
||||
};
|
||||
saveDepartRoleDataRule(params);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
doReset();
|
||||
}
|
||||
|
||||
function doReset() {
|
||||
functionId.value = '';
|
||||
roleId.value = '';
|
||||
dataRuleList.value = [];
|
||||
dataRuleChecked.value = [];
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="addDepartRole">添加部门角色</a-button>
|
||||
<template v-if="selectedRowKeys.length > 0">
|
||||
<a-divider type="vertical" />
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="onDeleteDepartRoleBatch">
|
||||
<icon icon="ant-design:delete-outlined" />
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作 </span>
|
||||
<icon icon="akar-icons:chevron-down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 插槽:行内操作按钮 -->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 添加部门弹窗 -->
|
||||
<DepartRoleModal :departId="departId" @register="registerFormModal" @success="onFormModalSuccess" />
|
||||
<DepartRoleAuthDrawer @register="registerAuthDrawer" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, ref, unref, watch, computed, onMounted } from 'vue';
|
||||
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
import DepartRoleModal from './DepartRoleModal.vue';
|
||||
import DepartRoleAuthDrawer from './DepartRoleAuthDrawer.vue';
|
||||
import { deleteBatchDepartRole, departRoleList } from '../depart.user.api';
|
||||
import { departRoleColumns, departRoleSearchFormSchema } from '../depart.user.data';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const props = defineProps({
|
||||
data: { require: true, type: Object },
|
||||
});
|
||||
defineEmits(['register']);
|
||||
// 当前选中的部门ID,可能会为空,代表未选择部门
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 12, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
// 列表页面公共参数、方法
|
||||
const { tableContext, createMessage } = useListPage({
|
||||
tableProps: {
|
||||
api: departRoleList,
|
||||
columns: departRoleColumns,
|
||||
canResize: false,
|
||||
formConfig: {
|
||||
labelWidth: 100,
|
||||
schemas: departRoleSearchFormSchema,
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'left',
|
||||
labelCol: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 9,
|
||||
xl: 7,
|
||||
xxl: 6,
|
||||
},
|
||||
wrapperCol: {},
|
||||
// 操作按钮配置
|
||||
actionColOptions: {
|
||||
...adaptiveColProps,
|
||||
style: { textAlign: 'left' },
|
||||
},
|
||||
},
|
||||
// 【issues/1064】列设置的 cacheKey
|
||||
tableSetting: { cacheKey: 'depart_user_departInfo' },
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
params.deptId = departId.value;
|
||||
},
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-53】未选择部门的情况下,部门角色全查出来了
|
||||
immediate: !!departId.value,
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-53】未选择部门的情况下,部门角色全查出来了
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setProps, setLoading, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
// 注册Form弹窗
|
||||
const [registerFormModal, formModal] = useModal();
|
||||
// 注册授权弹窗抽屉
|
||||
const [registerAuthDrawer, authDrawer] = useDrawer();
|
||||
|
||||
// 监听 data 更改,重新加载数据
|
||||
watch(
|
||||
() => props.data,
|
||||
() => reload()
|
||||
);
|
||||
onMounted(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-53】未选择部门的情况下,部门角色全查出来了
|
||||
// reload();
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-53】未选择部门的情况下,部门角色全查出来了
|
||||
});
|
||||
|
||||
// 清空选择的行
|
||||
function clearSelection() {
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
|
||||
// 添加部门角色
|
||||
function addDepartRole() {
|
||||
formModal.openModal(true, {
|
||||
isUpdate: false,
|
||||
record: {},
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑部门角色
|
||||
function editDepartRole(record) {
|
||||
formModal.openModal(true, {
|
||||
isUpdate: true,
|
||||
record: record,
|
||||
});
|
||||
}
|
||||
|
||||
// 授权部门角色
|
||||
function permissionDepartRole(record) {
|
||||
authDrawer.openDrawer(true, { record });
|
||||
}
|
||||
|
||||
// 批量删除部门角色
|
||||
async function deleteDepartRole(idList, confirm) {
|
||||
if (!departId.value) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
} else {
|
||||
setLoading(true);
|
||||
let ids = unref(idList).join(',');
|
||||
try {
|
||||
await deleteBatchDepartRole({ ids }, confirm);
|
||||
return reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// 批量删除部门角色事件
|
||||
async function onDeleteDepartRoleBatch() {
|
||||
try {
|
||||
await deleteDepartRole(selectedRowKeys, true);
|
||||
// 批量删除成功后清空选择
|
||||
clearSelection();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 表单弹窗成功后的回调
|
||||
function onFormModalSuccess({ isUpdate, values }) {
|
||||
isUpdate ? updateTableDataRecord(values.id, values) : reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [{ label: '编辑', onClick: editDepartRole.bind(null, record) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '授权', onClick: permissionDepartRole.bind(null, record) },
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认要删除吗?',
|
||||
confirm: deleteDepartRole.bind(null, [record.id], false),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, unref } from 'vue';
|
||||
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
// noinspection ES6UnusedImports
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
|
||||
import { saveOrUpdateDepartRole } from '../depart.user.api';
|
||||
import { departRoleModalFormSchema } from '../depart.user.data';
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({
|
||||
// 当前部门ID
|
||||
departId: { require: true, type: String },
|
||||
});
|
||||
const prefixCls = inject('prefixCls');
|
||||
// 当前是否是更新模式
|
||||
const isUpdate = ref<boolean>(true);
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: departRoleModalFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
// 注册弹窗
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
isUpdate.value = unref(data?.isUpdate);
|
||||
// 无论新增还是编辑,都可以设置表单值
|
||||
let record = unref(data?.record);
|
||||
if (typeof record === 'object') {
|
||||
model.value = record;
|
||||
await setFieldsValue({ ...record });
|
||||
}
|
||||
});
|
||||
|
||||
//提交事件
|
||||
async function handleOk() {
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
let values = await validate();
|
||||
values.departId = unref(props.departId);
|
||||
//提交表单
|
||||
await saveOrUpdateDepartRole(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success', { isUpdate: unref(isUpdate), values });
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<BasicDrawer title="部门角色分配" :width="365" @close="onClose" @register="registerDrawer">
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="desformList.length > 0">
|
||||
<a-checkbox-group v-model:value="designNameValue">
|
||||
<a-row>
|
||||
<a-col :span="24" v-for="item of desformList">
|
||||
<a-checkbox :value="item.id">{{ item.roleName }}</a-checkbox>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="onSubmit">
|
||||
<span>点击保存</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty v-else description="无配置信息" />
|
||||
</a-spin>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
|
||||
import { queryDepartRoleByUserId, queryDepartRoleUserList, saveDepartRoleUser } from '../depart.user.api';
|
||||
|
||||
defineEmits(['register']);
|
||||
const loading = ref<boolean>(false);
|
||||
const userId = ref('');
|
||||
const departId = ref('');
|
||||
const oldRoleId = ref('');
|
||||
const desformList = ref<Array<any>>([]);
|
||||
const designNameValue = ref<Array<any>>([]);
|
||||
|
||||
// 注册抽屉组件
|
||||
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
|
||||
userId.value = unref(data.userId);
|
||||
departId.value = unref(data.departId);
|
||||
loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
departId: departId.value,
|
||||
userId: userId.value,
|
||||
};
|
||||
// 查询 DepartRole
|
||||
const [$desformList, $departRoleList] = await Promise.all([queryDepartRoleUserList(params), queryDepartRoleByUserId(params)]);
|
||||
desformList.value = $desformList;
|
||||
designNameValue.value = $departRoleList.map((item) => item.droleId);
|
||||
oldRoleId.value = designNameValue.value.join(',');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
loading.value = true;
|
||||
await saveDepartRoleUser({
|
||||
userId: userId.value,
|
||||
newRoleId: designNameValue.value.join(','),
|
||||
oldRoleId: oldRoleId.value,
|
||||
});
|
||||
doClose();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
doReset();
|
||||
}
|
||||
|
||||
function doClose() {
|
||||
doReset();
|
||||
closeDrawer();
|
||||
}
|
||||
|
||||
function doReset() {
|
||||
userId.value = '';
|
||||
departId.value = '';
|
||||
oldRoleId.value = '';
|
||||
desformList.value = [];
|
||||
designNameValue.value = [];
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="bg-white m-4 mr-0 overflow-hidden">
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="userIdentity === '2'">
|
||||
<!--组织机构树-->
|
||||
<BasicTree
|
||||
v-if="!treeReloading"
|
||||
title="部门列表"
|
||||
toolbar
|
||||
search
|
||||
showLine
|
||||
:checkStrictly="true"
|
||||
:clickRowToExpand="false"
|
||||
:treeData="treeData"
|
||||
:selectedKeys="selectedKeys"
|
||||
:expandedKeys="expandedKeys"
|
||||
:autoExpandParent="autoExpandParent"
|
||||
@select="onSelect"
|
||||
@expand="onExpand"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
<a-empty v-else description="普通员工无此权限" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { BasicTree } from '/@/components/Tree';
|
||||
import { queryMyDepartTreeList, searchByKeywords } from '../depart.user.api';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select']);
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
let loading = ref<boolean>(false);
|
||||
// 部门树列表数据
|
||||
let treeData = ref<any[]>([]);
|
||||
// 当前展开的项
|
||||
let expandedKeys = ref<any[]>([]);
|
||||
// 当前选中的项
|
||||
let selectedKeys = ref<any[]>([]);
|
||||
// 是否自动展开父级
|
||||
let autoExpandParent = ref<boolean>(true);
|
||||
// 用户身份
|
||||
let userIdentity = ref<string>('2');
|
||||
// 树组件重新加载
|
||||
let treeReloading = ref<boolean>(false);
|
||||
|
||||
// 加载部门信息
|
||||
function loadDepartTreeData() {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
queryMyDepartTreeList()
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (Array.isArray(res.result)) {
|
||||
treeData.value = res.result;
|
||||
userIdentity.value = res.message;
|
||||
autoExpandParentNode();
|
||||
}
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
}
|
||||
|
||||
loadDepartTreeData();
|
||||
|
||||
// 自动展开父节点,只展开一级
|
||||
function autoExpandParentNode() {
|
||||
let keys: Array<any> = [];
|
||||
treeData.value.forEach((item, index) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
keys.push(item.key);
|
||||
}
|
||||
if (index === 0) {
|
||||
// 默认选中第一个
|
||||
setSelectedKey(item.id, item);
|
||||
}
|
||||
});
|
||||
if (keys.length > 0) {
|
||||
reloadTree();
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载树组件,防止无法默认展开数据
|
||||
async function reloadTree() {
|
||||
await nextTick();
|
||||
treeReloading.value = true;
|
||||
await nextTick();
|
||||
treeReloading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中的行
|
||||
*/
|
||||
function setSelectedKey(key: string, data?: object) {
|
||||
selectedKeys.value = [key];
|
||||
if (data) {
|
||||
emit('select', data);
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索事件
|
||||
function onSearch(value: string) {
|
||||
if (value) {
|
||||
loading.value = true;
|
||||
searchByKeywords({ keyWord: value, myDeptSearch: '1' })
|
||||
.then((result) => {
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
} else {
|
||||
createMessage.warning('未查询到部门信息');
|
||||
treeData.value = [];
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
} else {
|
||||
loadDepartTreeData();
|
||||
}
|
||||
}
|
||||
|
||||
// 树选择事件
|
||||
function onSelect(selKeys, event) {
|
||||
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
|
||||
setSelectedKey(selKeys[0], event.selectedNodes[0]);
|
||||
} else {
|
||||
// 这样可以防止用户取消选择
|
||||
setSelectedKey(selectedKeys.value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// 树展开事件
|
||||
function onExpand(keys) {
|
||||
expandedKeys.value = keys;
|
||||
autoExpandParent.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/*升级antd3后,查询框与树贴的太近,样式优化*/
|
||||
:deep(.jeecg-tree-header) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser">添加已有用户</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser">新建用户</a-button>
|
||||
<template v-if="selectedRowKeys.length > 0">
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="onUnlinkDepartUserBatch">
|
||||
<icon icon="bx:bx-unlink" />
|
||||
<span>取消关联</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作 </span>
|
||||
<icon icon="akar-icons:chevron-down" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 插槽:行内操作按钮 -->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<UserDrawer @register="registerDrawer" @success="onUserDrawerSuccess" />
|
||||
<DepartRoleUserAuthDrawer @register="registerUserAuthDrawer" />
|
||||
<UserSelectModal rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, unref, watch } from 'vue';
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
import UserDrawer from '/@/views/system/user/UserDrawer.vue';
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
|
||||
import DepartRoleUserAuthDrawer from './DepartRoleUserAuthDrawer.vue';
|
||||
import { departUserList, linkDepartUserBatch, unlinkDepartUserBatch } from '../depart.user.api';
|
||||
import { userInfoColumns, userInfoSearchFormSchema } from '../depart.user.data';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const props = defineProps({
|
||||
data: { require: true, type: Object },
|
||||
});
|
||||
// 当前选中的部门ID,可能会为空,代表未选择部门
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 12, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
// 列表页面公共参数、方法
|
||||
const { tableContext, createMessage } = useListPage({
|
||||
tableProps: {
|
||||
api: departUserList,
|
||||
columns: userInfoColumns,
|
||||
canResize: false,
|
||||
formConfig: {
|
||||
schemas: userInfoSearchFormSchema,
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'left',
|
||||
labelCol: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 9,
|
||||
xl: 7,
|
||||
xxl: 5,
|
||||
},
|
||||
wrapperCol: {},
|
||||
// 操作按钮配置
|
||||
actionColOptions: {
|
||||
...adaptiveColProps,
|
||||
style: { textAlign: 'left' },
|
||||
},
|
||||
},
|
||||
// 【issues/1064】列设置的 cacheKey
|
||||
tableSetting: { cacheKey: 'depart_user_userInfo' },
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
params.depId = departId.value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setProps, setLoading, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => reload()
|
||||
);
|
||||
//注册drawer
|
||||
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
|
||||
const [registerUserAuthDrawer, userAuthDrawer] = useDrawer();
|
||||
// 注册用户选择 modal
|
||||
const [registerSelUserModal, selUserModal] = useModal();
|
||||
|
||||
// 清空选择的行
|
||||
function clearSelection() {
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
|
||||
// 查看部门角色
|
||||
function showDepartRole(record) {
|
||||
userAuthDrawer.openDrawer(true, { userId: record.id, departId });
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
function createUser() {
|
||||
if (!departId.value) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
} else {
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
departDisabled: true,
|
||||
// 初始化负责部门
|
||||
nextDepartOptions: { value: props.data?.key, label: props.data?.title },
|
||||
record: {
|
||||
activitiSync: 1,
|
||||
userIdentity: 1,
|
||||
selecteddeparts: departId.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 查看用户详情
|
||||
function showUserDetail(record) {
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
departDisabled: true,
|
||||
showFooter: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑用户信息
|
||||
function editUserInfo(record) {
|
||||
openDrawer(true, { isUpdate: true, record, departDisabled: true });
|
||||
}
|
||||
|
||||
// 选择添加已有用户
|
||||
function selectAddUser() {
|
||||
selUserModal.openModal();
|
||||
}
|
||||
|
||||
// 批量取消关联部门和用户之间的关系
|
||||
async function unlinkDepartUser(idList, confirm) {
|
||||
if (!departId.value) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
} else {
|
||||
setLoading(true);
|
||||
let userIds = unref(idList).join(',');
|
||||
try {
|
||||
await unlinkDepartUserBatch({ depId: departId.value, userIds }, confirm);
|
||||
return reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// 批量取消关联事件
|
||||
async function onUnlinkDepartUserBatch() {
|
||||
try {
|
||||
await unlinkDepartUser(selectedRowKeys, true);
|
||||
// 批量删除成功后清空选择
|
||||
clearSelection();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 选择用户成功
|
||||
async function onSelectUserOk(options, userIdList) {
|
||||
if (userIdList.length > 0) {
|
||||
try {
|
||||
setLoading(true);
|
||||
await linkDepartUserBatch(departId.value, userIdList);
|
||||
reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户抽屉表单成功回调
|
||||
*/
|
||||
function onUserDrawerSuccess({ isUpdate, values }) {
|
||||
isUpdate ? updateTableDataRecord(values.id, values) : reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [{ label: '编辑', onClick: editUserInfo.bind(null, record) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '部门角色', onClick: showDepartRole.bind(null, record) },
|
||||
{ label: '用户详情', onClick: showUserDetail.bind(null, record) },
|
||||
{
|
||||
label: '取消关联',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认取消关联吗?',
|
||||
confirm: unlinkDepartUser.bind(null, [record.id], false),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
159
jeecgboot-vue3/src/views/system/departUser/depart.user.api.ts
Normal file
159
jeecgboot-vue3/src/views/system/departUser/depart.user.api.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { unref } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
treeList = '/sys/sysDepart/queryMyDeptTreeList',
|
||||
queryIdTree = '/sys/sysDepart/queryIdTree',
|
||||
searchBy = '/sys/sysDepart/searchBy',
|
||||
}
|
||||
|
||||
// 部门用户API
|
||||
enum DepartUserApi {
|
||||
list = '/sys/user/departUserList',
|
||||
link = '/sys/user/editSysDepartWithUser',
|
||||
unlink = '/sys/user/deleteUserInDepartBatch',
|
||||
}
|
||||
|
||||
// 部门角色API
|
||||
enum DepartRoleApi {
|
||||
list = '/sys/sysDepartRole/list',
|
||||
deleteBatch = '/sys/sysDepartRole/deleteBatch',
|
||||
save = '/sys/sysDepartRole/add',
|
||||
edit = '/sys/sysDepartRole/edit',
|
||||
queryTreeListForDeptRole = '/sys/sysDepartPermission/queryTreeListForDeptRole',
|
||||
queryDeptRolePermission = '/sys/sysDepartPermission/queryDeptRolePermission',
|
||||
saveDeptRolePermission = '/sys/sysDepartPermission/saveDeptRolePermission',
|
||||
dataRule = '/sys/sysDepartRole/datarule',
|
||||
getDeptRoleList = '/sys/sysDepartRole/getDeptRoleList',
|
||||
getDeptRoleByUserId = '/sys/sysDepartRole/getDeptRoleByUserId',
|
||||
saveDeptRoleUser = '/sys/sysDepartRole/deptRoleUserAdd',
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门树列表
|
||||
*/
|
||||
export const queryMyDepartTreeList = (params?) => defHttp.get({ url: Api.treeList, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 查询数据,以树结构形式加载所有部门的名称
|
||||
*/
|
||||
export const queryIdTree = (params?) => defHttp.get({ url: Api.queryIdTree, params });
|
||||
|
||||
/**
|
||||
* 根据关键字搜索部门
|
||||
*/
|
||||
export const searchByKeywords = (params) => defHttp.get({ url: Api.searchBy, params });
|
||||
|
||||
/**
|
||||
* 查询部门下的用户信息
|
||||
*/
|
||||
export const departUserList = (params) => defHttp.get({ url: DepartUserApi.list, params });
|
||||
|
||||
/**
|
||||
* 批量添加部门和用户的关联关系
|
||||
*
|
||||
* @param departId 部门ID
|
||||
* @param userIdList 用户ID列表
|
||||
*/
|
||||
export const linkDepartUserBatch = (departId: string, userIdList: string[]) =>
|
||||
defHttp.post({ url: DepartUserApi.link, params: { depId: departId, userIdList } });
|
||||
|
||||
/**
|
||||
* 批量取消部门和用户的关联关系
|
||||
*/
|
||||
export const unlinkDepartUserBatch = (params, confirm = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const doDelete = () => {
|
||||
resolve(defHttp.delete({ url: DepartUserApi.unlink, params }, { joinParamsToUrl: true }));
|
||||
};
|
||||
if (confirm) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '取消关联',
|
||||
content: '确定要取消关联吗?',
|
||||
onOk: () => doDelete(),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
doDelete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询部门角色信息
|
||||
*/
|
||||
export const departRoleList = (params) => defHttp.get({ url: DepartRoleApi.list, params });
|
||||
|
||||
/**
|
||||
* 保存或者更新部门角色
|
||||
*/
|
||||
export const saveOrUpdateDepartRole = (params, isUpdate) => {
|
||||
if (isUpdate) {
|
||||
return defHttp.put({ url: DepartRoleApi.edit, params });
|
||||
} else {
|
||||
return defHttp.post({ url: DepartRoleApi.save, params });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除部门角色
|
||||
*/
|
||||
export const deleteBatchDepartRole = (params, confirm = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const doDelete = () => {
|
||||
resolve(defHttp.delete({ url: DepartRoleApi.deleteBatch, params }, { joinParamsToUrl: true }));
|
||||
};
|
||||
if (confirm) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '删除',
|
||||
content: '确定要删除吗?',
|
||||
onOk: () => doDelete(),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
doDelete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户角色授权功能,查询菜单权限树
|
||||
*/
|
||||
export const queryTreeListForDeptRole = (params) => defHttp.get({ url: DepartRoleApi.queryTreeListForDeptRole, params });
|
||||
/**
|
||||
* 查询角色授权
|
||||
*/
|
||||
export const queryDeptRolePermission = (params) => defHttp.get({ url: DepartRoleApi.queryDeptRolePermission, params });
|
||||
/**
|
||||
* 保存角色授权
|
||||
*/
|
||||
export const saveDeptRolePermission = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRolePermission, params });
|
||||
|
||||
/**
|
||||
* 查询部门角色数据权限列表
|
||||
*/
|
||||
export const queryDepartRoleDataRule = (functionId, departId, roleId, params?) => {
|
||||
let url = `${DepartRoleApi.dataRule}/${unref(functionId)}/${unref(departId)}/${unref(roleId)}`;
|
||||
return defHttp.get({ url, params });
|
||||
};
|
||||
/**
|
||||
* 保存部门角色数据权限
|
||||
*/
|
||||
export const saveDepartRoleDataRule = (params) => defHttp.post({ url: DepartRoleApi.dataRule, params });
|
||||
/**
|
||||
* 查询部门角色用户授权
|
||||
*/
|
||||
export const queryDepartRoleUserList = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleList, params });
|
||||
/**
|
||||
* 根据 userId 查询部门角色用户授权
|
||||
*/
|
||||
export const queryDepartRoleByUserId = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleByUserId, params });
|
||||
/**
|
||||
* 保存部门角色用户授权
|
||||
*/
|
||||
export const saveDepartRoleUser = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRoleUser, params });
|
||||
195
jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts
Normal file
195
jeecgboot-vue3/src/views/system/departUser/depart.user.data.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import { Ref } from 'vue';
|
||||
import { duplicateCheckDelay } from '/@/views/system/user/user.api';
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { DescItem } from '/@/components/Description';
|
||||
import { findTree } from '/@/utils/common/compUtils';
|
||||
|
||||
// 用户信息 columns
|
||||
export const userInfoColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'username',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '用户名称',
|
||||
dataIndex: 'realname',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'orgCode',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex_dictText',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '电话',
|
||||
dataIndex: 'phone',
|
||||
width: 120,
|
||||
},
|
||||
];
|
||||
|
||||
// 用户信息查询条件表单
|
||||
export const userInfoSearchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'username',
|
||||
label: '用户账号',
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
|
||||
// 部门角色 columns
|
||||
export const departRoleColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '部门角色名称',
|
||||
dataIndex: 'roleName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门角色编码',
|
||||
dataIndex: 'roleCode',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departId_dictText',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
// 部门角色查询条件表单
|
||||
export const departRoleSearchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'roleName',
|
||||
label: '部门角色名称',
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
|
||||
// 部门角色弹窗form表单
|
||||
export const departRoleModalFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: 'id',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'roleName',
|
||||
label: '部门角色名称',
|
||||
component: 'Input',
|
||||
rules: [
|
||||
{ required: true, message: '部门角色名称不能为空!' },
|
||||
{ min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'roleCode',
|
||||
label: '部门角色编码',
|
||||
component: 'Input',
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
dynamicRules: ({ model }) => {
|
||||
return [
|
||||
{ required: true, message: '部门角色编码不能为空!' },
|
||||
{ min: 0, max: 64, message: '长度不能超过 64 个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (/[\u4E00-\u9FA5]/g.test(value)) {
|
||||
return Promise.reject('部门角色编码不可输入汉字!');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let params = {
|
||||
tableName: 'sys_depart_role',
|
||||
fieldName: 'role_code',
|
||||
fieldVal: value,
|
||||
dataId: model.id,
|
||||
};
|
||||
duplicateCheckDelay(params)
|
||||
.then((res) => {
|
||||
res.success ? resolve() : reject(res.message || '校验失败');
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || '验证失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
label: '描述',
|
||||
component: 'Input',
|
||||
rules: [{ min: 0, max: 126, message: '长度不能超过 126 个字符', trigger: 'blur' }],
|
||||
},
|
||||
];
|
||||
|
||||
// 基本信息form
|
||||
export function useBaseInfoForm(treeData: Ref<any[]>) {
|
||||
const descItems: DescItem[] = [
|
||||
{
|
||||
field: 'departName',
|
||||
label: '机构名称',
|
||||
},
|
||||
{
|
||||
field: 'parentId',
|
||||
label: '上级部门',
|
||||
render(val) {
|
||||
if (val) {
|
||||
let data = findTree(treeData.value, (item) => item.key == val);
|
||||
return data?.title ?? val;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orgCode',
|
||||
label: '机构编码',
|
||||
},
|
||||
{
|
||||
field: 'orgCategory',
|
||||
label: '机构类型',
|
||||
render(val) {
|
||||
if (val === '1') {
|
||||
return '公司';
|
||||
} else if (val === '2') {
|
||||
return '部门';
|
||||
} else if (val === '3') {
|
||||
return '岗位';
|
||||
}
|
||||
return val;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'departOrder',
|
||||
label: '排序',
|
||||
},
|
||||
|
||||
{
|
||||
field: 'mobile',
|
||||
label: '手机号',
|
||||
},
|
||||
{
|
||||
field: 'address',
|
||||
label: '地址',
|
||||
},
|
||||
{
|
||||
field: 'memo',
|
||||
label: '备注',
|
||||
},
|
||||
];
|
||||
|
||||
return { descItems };
|
||||
}
|
||||
48
jeecgboot-vue3/src/views/system/departUser/index.less
Normal file
48
jeecgboot-vue3/src/views/system/departUser/index.less
Normal file
@ -0,0 +1,48 @@
|
||||
@prefix-cls: ~'@{namespace}-depart-user';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&--tree-search {
|
||||
width: 100%;
|
||||
margin: 10px 0 20px;
|
||||
}
|
||||
|
||||
&--base-info-form {
|
||||
@media (min-width: 576px) {
|
||||
.no-border {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ant-select.ant-select-disabled {
|
||||
.ant-select-selector {
|
||||
border: 0;
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-select-selector,
|
||||
.ant-select-selection-item {
|
||||
cursor: text !important;
|
||||
user-select: initial !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-search,
|
||||
.ant-select-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 夜间模式样式兼容
|
||||
[data-theme='dark'] .@{prefix-cls} {
|
||||
&--base-info-form {
|
||||
.ant-select.ant-select-disabled {
|
||||
.ant-select-selector {
|
||||
color: #c9d1d9;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
jeecgboot-vue3/src/views/system/departUser/index.vue
Normal file
49
jeecgboot-vue3/src/views/system/departUser/index.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" :gutter="10">
|
||||
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex: 1">
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<DepartTree @select="onTreeSelect" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xl="18" :lg="16" :md="14" :sm="24" style="flex: 1">
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<a-tabs defaultActiveKey="user-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender>
|
||||
<DepartBaseInfoTab :data="departData" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="用户信息" key="user-info">
|
||||
<DepartUserInfoTab :data="departData" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="部门角色" key="role-info">
|
||||
<DepartRoleInfoTab :data="departData" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="system-depart-user">
|
||||
import { provide, ref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
import DepartTree from './components/DepartTree.vue';
|
||||
import DepartBaseInfoTab from './components/DepartBaseInfoTab.vue';
|
||||
import DepartUserInfoTab from './components/DepartUserInfoTab.vue';
|
||||
import DepartRoleInfoTab from './components/DepartRoleInfoTab.vue';
|
||||
|
||||
const { prefixCls } = useDesign('depart-user');
|
||||
provide('prefixCls', prefixCls);
|
||||
|
||||
// 当前选中的部门信息
|
||||
let departData = ref({});
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
departData.value = data;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
||||
140
jeecgboot-vue3/src/views/system/dict/components/DictItemList.vue
Normal file
140
jeecgboot-vue3/src/views/system/dict/components/DictItemList.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" title="字典列表" width="800px">
|
||||
<BasicTable @register="registerTable" :rowClassName="getRowClassName">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" @click="handleCreate"> 新增</a-button>
|
||||
</template>
|
||||
<template v-slot:bodyCell="{column, record, index}">
|
||||
<template v-if="column.dataIndex ==='action'">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</BasicTable>
|
||||
</BasicDrawer>
|
||||
<DictItemModal @register="registerModal" @success="reload" :dictId="dictId" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/src/components/Drawer';
|
||||
import { BasicTable, useTable, TableAction } from '/src/components/Table';
|
||||
import { useModal } from '/src/components/Modal';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DictItemModal from './DictItemModal.vue';
|
||||
import { dictItemColumns, dictItemSearchFormSchema } from '../dict.data';
|
||||
import { itemList, deleteItem } from '../dict.api';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
|
||||
const { prefixCls } = useDesign('row-invalid');
|
||||
const dictId = ref('');
|
||||
//字典配置model
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerDrawer] = useDrawerInner(async (data) => {
|
||||
dictId.value = data.id;
|
||||
setProps({ searchInfo: { dictId: unref(dictId) } });
|
||||
reload();
|
||||
});
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 12, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
const [registerTable, { reload, setProps }] = useTable({
|
||||
//需要配置rowKey,否则会有警告
|
||||
rowKey:'dictId',
|
||||
api: itemList,
|
||||
columns: dictItemColumns,
|
||||
formConfig: {
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'right',
|
||||
labelCol: {
|
||||
offset: 1,
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 9,
|
||||
xl: 7,
|
||||
xxl: 4,
|
||||
},
|
||||
wrapperCol: {},
|
||||
schemas: dictItemSearchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
actionColOptions: {
|
||||
span: 8
|
||||
}
|
||||
},
|
||||
striped: true,
|
||||
useSearchForm: true,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
canResize: false,
|
||||
immediate: false,
|
||||
actionColumn: {
|
||||
width: 100,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
//slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleCreate() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteItem({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
function getRowClassName(record) {
|
||||
return record.status == 0 ? prefixCls : '';
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@prefix-cls: ~'@{namespace}-row-invalid';
|
||||
|
||||
:deep(.@{prefix-cls}) {
|
||||
background: #f4f4f4;
|
||||
color: #bababa;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" width="800px">
|
||||
<!-- update-begin---author:wangshuai---date:2023-10-23---for:【QQYUN-6804】后台模式字典没有颜色配置--- -->
|
||||
<BasicForm @register="registerForm" >
|
||||
<template #itemColor="{ model, field }">
|
||||
<div class="item-tool">
|
||||
<div
|
||||
v-for="(item,index) in Colors"
|
||||
:style="{ color: item[0] }"
|
||||
:class="model.itemColor===item[0]?'item-active':''"
|
||||
class="item-color"
|
||||
@click="itemColorClick(item)">
|
||||
<div class="item-color-border"></div>
|
||||
<div class="item-back" :style="{ background: item[0] }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<!-- update-end---author:wangshuai---date:2023-10-23---for:【QQYUN-6804】后台模式字典没有颜色配置--- -->
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, ref, computed, unref, reactive } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { BasicForm, useForm } from '/src/components/Form';
|
||||
import { itemFormSchema } from '../dict.data';
|
||||
import { saveOrUpdateDictItem } from '../dict.api';
|
||||
import { Colors } from '/@/utils/dict/DictColors.js'
|
||||
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({ dictId: String });
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: itemFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
mergeDynamicData: props,
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 4 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 18 },
|
||||
},
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
|
||||
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
values.dictId = props.dictId;
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateDictItem(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典颜色点击事件
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
* @param model
|
||||
*/
|
||||
function itemColorClick(item) {
|
||||
console.log(item)
|
||||
setFieldsValue({ itemColor: item[0] })
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/*begin 字典颜色配置样式*/
|
||||
.item-tool{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.item-color{
|
||||
width: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.item-back{
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.item-color-border{
|
||||
visibility: hidden;
|
||||
}
|
||||
.item-active .item-color-border{
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
border: 1px solid;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
/*end 字典颜色配置样式*/
|
||||
</style>
|
||||
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" width="550px" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { BasicForm, useForm } from '/src/components/Form';
|
||||
import { formSchema } from '../dict.data';
|
||||
import { saveOrUpdateDict } from '../dict.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
const rowId = ref('');
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false, minHeight: 80 });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
rowId.value = data.record.id;
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增字典' : '编辑字典'));
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateDict(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="字典回收站" :showOkBtn="false" width="1000px" destroyOnClose>
|
||||
<BasicTable @register="registerTable">
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRaw } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/src/components/Modal';
|
||||
import { BasicTable, useTable, TableAction } from '/src/components/Table';
|
||||
import { recycleBincolumns } from '../dict.data';
|
||||
import { getRecycleBinList, putRecycleBin, deleteRecycleBin } from '../dict.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner();
|
||||
//注册table数据
|
||||
const [registerTable, { reload }] = useTable({
|
||||
api: getRecycleBinList,
|
||||
columns: recycleBincolumns,
|
||||
striped: true,
|
||||
useSearchForm: false,
|
||||
showTableSetting: false,
|
||||
clickToRowSelect: false,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
pagination: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
canResize: false,
|
||||
actionColumn: {
|
||||
width: 100,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 还原事件
|
||||
*/
|
||||
async function handleRevert(record) {
|
||||
await putRecycleBin(record.id, reload);
|
||||
emit('success');
|
||||
}
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteRecycleBin(record.id, reload);
|
||||
}
|
||||
/**
|
||||
* 批量还原事件
|
||||
*/
|
||||
function batchHandleRevert() {
|
||||
handleRevert({ id: toRaw(checkedKeys.value).join(',') });
|
||||
}
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
function batchHandleDelete() {
|
||||
handleDelete({ id: toRaw(checkedKeys.value).join(',') });
|
||||
}
|
||||
//获取操作栏事件
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '取回',
|
||||
icon: 'ant-design:redo-outlined',
|
||||
popConfirm: {
|
||||
title: '是否确认取回',
|
||||
confirm: handleRevert.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '彻底删除',
|
||||
icon: 'ant-design:scissor-outlined',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
135
jeecgboot-vue3/src/views/system/dict/dict.api.ts
Normal file
135
jeecgboot-vue3/src/views/system/dict/dict.api.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
enum Api {
|
||||
list = '/sys/dict/list',
|
||||
save = '/sys/dict/add',
|
||||
edit = '/sys/dict/edit',
|
||||
duplicateCheck = '/sys/duplicate/check',
|
||||
deleteDict = '/sys/dict/delete',
|
||||
deleteBatch = '/sys/dict/deleteBatch',
|
||||
importExcel = '/sys/dict/importExcel',
|
||||
exportXls = '/sys/dict/exportXls',
|
||||
recycleBinList = '/sys/dict/deleteList',
|
||||
putRecycleBin = '/sys/dict/back',
|
||||
deleteRecycleBin = '/sys/dict/deletePhysic',
|
||||
itemList = '/sys/dictItem/list',
|
||||
deleteItem = '/sys/dictItem/delete',
|
||||
itemSave = '/sys/dictItem/add',
|
||||
itemEdit = '/sys/dictItem/edit',
|
||||
dictItemCheck = '/sys/dictItem/dictItemCheck',
|
||||
refreshCache = '/sys/dict/refleshCache',
|
||||
queryAllDictItems = '/sys/dict/queryAllDictItems',
|
||||
}
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入api
|
||||
* @param params
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
/**
|
||||
* 字典列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
/**
|
||||
* 删除字典
|
||||
*/
|
||||
export const deleteDict = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteDict, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 批量删除字典
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteDict = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 保存或者更新字典
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateDict = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
/**
|
||||
* 唯一校验
|
||||
* @param params
|
||||
*/
|
||||
export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck, params }, { isTransformResponse: false });
|
||||
/**
|
||||
* 字典回收站列表
|
||||
* @param params
|
||||
*/
|
||||
export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params });
|
||||
/**
|
||||
* 回收站还原
|
||||
* @param params
|
||||
*/
|
||||
export const putRecycleBin = (id, handleSuccess) => {
|
||||
return defHttp.put({ url: Api.putRecycleBin + `/${id}` }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 回收站删除
|
||||
* @param params
|
||||
*/
|
||||
export const deleteRecycleBin = (id, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteRecycleBin + `/${id}` }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 字典配置列表
|
||||
* @param params
|
||||
*/
|
||||
export const itemList = (params) => defHttp.get({ url: Api.itemList, params });
|
||||
/**
|
||||
* 字典配置删除
|
||||
* @param params
|
||||
*/
|
||||
export const deleteItem = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteItem, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 保存或者更新字典配置
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateDictItem = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.itemEdit : Api.itemSave;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
/**
|
||||
* 校验字典数据值
|
||||
* @param params
|
||||
*/
|
||||
export const dictItemCheck = (params) => defHttp.get({ url: Api.dictItemCheck, params }, { isTransformResponse: false });
|
||||
/**
|
||||
* 刷新字典
|
||||
* @param params
|
||||
*/
|
||||
export const refreshCache = () => defHttp.get({ url: Api.refreshCache }, { isTransformResponse: false });
|
||||
/**
|
||||
* 获取所有字典项
|
||||
* @param params
|
||||
*/
|
||||
export const queryAllDictItems = () => defHttp.get({ url: Api.queryAllDictItems }, { isTransformResponse: false });
|
||||
203
jeecgboot-vue3/src/views/system/dict/dict.data.ts
Normal file
203
jeecgboot-vue3/src/views/system/dict/dict.data.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
import { dictItemCheck } from './dict.api';
|
||||
import { rules } from '/@/utils/helper/validator';
|
||||
import { h } from "vue";
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: '字典编码',
|
||||
dataIndex: 'dictCode',
|
||||
width: 240,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
// width: 120
|
||||
},
|
||||
];
|
||||
|
||||
export const recycleBincolumns: BasicColumn[] = [
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '字典编码',
|
||||
dataIndex: 'dictCode',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
width: 120,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '字典名称',
|
||||
field: 'dictName',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '字典编码',
|
||||
field: 'dictCode',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '字典名称',
|
||||
field: 'dictName',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '字典编码',
|
||||
field: 'dictCode',
|
||||
component: 'Input',
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_dict', 'dict_code', model, schema, true),
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
field: 'description',
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
|
||||
export const dictItemColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'itemText',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '数据值',
|
||||
dataIndex: 'itemValue',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字典颜色',
|
||||
dataIndex: 'itemColor',
|
||||
width: 80,
|
||||
align:'center',
|
||||
customRender:({ text }) => {
|
||||
return h('div', {
|
||||
style: {"background": text, "width":"18px","height":"18px","border-radius":"50%","margin":"0 auto"}
|
||||
})
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
export const dictItemSearchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '名称',
|
||||
field: 'itemText',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'dict_item_status',
|
||||
stringToNumber: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const itemFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '名称',
|
||||
field: 'itemText',
|
||||
required: true,
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '数据值',
|
||||
field: 'itemValue',
|
||||
component: 'Input',
|
||||
dynamicRules: ({ values, model }) => {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请输入数据值');
|
||||
}
|
||||
if (new RegExp("[`~!@#$^&*()=|{}'.<>《》/?!¥()—【】‘;:”“。,、?]").test(value)) {
|
||||
return Promise.reject('数据值不能包含特殊字符!');
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let params = {
|
||||
dictId: values.dictId,
|
||||
id: model.id,
|
||||
itemValue: value,
|
||||
};
|
||||
dictItemCheck(params)
|
||||
.then((res) => {
|
||||
res.success ? resolve() : reject(res.message || '校验失败');
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || '验证失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '颜色值',
|
||||
field: 'itemColor',
|
||||
component: 'Input',
|
||||
slot:'itemColor'
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
field: 'description',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
field: 'sortOrder',
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 1,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '是否启用',
|
||||
defaultValue: 1,
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
type: 'radioButton',
|
||||
dictCode: 'dict_item_status',
|
||||
stringToNumber: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
191
jeecgboot-vue3/src/views/system/dict/index.vue
Normal file
191
jeecgboot-vue3/src/views/system/dict/index.vue
Normal file
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-button type="primary" @click="handlerRefreshCache" preIcon="ant-design:sync-outlined"> 刷新缓存</a-button>
|
||||
<a-button type="primary" @click="openRecycleModal(true)" preIcon="ant-design:hdd-outlined"> 回收站</a-button>
|
||||
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!--字典弹窗-->
|
||||
<DictModal @register="registerModal" @success="handleSuccess" />
|
||||
<!--字典配置抽屉-->
|
||||
<DictItemList @register="registerDrawer" />
|
||||
<!--回收站弹窗-->
|
||||
<DictRecycleBinModal @register="registerModal1" @success="reload" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="system-dict" setup>
|
||||
//ts语法
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/src/components/Table';
|
||||
import { useDrawer } from '/src/components/Drawer';
|
||||
import { useModal } from '/src/components/Modal';
|
||||
import DictItemList from './components/DictItemList.vue';
|
||||
import DictModal from './components/DictModal.vue';
|
||||
import DictRecycleBinModal from './components/DictRecycleBinModal.vue';
|
||||
import { useMessage } from '/src/hooks/web/useMessage';
|
||||
import { removeAuthCache, setAuthCache } from '/src/utils/auth';
|
||||
import { columns, searchFormSchema } from './dict.data';
|
||||
import { list, deleteDict, batchDeleteDict, getExportUrl, getImportUrl, refreshCache, queryAllDictItems } from './dict.api';
|
||||
import { DB_DICT_DATA_KEY } from '/src/enums/cacheEnum';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
//字典model
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
//字典配置drawer
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
//回收站model
|
||||
const [registerModal1, { openModal: openRecycleModal }] = useModal();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
designScope: 'dict-template',
|
||||
tableProps: {
|
||||
title: '数据字典',
|
||||
api: list,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 240,
|
||||
},
|
||||
},
|
||||
//update-begin---author:wangshuai ---date:20220616 for:[issues/I5AMDD]导入/导出功能,操作后提示没有传递 export.url/import.url 参数------------
|
||||
exportConfig: {
|
||||
name: '数据字典列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
//update-end---author:wangshuai ---date:20220616 for:[issues/I5AMDD]导入/导出功能,操作后提示没有传递 export.url/import.url 参数--------------
|
||||
});
|
||||
|
||||
//注册table数据
|
||||
const [registerTable, { reload, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleCreate() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
async function handleEdit(record: Recordable) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
async function handleDetail(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteDict({ id: record.id }, reload);
|
||||
}
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteDict({ ids: selectedRowKeys.value }, reload);
|
||||
}
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess({ isUpdate, values }) {
|
||||
if (isUpdate) {
|
||||
updateTableDataRecord(values.id, values);
|
||||
} else {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 刷新缓存
|
||||
*/
|
||||
async function handlerRefreshCache() {
|
||||
const result = await refreshCache();
|
||||
if (result.success) {
|
||||
const res = await queryAllDictItems();
|
||||
removeAuthCache(DB_DICT_DATA_KEY);
|
||||
// update-begin--author:liaozhiyang---date:20230908---for:【QQYUN-6417】生产环境字典慢的问题
|
||||
const userStore = useUserStore();
|
||||
userStore.setAllDictItems(res.result);
|
||||
// update-end--author:liaozhiyang---date:20230908---for:【QQYUN-6417】生产环境字典慢的问题
|
||||
createMessage.success('刷新缓存完成!');
|
||||
} else {
|
||||
createMessage.error('刷新缓存失败!');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 字典配置
|
||||
*/
|
||||
function handleItem(record) {
|
||||
openDrawer(true, {
|
||||
id: record.id,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '字典配置',
|
||||
onClick: handleItem.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '确定删除吗?',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
69
jeecgboot-vue3/src/views/system/examples/demo/DemoModal.vue
Normal file
69
jeecgboot-vue3/src/views/system/examples/demo/DemoModal.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="40%">
|
||||
<BasicForm @register="registerForm" :disabled="isDisabled" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './demo.data';
|
||||
import { saveOrUpdateDemo, getDemoById } from './demo.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
|
||||
//自定义接受参数
|
||||
const props = defineProps({
|
||||
//是否禁用页面
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
//labelWidth: 150,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false, showOkBtn: !props.isDisabled});
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if(data.createBy){
|
||||
await setFieldsValue({createBy: data.createBy})
|
||||
}
|
||||
if(data.createTime){
|
||||
await setFieldsValue({createTime: data.createTime})
|
||||
}
|
||||
if (unref(isUpdate)) {
|
||||
//获取详情
|
||||
data.record = await getDemoById({ id: data.record.id });
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
|
||||
//表单提交事件
|
||||
async function handleSubmit(v) {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateDemo(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success', values);
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
73
jeecgboot-vue3/src/views/system/examples/demo/demo.api.ts
Normal file
73
jeecgboot-vue3/src/views/system/examples/demo/demo.api.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/test/jeecgDemo/list',
|
||||
save = '/test/jeecgDemo/add',
|
||||
edit = '/test/jeecgDemo/edit',
|
||||
get = '/test/jeecgDemo/queryById',
|
||||
delete = '/test/jeecgDemo/delete',
|
||||
deleteBatch = '/test/jeecgDemo/deleteBatch',
|
||||
exportXls = '/test/jeecgDemo/exportXls',
|
||||
importExcel = '/test/jeecgDemo/importExcel',
|
||||
}
|
||||
/**
|
||||
* 导出api
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
/**
|
||||
* 查询示例列表
|
||||
* @param params
|
||||
*/
|
||||
export const getDemoList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新示例
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateDemo = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询示例详情
|
||||
* @param params
|
||||
*/
|
||||
export const getDemoById = (params) => {
|
||||
return defHttp.get({ url: Api.get, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除示例
|
||||
* @param params
|
||||
*/
|
||||
export const deleteDemo = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除示例
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteDemo = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
223
jeecgboot-vue3/src/views/system/examples/demo/demo.data.ts
Normal file
223
jeecgboot-vue3/src/views/system/examples/demo/demo.data.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
width: 170,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
sorter: {
|
||||
multiple:1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '关键词',
|
||||
dataIndex: 'keyWord',
|
||||
width: 130,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '打卡时间',
|
||||
dataIndex: 'punchTime',
|
||||
width: 140,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '工资',
|
||||
dataIndex: 'salaryMoney',
|
||||
width: 140,
|
||||
resizable: true,
|
||||
sorter: {
|
||||
multiple: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '奖金',
|
||||
dataIndex: 'bonusMoney',
|
||||
width: 140,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex',
|
||||
sorter: {
|
||||
multiple: 3
|
||||
},
|
||||
customRender: ({ record }) => {
|
||||
return render.renderDict(record.sex, 'sex');
|
||||
// let v = record.sex ? (record.sex == '1' ? '男' : '女') : '';
|
||||
// return h('span', v);
|
||||
},
|
||||
width: 120,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '生日',
|
||||
dataIndex: 'birthday',
|
||||
width: 120,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 120,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
title: '个人简介',
|
||||
dataIndex: 'content',
|
||||
width: 120,
|
||||
resizable: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '姓名',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
trim: true,
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'birthday',
|
||||
label: '生日',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
valueType: 'Date'
|
||||
},
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'age',
|
||||
label: '年龄',
|
||||
component: 'Input',
|
||||
slot: 'age',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'sex',
|
||||
label: '性别',
|
||||
colProps: { span: 8 },
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'sex',
|
||||
placeholder: '请选择性别',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
label: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'createBy',
|
||||
label: 'createBy',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
label: 'createTime',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '名字',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
componentProps: {
|
||||
placeholder: '请输入名字',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'keyWord',
|
||||
label: '关键词',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入关键词',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'punchTime',
|
||||
label: '打卡时间',
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择打卡时间',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'salaryMoney',
|
||||
label: '工资',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入工资',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'sex',
|
||||
label: '性别',
|
||||
component: 'JDictSelectTag',
|
||||
defaultValue: '1',
|
||||
componentProps: {
|
||||
type: 'radio',
|
||||
dictCode: 'sex',
|
||||
placeholder: '请选择性别',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'age',
|
||||
label: '年龄',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 1,
|
||||
componentProps: {
|
||||
placeholder: '请输入年龄',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'birthday',
|
||||
label: '生日',
|
||||
component: 'DatePicker',
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择生日',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
rules: [{ required: false, type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
|
||||
componentProps: {
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: '个人简介 - To introduce myself',
|
||||
component: 'InputTextArea',
|
||||
labelLength: 4,
|
||||
componentProps: {
|
||||
placeholder: '请输入个人简介',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'updateCount',
|
||||
label: '乐观锁',
|
||||
show: false,
|
||||
component: 'Input',
|
||||
},
|
||||
];
|
||||
311
jeecgboot-vue3/src/views/system/examples/demo/index.vue
Normal file
311
jeecgboot-vue3/src/views/system/examples/demo/index.vue
Normal file
@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--自定义查询区域-->
|
||||
<div class="jeecg-basic-table-form-container" @keyup.enter="searchQuery" v-if="customSearch">
|
||||
<a-form ref="formRef" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-row :gutter="24">
|
||||
<a-col :lg="8">
|
||||
<a-form-item label="用户名">
|
||||
<a-input placeholder="请输入名称模糊查询" v-model:value="queryParam.name"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8">
|
||||
<a-form-item label="年龄">
|
||||
<a-input placeholder="最小年龄" type="ge" v-model:value="queryParam.age_begin" style="width: calc(50% - 15px)"></a-input>
|
||||
<span>~</span>
|
||||
<a-input placeholder="最大年龄" type="le" v-model:value="queryParam.age_end" style="width: calc(50% - 15px)"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<template v-if="toggleSearchStatus">
|
||||
<a-col :lg="8">
|
||||
<a-form-item label="性别">
|
||||
<JDictSelectTag v-model:value="queryParam.sex" placeholder="请选择性别" dictCode="sex" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8">
|
||||
<a-form-item label="选择用户">
|
||||
<JDictSelectTag v-model:value="queryParam.id" placeholder="请选择用户" dictCode="demo,name,id" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
|
||||
<a-col :lg="6">
|
||||
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
|
||||
<a @click="toggleSearchStatus = !toggleSearchStatus" style="margin-left: 8px">
|
||||
{{ toggleSearchStatus ? '收起' : '展开' }}
|
||||
<Icon :icon="toggleSearchStatus ? 'ant-design:up-outlined' : 'ant-design:down-outlined'" />
|
||||
</a>
|
||||
</a-col>
|
||||
</span>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection" :class="{ 'p-4': customSearch }">
|
||||
<template #form-age="{ model, field }">
|
||||
<a-input placeholder="最小年龄" type="ge" v-model:value="min" style="width: calc(50% - 15px)" @change="ageChange(model, field)"></a-input>
|
||||
<span>~</span>
|
||||
<a-input placeholder="最大年龄" type="le" v-model:value="max" style="width: calc(50% - 15px)" @change="ageChange(model, field)"></a-input>
|
||||
</template>
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新增</a-button>
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="(file) => handleImportXls(file, getImportUrl, reload)">
|
||||
<a-button preIcon="ant-design:import-outlined" type="primary">导入</a-button>
|
||||
</a-upload>
|
||||
<a-button preIcon="ant-design:export-outlined" type="primary" @click="handleExportXls('单表示例', getExportUrl,exportParams)">导出</a-button>
|
||||
<a-button preIcon="ant-design:filter" type="primary" @click="">高级查询</a-button>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="openTab">打开Tab页</a-button>
|
||||
<a-button preIcon="ant-design:retweet-outlined" type="primary" @click="customSearch = !customSearch">{{
|
||||
customSearch ? '表单配置查询' : '自定义查询'
|
||||
}}</a-button>
|
||||
<a-button preIcon="ant-design:import-outlined" type="primary" @click="handleImport">弹窗导入</a-button>
|
||||
|
||||
<super-query :config="superQueryConfig" @search="handleSuperQuery"/>
|
||||
|
||||
<a-dropdown v-if="checkedKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon style="fontsize: 12px" icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<DemoModal @register="registerModal" @success="reload" :isDisabled="isDisabled"/>
|
||||
<JImportModal @register="registerModalJimport" :url="getImportUrl" online />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref, reactive, toRaw, watch,computed } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import DemoModal from './DemoModal.vue';
|
||||
import JImportModal from '/@/components/Form/src/jeecg/components/JImportModal.vue';
|
||||
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useMethods } from '/@/hooks/system/useMethods';
|
||||
import { getDemoList, deleteDemo, batchDeleteDemo, getExportUrl, getImportUrl } from './demo.api';
|
||||
import { columns, searchFormSchema } from './demo.data';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { router } from '/@/router';
|
||||
import { filterObj } from '/@/utils/common/compUtils';
|
||||
|
||||
const go = useGo();
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerModalJimport, { openModal: openModalJimport }] = useModal();
|
||||
const { handleExportXls, handleImportXls } = useMethods();
|
||||
const min = ref();
|
||||
const max = ref();
|
||||
const isDisabled = ref(false);
|
||||
|
||||
const [registerTable, { reload, setProps }] = useTable({
|
||||
title: '单表示例',
|
||||
api: getDemoList,
|
||||
columns,
|
||||
formConfig: {
|
||||
//labelWidth: 120,
|
||||
schemas: searchFormSchema,
|
||||
fieldMapToTime: [['birthday', ['birthday_begin', 'birthday_end'], 'YYYY-MM-DD']],
|
||||
fieldMapToNumber: [['age', ['age_begin', 'age_end']]],
|
||||
autoAdvancedCol: 2,
|
||||
actionColOptions: {
|
||||
style: { textAlign: 'left' },
|
||||
},
|
||||
},
|
||||
//自定义默认排序
|
||||
defSort: {
|
||||
column: 'createTime,sex',
|
||||
order: 'desc',
|
||||
},
|
||||
striped: true,
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
clickToRowSelect: false,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
canResize: false,
|
||||
rowKey: 'id',
|
||||
actionColumn: {
|
||||
width: 180,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
/**
|
||||
* 选择列配置
|
||||
*/
|
||||
const rowSelection = {
|
||||
type: 'checkbox',
|
||||
columnWidth: 40,
|
||||
selectedRowKeys: checkedKeys,
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
|
||||
function handleImport() {
|
||||
openModalJimport(true);
|
||||
}
|
||||
|
||||
const exportParams = computed(()=>{
|
||||
let paramsForm = {};
|
||||
if (checkedKeys.value && checkedKeys.value.length > 0) {
|
||||
paramsForm['selections'] = checkedKeys.value.join(',');
|
||||
}
|
||||
return filterObj(paramsForm)
|
||||
})
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择事件
|
||||
*/
|
||||
function onSelectChange(selectedRowKeys: (string | number)[]) {
|
||||
console.log("checkedKeys------>",checkedKeys)
|
||||
checkedKeys.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
isDisabled.value = false;
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
isDisabled.value = false;
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页面
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
isDisabled.value = true;
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteDemo({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteDemo({ ids: checkedKeys.value }, reload);
|
||||
}
|
||||
/**
|
||||
* 年龄修改事件
|
||||
*/
|
||||
function ageChange(model, field) {
|
||||
model[field] = [unref(min), unref(max)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开tab页面
|
||||
*/
|
||||
function openTab() {
|
||||
go(`/comp/jeecg/basic`);
|
||||
}
|
||||
//-----自定义查询----begin--------
|
||||
const formElRef = ref();
|
||||
const labelCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 7 },
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
});
|
||||
const toggleSearchStatus = ref(false);
|
||||
const customSearch = ref(false);
|
||||
const queryParam = reactive({
|
||||
name: '',
|
||||
age_begin: '',
|
||||
age_end: '',
|
||||
sex: '',
|
||||
id: '',
|
||||
});
|
||||
watch(customSearch, () => {
|
||||
setProps({ useSearchForm: !unref(customSearch) });
|
||||
});
|
||||
function searchQuery() {
|
||||
setProps({ searchInfo: toRaw(queryParam) });
|
||||
reload();
|
||||
}
|
||||
function searchReset() {
|
||||
Object.assign(queryParam, { name: '', age_begin: '', age_end: '', sex: '', id: '' });
|
||||
reload();
|
||||
}
|
||||
//自定义查询----end---------
|
||||
|
||||
const superQueryConfig = reactive({
|
||||
name:{ title: "名称", view: "text", type: "string", order: 1 },
|
||||
sex:{ title: "性别", view: "list", type: "string", dictCode:'sex', order: 2 },
|
||||
});
|
||||
|
||||
function handleSuperQuery(params) {
|
||||
Object.keys(params).map(k=>{
|
||||
queryParam[k] = params[k]
|
||||
});
|
||||
searchQuery();
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.jeecg-basic-table-form-container {
|
||||
.table-page-search-submitButtons {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
jeecgboot-vue3/src/views/system/fillRule/FillRuleModal.vue
Normal file
65
jeecgboot-vue3/src/views/system/fillRule/FillRuleModal.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" :width="800" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './fill.rule.data';
|
||||
import { saveFillRule, updateFillRule } from './fill.rule.api';
|
||||
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
|
||||
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, getFieldsValue }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
baseColProps: { span: 12 },
|
||||
});
|
||||
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
let formValue = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
if (isUpdate.value) {
|
||||
let allFieldsValue = getFieldsValue();
|
||||
// 编辑页面 如果表单没有父级下拉框 则提交时候 validate方法不返该值 需要手动设置
|
||||
if (!formValue.parentId && allFieldsValue.parentId) {
|
||||
formValue.parentId = allFieldsValue.parentId;
|
||||
}
|
||||
await updateFillRule(formValue);
|
||||
} else {
|
||||
await saveFillRule(formValue);
|
||||
}
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
83
jeecgboot-vue3/src/views/system/fillRule/fill.rule.api.ts
Normal file
83
jeecgboot-vue3/src/views/system/fillRule/fill.rule.api.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/fillRule/list',
|
||||
test = '/sys/fillRule/testFillRule',
|
||||
save = '/sys/fillRule/add',
|
||||
edit = '/sys/fillRule/edit',
|
||||
delete = '/sys/fillRule/delete',
|
||||
deleteBatch = '/sys/fillRule/deleteBatch',
|
||||
exportXls = '/sys/fillRule/exportXls',
|
||||
importExcel = '/sys/fillRule/importExcel',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出地址
|
||||
*/
|
||||
export const exportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入地址
|
||||
*/
|
||||
export const importUrl = Api.importExcel;
|
||||
|
||||
/**
|
||||
* 列表查询
|
||||
* @param params
|
||||
*/
|
||||
export const getFillRuleList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteFillRule = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteFillRule = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 规则功能测试
|
||||
* @param params
|
||||
*/
|
||||
export const handleTest = (params) => {
|
||||
return defHttp.get({ url: Api.test, params }, { isTransformResponse: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @param params
|
||||
*/
|
||||
export const saveFillRule = (params) => {
|
||||
return defHttp.post({ url: Api.save, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param params
|
||||
*/
|
||||
export const updateFillRule = (params) => {
|
||||
return defHttp.put({ url: Api.edit, params });
|
||||
};
|
||||
112
jeecgboot-vue3/src/views/system/fillRule/fill.rule.data.ts
Normal file
112
jeecgboot-vue3/src/views/system/fillRule/fill.rule.data.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { duplicateCheckDelay } from '/@/views/system/user/user.api';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '规则名称',
|
||||
dataIndex: 'ruleName',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '规则编码',
|
||||
dataIndex: 'ruleCode',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '规则实现类',
|
||||
dataIndex: 'ruleClass',
|
||||
width: 300,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '规则参数',
|
||||
dataIndex: 'ruleParams',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
field: 'ruleCode',
|
||||
label: '规则编码',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
field: 'ruleCode',
|
||||
label: '规则编码',
|
||||
component: 'Input',
|
||||
colProps: { span: 24 },
|
||||
dynamicDisabled: ({ values }) => {
|
||||
return !!values.id;
|
||||
},
|
||||
dynamicRules: ({ model }) => {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!value) {
|
||||
return reject('请输入规则编码!');
|
||||
}
|
||||
let params = {
|
||||
tableName: 'sys_fill_rule',
|
||||
fieldName: 'rule_code',
|
||||
fieldVal: value,
|
||||
dataId: model.id,
|
||||
};
|
||||
duplicateCheckDelay(params)
|
||||
.then((res) => {
|
||||
res.success ? resolve() : reject('规则编码已存在!');
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.message || '校验失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ruleClass',
|
||||
label: '规则实现类',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
field: 'ruleParams',
|
||||
label: '规则参数',
|
||||
colProps: { span: 24 },
|
||||
component: 'JAddInput',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
146
jeecgboot-vue3/src/views/system/fillRule/index.vue
Normal file
146
jeecgboot-vue3/src/views/system/fillRule/index.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作</span>
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<FillRuleModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script name="system-fillrule" lang="ts" setup>
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { getFillRuleList, exportUrl, importUrl, deleteFillRule, batchDeleteFillRule, handleTest } from '/@/views/system/fillRule/fill.rule.api';
|
||||
import { columns, searchFormSchema } from '/@/views/system/fillRule/fill.rule.data';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { ActionItem } from '/@/components/Table';
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
import FillRuleModal from '/@/views/system/fillRule/FillRuleModal.vue';
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, createMessage, createSuccessModal, onExportXls, onImportXls } = useListPage({
|
||||
designScope: 'fill-rule',
|
||||
tableProps: {
|
||||
title: '填值规则管理页面',
|
||||
api: getFillRuleList,
|
||||
columns: columns,
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
url: exportUrl,
|
||||
name: '填值规则列表',
|
||||
},
|
||||
importConfig: {
|
||||
url: importUrl,
|
||||
success: () => reload(),
|
||||
},
|
||||
});
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
console.log('record....', record);
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
console.log(12345, record);
|
||||
await deleteFillRule({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteFillRule({ ids: selectedRowKeys.value }, () => {
|
||||
selectedRowKeys.value = [];
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能测试
|
||||
*/
|
||||
function testRule(record) {
|
||||
let params = { ruleCode: record.ruleCode };
|
||||
handleTest(params).then((res) => {
|
||||
if (res.success) {
|
||||
createSuccessModal({
|
||||
title: '填值规则功能测试',
|
||||
content: '生成结果:' + res.result,
|
||||
});
|
||||
} else {
|
||||
createMessage.warn(res.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [{ label: '编辑', onClick: handleEdit.bind(null, record) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '功能测试', onClick: testRule.bind(null, record) },
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认要删除吗?',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
165
jeecgboot-vue3/src/views/system/loginmini/MiniCodelogin.vue
Normal file
165
jeecgboot-vue3/src/views/system/loginmini/MiniCodelogin.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="aui-content">
|
||||
<div class="aui-container">
|
||||
<div class="aui-form">
|
||||
<div class="aui-image">
|
||||
<div class="aui-image-text">
|
||||
<img :src="adTextImg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formBox aui-formEwm">
|
||||
<div class="aui-formWell">
|
||||
<form>
|
||||
<div class="aui-flex aui-form-nav investment_title" style="padding-bottom: 19px">
|
||||
<div class="aui-flex-box activeNav">{{t('sys.login.qrSignInFormTitle')}}</div>
|
||||
</div>
|
||||
<div class="aui-form-box">
|
||||
<div class="aui-account" style="padding: 30px 0">
|
||||
<div class="aui-ewm">
|
||||
<QrCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formButton">
|
||||
<a class="aui-linek-code aui-link-register" @click="goBackHandleClick">{{t('sys.login.backSignIn')}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="aui-flex aui-third-text">
|
||||
<div class="aui-flex-box aui-third-border">
|
||||
<span>{{ t('sys.login.otherSignIn') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a href="" title="github" @click="onThirdLogin('github')"><GithubFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a href="" title="企业微信" @click="onThirdLogin('wechat_enterprise')"><icon-font class="item-icon" type="icon-qiyeweixin3" /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a href="" title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a href="" title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第三方登录相关弹框 -->
|
||||
<ThirdModal ref="thirdModalRef"></ThirdModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="mini-code-login">
|
||||
import { ref, onUnmounted } from 'vue';
|
||||
import { getLoginQrcode, getQrcodeToken } from '/@/api/sys/user';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { QrCode } from '/@/components/Qrcode/index';
|
||||
import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
|
||||
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
|
||||
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useDesign } from "/@/hooks/web/useDesign";
|
||||
import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
|
||||
});
|
||||
const { prefixCls } = useDesign('minilogin');
|
||||
const { t } = useI18n();
|
||||
const qrCodeUrl = ref<string>('');
|
||||
let timer: IntervalHandle;
|
||||
const state = ref('0');
|
||||
const thirdModalRef = ref();
|
||||
const userStore = useUserStore();
|
||||
const emit = defineEmits(['go-back', 'success', 'register']);
|
||||
|
||||
//加载二维码信息
|
||||
function loadQrCode() {
|
||||
state.value = '0';
|
||||
getLoginQrcode().then((res) => {
|
||||
qrCodeUrl.value = res.qrcodeId;
|
||||
if (res.qrcodeId) {
|
||||
openTimer(res.qrcodeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
//监控扫码状态
|
||||
function watchQrcodeToken(qrcodeId) {
|
||||
getQrcodeToken({ qrcodeId: qrcodeId }).then((res) => {
|
||||
let token = res.token;
|
||||
if (token == '-2') {
|
||||
//二维码过期重新获取
|
||||
loadQrCode();
|
||||
clearInterval(timer);
|
||||
}
|
||||
//扫码成功
|
||||
if (res.success) {
|
||||
state.value = '2';
|
||||
clearInterval(timer);
|
||||
setTimeout(() => {
|
||||
userStore.qrCodeLogin(token);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 开启定时器 */
|
||||
function openTimer(qrcodeId) {
|
||||
watchQrcodeToken(qrcodeId);
|
||||
closeTimer();
|
||||
timer = setInterval(() => {
|
||||
watchQrcodeToken(qrcodeId);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
/** 关闭定时器 */
|
||||
function closeTimer() {
|
||||
if (timer) clearInterval(timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
* @param type
|
||||
*/
|
||||
function onThirdLogin(type) {
|
||||
thirdModalRef.value.onThirdLogin(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单
|
||||
*/
|
||||
function initFrom() {
|
||||
loadQrCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回
|
||||
*/
|
||||
function goBackHandleClick() {
|
||||
emit('go-back');
|
||||
closeTimer();
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
closeTimer();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
initFrom,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '/@/assets/loginmini/style/home.less';
|
||||
@import '/@/assets/loginmini/style/base.less';
|
||||
</style>
|
||||
294
jeecgboot-vue3/src/views/system/loginmini/MiniForgotpad.vue
Normal file
294
jeecgboot-vue3/src/views/system/loginmini/MiniForgotpad.vue
Normal file
@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="aui-content">
|
||||
<div class="aui-container">
|
||||
<div class="aui-form">
|
||||
<div class="aui-image">
|
||||
<div class="aui-image-text">
|
||||
<img :src="adTextImg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formBox">
|
||||
<div class="aui-formWell">
|
||||
<div class="aui-step-box">
|
||||
<div class="aui-step-item" :class="activeKey === 1 ? 'activeStep' : ''">
|
||||
<div class="aui-step-tags">
|
||||
<em>1</em>
|
||||
<p>{{t('sys.login.authentication')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-step-item" :class="activeKey === 2 ? 'activeStep' : ''">
|
||||
<div class="aui-step-tags">
|
||||
<em>2</em>
|
||||
<p>{{t('sys.login.resetLoginPassword')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-step-item" :class="activeKey === 3 ? 'activeStep' : ''">
|
||||
<div class="aui-step-tags">
|
||||
<em>3</em>
|
||||
<p>{{t('sys.login.resetSuccess')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="" style="height: 230px; position: relative">
|
||||
<a-form ref="formRef" :model="formData" v-if="activeKey === 1">
|
||||
<!-- 身份验证 begin -->
|
||||
<div class="aui-account aui-account-line aui-forgot">
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<a-input type="text" :placeholder="t('sys.login.mobile')" v-model:value="formData.mobile" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<a-form-item>
|
||||
<a-input type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
|
||||
</a-form-item>
|
||||
<div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{t('component.countdown.normalText')}}</div>
|
||||
<div v-else class="aui-code-line">{{t('component.countdown.sendText',[unref(timeRuning)])}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 身份验证 end -->
|
||||
</a-form>
|
||||
<a-form ref="pwdFormRef" :model="pwdFormData" v-else-if="activeKey === 2">
|
||||
<!-- 重置密码 begin -->
|
||||
<div class="aui-account aui-account-line aui-forgot">
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<a-input type="password" :placeholder="t('sys.login.passwordPlaceholder')" v-model:value="pwdFormData.password" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<a-input type="password" :placeholder="t('sys.login.confirmPassword')" v-model:value="pwdFormData.confirmPassword" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<!-- 重置密码 end -->
|
||||
</a-form>
|
||||
<!-- 重置成功 begin -->
|
||||
<div class="aui-success" v-else>
|
||||
<div class="aui-success-icon">
|
||||
<img :src="successImg"/>
|
||||
</div>
|
||||
<h3>恭喜您,重置密码成功!</h3>
|
||||
</div>
|
||||
<!-- 重置成功 end -->
|
||||
</div>
|
||||
<div class="aui-formButton" style="padding-bottom: 40px">
|
||||
<div class="aui-flex" v-if="activeKey === 1 || activeKey === 2">
|
||||
<a class="aui-link-login aui-flex-box" @click="nextStepClick">{{t('sys.login.nextStep')}}</a>
|
||||
</div>
|
||||
<div class="aui-flex" v-else>
|
||||
<a class="aui-linek-code aui-flex-box" @click="toLogin">{{t('sys.login.goToLogin')}}</a>
|
||||
</div>
|
||||
<div class="aui-flex">
|
||||
<a class="aui-linek-code aui-flex-box" @click="goBack"> {{ t('sys.login.backSignIn') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图片验证码弹窗 -->
|
||||
<CaptchaModal @register="captchaRegisterModal" @ok="getLoginCode" />
|
||||
</template>
|
||||
<script lang="ts" name="mini-forgotpad" setup>
|
||||
import { reactive, ref, toRaw, unref } from 'vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { SmsEnum, useFormRules, useFormValid, useLoginState } from '/@/views/sys/login/useLogin';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getCaptcha, passwordChange, phoneVerify } from '/@/api/sys/user';
|
||||
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png'
|
||||
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png'
|
||||
import successImg from '/@/assets/loginmini/icon/icon-success.png'
|
||||
import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
|
||||
import { useModal } from "@/components/Modal";
|
||||
import { ExceptionEnum } from "@/enums/exceptionEnum";
|
||||
const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
|
||||
|
||||
//下一步控制
|
||||
const activeKey = ref<number>(1);
|
||||
const { t } = useI18n();
|
||||
const { handleBackLogin } = useLoginState();
|
||||
const { notification, createMessage, createErrorModal } = useMessage();
|
||||
//是否显示获取验证码
|
||||
const showInterval = ref<boolean>(true);
|
||||
//60s
|
||||
const timeRuning = ref<number>(60);
|
||||
//定时器
|
||||
const timer = ref<any>(null);
|
||||
const formRef = ref();
|
||||
const pwdFormRef = ref();
|
||||
//账号数据
|
||||
const accountInfo = reactive<any>({});
|
||||
//手机号表单
|
||||
const formData = reactive({
|
||||
mobile: '',
|
||||
smscode: '',
|
||||
});
|
||||
//密码表单
|
||||
const pwdFormData = reactive<any>({
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
const emit = defineEmits(['go-back', 'success', 'register']);
|
||||
|
||||
/**
|
||||
* 下一步
|
||||
*/
|
||||
async function handleNext() {
|
||||
if (!formData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.smscode) {
|
||||
createMessage.warn(t('sys.login.smsPlaceholder'));
|
||||
return;
|
||||
}
|
||||
const resultInfo = await phoneVerify(
|
||||
toRaw({
|
||||
phone: formData.mobile,
|
||||
smscode: formData.smscode,
|
||||
})
|
||||
);
|
||||
if (resultInfo.success) {
|
||||
Object.assign(accountInfo, {
|
||||
username: resultInfo.result.username,
|
||||
phone: formData.mobile,
|
||||
smscode: formData.smscode,
|
||||
});
|
||||
activeKey.value = 2;
|
||||
setTimeout(()=>{
|
||||
pwdFormRef.value.resetFields();
|
||||
},300)
|
||||
} else {
|
||||
notification.error({
|
||||
message: '错误提示',
|
||||
description: resultInfo.message || t('sys.api.networkExceptionMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成修改密码
|
||||
*/
|
||||
async function finishedPwd() {
|
||||
if (!pwdFormData.password) {
|
||||
createMessage.warn(t('sys.login.passwordPlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!pwdFormData.confirmPassword) {
|
||||
createMessage.warn(t('sys.login.confirmPassword'));
|
||||
return;
|
||||
}
|
||||
if (pwdFormData.password !== pwdFormData.confirmPassword) {
|
||||
createMessage.warn(t('sys.login.diffPwd'));
|
||||
return;
|
||||
}
|
||||
const resultInfo = await passwordChange(
|
||||
toRaw({
|
||||
username: accountInfo.username,
|
||||
password: pwdFormData.password,
|
||||
smscode: accountInfo.smscode,
|
||||
phone: accountInfo.phone,
|
||||
})
|
||||
);
|
||||
if (resultInfo.success) {
|
||||
accountInfo.password = pwdFormData.password;
|
||||
//修改密码
|
||||
activeKey.value = 3;
|
||||
} else {
|
||||
//错误提示
|
||||
createErrorModal({
|
||||
title: t('sys.api.errorTip'),
|
||||
content: resultInfo.message || t('sys.api.networkExceptionMsg'),
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 下一步
|
||||
*/
|
||||
function nextStepClick() {
|
||||
if (unref(activeKey) == 1) {
|
||||
handleNext();
|
||||
} else if (unref(activeKey) == 2) {
|
||||
finishedPwd();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 去登录
|
||||
*/
|
||||
function toLogin() {
|
||||
emit('success', { username: accountInfo.username, password: accountInfo.password });
|
||||
initForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回
|
||||
*/
|
||||
function goBack() {
|
||||
emit('go-back');
|
||||
initForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机验证码
|
||||
*/
|
||||
async function getLoginCode() {
|
||||
if (!formData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD }).catch((res) =>{
|
||||
if(res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE){
|
||||
openCaptchaModal(true, {});
|
||||
}
|
||||
});
|
||||
//update-end---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
if (result) {
|
||||
const TIME_COUNT = 60;
|
||||
if (!unref(timer)) {
|
||||
timeRuning.value = TIME_COUNT;
|
||||
showInterval.value = false;
|
||||
timer.value = setInterval(() => {
|
||||
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
|
||||
timeRuning.value = timeRuning.value - 1;
|
||||
} else {
|
||||
showInterval.value = true;
|
||||
clearInterval(unref(timer));
|
||||
timer.value = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单
|
||||
*/
|
||||
function initForm() {
|
||||
activeKey.value = 1;
|
||||
Object.assign(formData, { phone: '', smscode: '' });
|
||||
Object.assign(pwdFormData, { password: '', confirmPassword: '' });
|
||||
Object.assign(accountInfo, {});
|
||||
if(unref(timer)){
|
||||
clearInterval(unref(timer));
|
||||
timer.value = null;
|
||||
showInterval.value = true;
|
||||
}
|
||||
setTimeout(()=>{
|
||||
formRef.value.resetFields();
|
||||
},300)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initForm,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '/@/assets/loginmini/style/home.less';
|
||||
@import '/@/assets/loginmini/style/base.less';
|
||||
</style>
|
||||
573
jeecgboot-vue3/src/views/system/loginmini/MiniLogin.vue
Normal file
573
jeecgboot-vue3/src/views/system/loginmini/MiniLogin.vue
Normal file
@ -0,0 +1,573 @@
|
||||
<template>
|
||||
<div :class="prefixCls" class="login-background-img">
|
||||
<AppLocalePicker class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
|
||||
<AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
|
||||
<div class="aui-logo" v-if="!getIsMobile">
|
||||
<div>
|
||||
<h3>
|
||||
<img :src="logoImg" alt="jeecg" />
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="aui-phone-logo">
|
||||
<img :src="logoImg" alt="jeecg" />
|
||||
</div>
|
||||
<div v-show="type === 'login'">
|
||||
<div class="aui-content">
|
||||
<div class="aui-container">
|
||||
<div class="aui-form">
|
||||
<div class="aui-image">
|
||||
<div class="aui-image-text">
|
||||
<img :src="adTextImg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formBox">
|
||||
<div class="aui-formWell">
|
||||
<div class="aui-flex aui-form-nav investment_title">
|
||||
<div class="aui-flex-box" :class="activeIndex === 'accountLogin' ? 'activeNav on' : ''" @click="loginClick('accountLogin')"
|
||||
>{{ t('sys.login.signInFormTitle') }}
|
||||
</div>
|
||||
<div class="aui-flex-box" :class="activeIndex === 'phoneLogin' ? 'activeNav on' : ''" @click="loginClick('phoneLogin')"
|
||||
>{{ t('sys.login.mobileSignInFormTitle') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-form-box" style="height: 180px">
|
||||
<a-form ref="loginRef" :model="formData" v-if="activeIndex === 'accountLogin'" @keyup.enter.native="loginHandleClick">
|
||||
<div class="aui-account">
|
||||
<div class="aui-inputClear">
|
||||
<i class="icon icon-code"></i>
|
||||
<a-form-item>
|
||||
<a-input class="fix-auto-fill" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="aui-inputClear">
|
||||
<i class="icon icon-password"></i>
|
||||
<a-form-item>
|
||||
<a-input class="fix-auto-fill" type="password" :placeholder="t('sys.login.password')" v-model:value="formData.password" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="aui-inputClear">
|
||||
<i class="icon icon-code"></i>
|
||||
<a-form-item>
|
||||
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.inputCode')" v-model:value="formData.inputCode" />
|
||||
</a-form-item>
|
||||
<div class="aui-code">
|
||||
<img v-if="randCodeData.requestCodeSuccess" :src="randCodeData.randCodeImage" @click="handleChangeCheckCode" />
|
||||
<img v-else style="margin-top: 2px; max-width: initial" :src="codeImg" @click="handleChangeCheckCode" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex">
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-choice">
|
||||
<a-input class="fix-auto-fill" type="checkbox" v-model:value="rememberMe" />
|
||||
<span style="margin-left: 5px">{{ t('sys.login.rememberMe') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-forget">
|
||||
<a @click="forgetHandelClick"> {{ t('sys.login.forgetPassword') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
<a-form v-else ref="phoneFormRef" :model="phoneFormData" @keyup.enter.native="loginHandleClick">
|
||||
<div class="aui-account phone">
|
||||
<div class="aui-inputClear phoneClear">
|
||||
<a-input class="fix-auto-fill" :placeholder="t('sys.login.mobile')" v-model:value="phoneFormData.mobile" />
|
||||
</div>
|
||||
<div class="aui-inputClear">
|
||||
<a-input class="fix-auto-fill" :maxlength="6" :placeholder="t('sys.login.smsCode')" v-model:value="phoneFormData.smscode" />
|
||||
<div v-if="showInterval" class="aui-code" @click="getLoginCode">
|
||||
<a>{{ t('component.countdown.normalText') }}</a>
|
||||
</div>
|
||||
<div v-else class="aui-code">
|
||||
<span class="aui-get-code code-shape">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="aui-formButton">
|
||||
<div class="aui-flex">
|
||||
<a-button :loading="loginLoading" class="aui-link-login" type="primary" @click="loginHandleClick">
|
||||
{{ t('sys.login.loginButton') }}</a-button>
|
||||
</div>
|
||||
<div class="aui-flex">
|
||||
<a class="aui-linek-code aui-flex-box" @click="codeHandleClick">{{ t('sys.login.qrSignInFormTitle') }}</a>
|
||||
</div>
|
||||
<div class="aui-flex">
|
||||
<a class="aui-linek-code aui-flex-box" @click="registerHandleClick">{{ t('sys.login.registerButton') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-form @keyup.enter.native="loginHandleClick">
|
||||
<div class="aui-flex aui-third-text">
|
||||
<div class="aui-flex-box aui-third-border">
|
||||
<span>{{ t('sys.login.otherSignIn') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a title="github" @click="onThirdLogin('github')"><GithubFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a title="企业微信" @click="onThirdLogin('wechat_enterprise')"><icon-font class="item-icon" type="icon-qiyeweixin3" /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-third-login">
|
||||
<a title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="type === 'forgot'" :class="`${prefixCls}-form`">
|
||||
<MiniForgotpad ref="forgotRef" @go-back="goBack" @success="handleSuccess" />
|
||||
</div>
|
||||
<div v-show="type === 'register'" :class="`${prefixCls}-form`">
|
||||
<MiniRegister ref="registerRef" @go-back="goBack" @success="handleSuccess" />
|
||||
</div>
|
||||
<div v-show="type === 'codeLogin'" :class="`${prefixCls}-form`">
|
||||
<MiniCodelogin ref="codeRef" @go-back="goBack" @success="handleSuccess" />
|
||||
</div>
|
||||
<!-- 第三方登录相关弹框 -->
|
||||
<ThirdModal ref="thirdModalRef"></ThirdModal>
|
||||
|
||||
<!-- 图片验证码弹窗 -->
|
||||
<CaptchaModal @register="captchaRegisterModal" @ok="getLoginCode" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="login-mini">
|
||||
import { getCaptcha, getCodeInfo } from '/@/api/sys/user';
|
||||
import { computed, onMounted, reactive, ref, toRaw, unref } from 'vue';
|
||||
import codeImg from '/@/assets/images/checkcode.png';
|
||||
import { Rule } from '/@/components/Form';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { SmsEnum } from '/@/views/sys/login/useLogin';
|
||||
import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
|
||||
import MiniForgotpad from './MiniForgotpad.vue';
|
||||
import MiniRegister from './MiniRegister.vue';
|
||||
import MiniCodelogin from './MiniCodelogin.vue';
|
||||
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
|
||||
import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
|
||||
import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
|
||||
import { useLocaleStore } from '/@/store/modules/locale';
|
||||
import { useDesign } from "/@/hooks/web/useDesign";
|
||||
import { useAppInject } from "/@/hooks/web/useAppInject";
|
||||
import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
|
||||
import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
|
||||
import { useModal } from "@/components/Modal";
|
||||
import { ExceptionEnum } from "@/enums/exceptionEnum";
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
|
||||
});
|
||||
const { prefixCls } = useDesign('mini-login');
|
||||
const { notification, createMessage } = useMessage();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const localeStore = useLocaleStore();
|
||||
const showLocale = localeStore.getShowPicker;
|
||||
const randCodeData = reactive<any>({
|
||||
randCodeImage: '',
|
||||
requestCodeSuccess: false,
|
||||
checkKey: null,
|
||||
});
|
||||
const rememberMe = ref<string>('0');
|
||||
//手机号登录还是账号登录
|
||||
const activeIndex = ref<string>('accountLogin');
|
||||
const type = ref<string>('login');
|
||||
//账号登录表单字段
|
||||
const formData = reactive<any>({
|
||||
inputCode: '',
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
});
|
||||
//手机登录表单字段
|
||||
const phoneFormData = reactive<any>({
|
||||
mobile: '',
|
||||
smscode: '',
|
||||
});
|
||||
const loginRef = ref();
|
||||
//第三方登录弹窗
|
||||
const thirdModalRef = ref();
|
||||
//扫码登录
|
||||
const codeRef = ref();
|
||||
//是否显示获取验证码
|
||||
const showInterval = ref<boolean>(true);
|
||||
//60s
|
||||
const timeRuning = ref<number>(60);
|
||||
//定时器
|
||||
const timer = ref<any>(null);
|
||||
//忘记密码
|
||||
const forgotRef = ref();
|
||||
//注册
|
||||
const registerRef = ref();
|
||||
const loginLoading = ref<boolean>(false);
|
||||
const { getIsMobile } = useAppInject();
|
||||
const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
|
||||
defineProps({
|
||||
sessionTimeout: {
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
function handleChangeCheckCode() {
|
||||
formData.inputCode = '';
|
||||
|
||||
randCodeData.checkKey = 1629428467008;
|
||||
getCodeInfo(randCodeData.checkKey).then((res) => {
|
||||
randCodeData.randCodeImage = res;
|
||||
randCodeData.requestCodeSuccess = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换登录方式
|
||||
*/
|
||||
function loginClick(type) {
|
||||
activeIndex.value = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号或者手机登录
|
||||
*/
|
||||
async function loginHandleClick() {
|
||||
if (unref(activeIndex) === 'accountLogin') {
|
||||
accountLogin();
|
||||
} else {
|
||||
//手机号登录
|
||||
phoneLogin();
|
||||
}
|
||||
}
|
||||
|
||||
async function accountLogin() {
|
||||
if (!formData.username) {
|
||||
createMessage.warn(t('sys.login.accountPlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.password) {
|
||||
createMessage.warn(t('sys.login.passwordPlaceholder'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { userInfo } = await userStore.login(
|
||||
toRaw({
|
||||
password: formData.password,
|
||||
username: formData.username,
|
||||
captcha: formData.inputCode,
|
||||
checkKey: randCodeData.checkKey,
|
||||
mode: 'none', //不要默认的错误提示
|
||||
})
|
||||
);
|
||||
if (userInfo) {
|
||||
notification.success({
|
||||
message: t('sys.login.loginSuccessTitle'),
|
||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('sys.api.errorTip'),
|
||||
description: error.message || t('sys.login.networkExceptionMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
handleChangeCheckCode();
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号登录
|
||||
*/
|
||||
async function phoneLogin() {
|
||||
if (!phoneFormData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!phoneFormData.smscode) {
|
||||
createMessage.warn(t('sys.login.smsPlaceholder'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { userInfo }: any = await userStore.phoneLogin({
|
||||
mobile: phoneFormData.mobile,
|
||||
captcha: phoneFormData.smscode,
|
||||
mode: 'none', //不要默认的错误提示
|
||||
});
|
||||
if (userInfo) {
|
||||
notification.success({
|
||||
message: t('sys.login.loginSuccessTitle'),
|
||||
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('sys.api.errorTip'),
|
||||
description: error.message || t('sys.login.networkExceptionMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机验证码
|
||||
*/
|
||||
async function getLoginCode() {
|
||||
if (!phoneFormData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
const result = await getCaptcha({ mobile: phoneFormData.mobile, smsmode: SmsEnum.FORGET_PASSWORD }).catch((res) =>{
|
||||
if(res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE){
|
||||
openCaptchaModal(true, {});
|
||||
}
|
||||
});
|
||||
//update-end---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
if (result) {
|
||||
const TIME_COUNT = 60;
|
||||
if (!unref(timer)) {
|
||||
timeRuning.value = TIME_COUNT;
|
||||
showInterval.value = false;
|
||||
timer.value = setInterval(() => {
|
||||
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
|
||||
timeRuning.value = timeRuning.value - 1;
|
||||
} else {
|
||||
showInterval.value = true;
|
||||
clearInterval(unref(timer));
|
||||
timer.value = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
* @param type
|
||||
*/
|
||||
function onThirdLogin(type) {
|
||||
thirdModalRef.value.onThirdLogin(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 忘记密码
|
||||
*/
|
||||
function forgetHandelClick() {
|
||||
type.value = 'forgot';
|
||||
setTimeout(() => {
|
||||
forgotRef.value.initForm();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回登录页面
|
||||
*/
|
||||
function goBack() {
|
||||
activeIndex.value = 'accountLogin';
|
||||
type.value = 'login';
|
||||
}
|
||||
|
||||
/**
|
||||
* 忘记密码/注册账号回调事件
|
||||
* @param value
|
||||
*/
|
||||
function handleSuccess(value) {
|
||||
Object.assign(formData, value);
|
||||
Object.assign(phoneFormData, { mobile: "", smscode: "" });
|
||||
type.value = 'login';
|
||||
activeIndex.value = 'accountLogin';
|
||||
handleChangeCheckCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
function registerHandleClick() {
|
||||
type.value = 'register';
|
||||
setTimeout(() => {
|
||||
registerRef.value.initForm();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
function codeHandleClick() {
|
||||
type.value = 'codeLogin';
|
||||
setTimeout(() => {
|
||||
codeRef.value.initFrom();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//加载验证码
|
||||
handleChangeCheckCode();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '/@/assets/loginmini/style/home.less';
|
||||
@import '/@/assets/loginmini/style/base.less';
|
||||
|
||||
:deep(.ant-input:focus) {
|
||||
box-shadow: none;
|
||||
}
|
||||
.aui-get-code {
|
||||
float: right;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
background: #ffffff;
|
||||
color: #1573e9;
|
||||
border-radius: 100px;
|
||||
padding: 5px 16px;
|
||||
margin: 7px;
|
||||
border: 1px solid #1573e9;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.aui-get-code:hover {
|
||||
color: #1573e9;
|
||||
}
|
||||
|
||||
.code-shape {
|
||||
border-color: #dadada !important;
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
:deep(.jeecg-dark-switch){
|
||||
position:absolute;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.aui-link-login{
|
||||
height: 42px;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 8px;
|
||||
flex: 1;
|
||||
color: #fff;
|
||||
}
|
||||
.aui-phone-logo{
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
width: 60px;
|
||||
top:2px;
|
||||
z-index: 4;
|
||||
}
|
||||
.top-3{
|
||||
top: 0.45rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-mini-login';
|
||||
@dark-bg: #293146;
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.@{prefix-cls} {
|
||||
background-color: @dark-bg !important;
|
||||
background-image: none;
|
||||
|
||||
&::before {
|
||||
background-image: url(/@/assets/svg/login-bg-dark.svg);
|
||||
}
|
||||
.aui-inputClear{
|
||||
background-color: #232a3b !important;
|
||||
}
|
||||
.ant-input,
|
||||
.ant-input-password {
|
||||
background-color: #232a3b !important;
|
||||
}
|
||||
|
||||
.ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
|
||||
border: 1px solid #4a5569 !important;
|
||||
}
|
||||
|
||||
&-form {
|
||||
background: @dark-bg !important;
|
||||
}
|
||||
|
||||
.app-iconify {
|
||||
color: #fff !important;
|
||||
}
|
||||
.aui-inputClear input,.aui-input-line input,.aui-choice{
|
||||
color: #c9d1d9 !important;
|
||||
}
|
||||
|
||||
.aui-formBox{
|
||||
background-color: @dark-bg !important;
|
||||
}
|
||||
.aui-third-text span{
|
||||
background-color: @dark-bg !important;
|
||||
}
|
||||
.aui-form-nav .aui-flex-box{
|
||||
color: #c9d1d9 !important;
|
||||
}
|
||||
|
||||
.aui-formButton .aui-linek-code{
|
||||
background: @dark-bg !important;
|
||||
color: white !important;
|
||||
}
|
||||
.aui-code-line{
|
||||
border-left: none !important;
|
||||
}
|
||||
.ant-checkbox-inner,.aui-success h3{
|
||||
border-color: #c9d1d9;
|
||||
}
|
||||
//update-begin---author:wangshuai ---date:20230828 for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
|
||||
&-sign-in-way {
|
||||
.anticon {
|
||||
font-size: 22px !important;
|
||||
color: #888 !important;
|
||||
cursor: pointer !important;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20230828 for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
|
||||
}
|
||||
|
||||
input.fix-auto-fill,
|
||||
.fix-auto-fill input {
|
||||
-webkit-text-fill-color: #c9d1d9 !important;
|
||||
box-shadow: inherit !important;
|
||||
}
|
||||
|
||||
.ant-divider-inner-text {
|
||||
font-size: 12px !important;
|
||||
color: @text-color-secondary !important;
|
||||
}
|
||||
.aui-third-login a{
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
278
jeecgboot-vue3/src/views/system/loginmini/MiniRegister.vue
Normal file
278
jeecgboot-vue3/src/views/system/loginmini/MiniRegister.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div class="aui-content">
|
||||
<div class="aui-container">
|
||||
<div class="aui-form">
|
||||
<div class="aui-image">
|
||||
<div class="aui-image-text">
|
||||
<img :src="jeecgAdTextImg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formBox">
|
||||
<div class="aui-formWell">
|
||||
<a-form ref="formRef" :model="formData">
|
||||
<div class="aui-flex aui-form-nav aui-clear-left" style="padding-bottom: 21px">
|
||||
<div class="aui-flex-box activeNav on">{{t('sys.login.signUpFormTitle')}}</div>
|
||||
</div>
|
||||
<div class="aui-form-box">
|
||||
<div class="aui-account aui-account-line">
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<Icon class="aui-icon" icon="ant-design:user-outlined"/>
|
||||
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<Icon class="aui-icon" icon="ant-design:mobile-outlined"/>
|
||||
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.mobile')" v-model:value="formData.mobile" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<Icon class="aui-icon" icon="ant-design:mail-outlined"/>
|
||||
<a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
|
||||
<div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{t('component.countdown.normalText')}}</div>
|
||||
<div v-else class="aui-code-line">{{t('component.countdown.sendText',[unref(timeRuning)])}}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<Icon class="aui-icon" icon="ant-design:lock-outlined"/>
|
||||
<a-input class="fix-auto-fill" :type="pwdIndex==='close'?'password':'text'" :placeholder="t('sys.login.password')" v-model:value="formData.password" />
|
||||
<div class="aui-eye">
|
||||
<img :src="eyeKImg" alt="开启" v-if="pwdIndex==='open'" @click="pwdClick('close')" />
|
||||
<img :src="eyeGImg" alt="关闭" v-else-if="pwdIndex==='close'" @click="pwdClick('open')" />
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<div class="aui-input-line">
|
||||
<Icon class="aui-icon" icon="ant-design:lock-outlined"/>
|
||||
<a-input class="fix-auto-fill" :type="confirmPwdIndex==='close'?'password':'text'" :placeholder="t('sys.login.confirmPassword')" v-model:value="formData.confirmPassword" />
|
||||
<div class="aui-eye">
|
||||
<img :src="eyeKImg" alt="开启" v-if="confirmPwdIndex==='open'" @click="confirmPwdClick('close')" />
|
||||
<img :src="eyeGImg" alt="关闭" v-else-if="confirmPwdIndex==='close'" @click="confirmPwdClick('open')" />
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item name="policy">
|
||||
<div class="aui-flex">
|
||||
<div class="aui-flex-box">
|
||||
<div class="aui-choice">
|
||||
<a-checkbox v-model:checked="formData.policy" />
|
||||
<span style="color: #1b90ff;margin-left: 4px">{{ t('sys.login.policy') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aui-formButton">
|
||||
<div class="aui-flex">
|
||||
<a class="aui-link-login aui-flex-box" @click="registerHandleClick"> {{ t('sys.login.registerButton') }}</a>
|
||||
</div>
|
||||
<div class="aui-flex">
|
||||
<a class="aui-linek-code aui-flex-box" @click="goBackHandleClick">{{ t('sys.login.backSignIn') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图片验证码弹窗 -->
|
||||
<CaptchaModal @register="captchaRegisterModal" @ok="getLoginCode" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="mini-register">
|
||||
import { ref, reactive, unref, toRaw } from 'vue';
|
||||
import { getCaptcha, register } from '/@/api/sys/user';
|
||||
import { SmsEnum } from '/@/views/sys/login/useLogin';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
|
||||
import jeecgAdTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
|
||||
import eyeKImg from '/@/assets/loginmini/icon/icon-eye-k.png';
|
||||
import eyeGImg from '/@/assets/loginmini/icon/icon-eye-g.png';
|
||||
import { useI18n } from "/@/hooks/web/useI18n";
|
||||
import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
|
||||
import { useModal } from "@/components/Modal";
|
||||
import { ExceptionEnum } from "@/enums/exceptionEnum";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notification, createErrorModal, createMessage } = useMessage();
|
||||
const emit = defineEmits(['go-back', 'success', 'register']);
|
||||
const formRef = ref();
|
||||
const formData = reactive<any>({
|
||||
username: '',
|
||||
mobile: '',
|
||||
smscode: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
policy: false,
|
||||
});
|
||||
//是否显示获取验证码
|
||||
const showInterval = ref<boolean>(true);
|
||||
//60s
|
||||
const timeRuning = ref<number>(60);
|
||||
//定时器
|
||||
const timer = ref<any>(null);
|
||||
//密码眼睛打开关闭
|
||||
const pwdIndex = ref<string>('close');
|
||||
//确认密码眼睛打开关闭
|
||||
const confirmPwdIndex = ref<string>('close');
|
||||
const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
|
||||
|
||||
/**
|
||||
* 返回
|
||||
*/
|
||||
function goBackHandleClick() {
|
||||
emit('go-back');
|
||||
initForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机验证码
|
||||
*/
|
||||
async function getLoginCode() {
|
||||
if (!formData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.REGISTER }).catch((res) =>{
|
||||
if(res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE){
|
||||
openCaptchaModal(true, {});
|
||||
}
|
||||
});
|
||||
//update-end---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
|
||||
if (result) {
|
||||
const TIME_COUNT = 60;
|
||||
if (!unref(timer)) {
|
||||
timeRuning.value = TIME_COUNT;
|
||||
showInterval.value = false;
|
||||
timer.value = setInterval(() => {
|
||||
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
|
||||
timeRuning.value = timeRuning.value - 1;
|
||||
} else {
|
||||
showInterval.value = true;
|
||||
clearInterval(unref(timer));
|
||||
timer.value = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerHandleClick() {
|
||||
if (!formData.username) {
|
||||
createMessage.warn(t('sys.login.accountPlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.mobile) {
|
||||
createMessage.warn(t('sys.login.mobilePlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.smscode) {
|
||||
createMessage.warn(t('sys.login.smsPlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.password) {
|
||||
createMessage.warn(t('sys.login.passwordPlaceholder'));
|
||||
return;
|
||||
}
|
||||
if (!formData.confirmPassword) {
|
||||
createMessage.warn(t('sys.login.confirmPassword'));
|
||||
return;
|
||||
}
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
createMessage.warn(t('sys.login.diffPwd'));
|
||||
return;
|
||||
}
|
||||
if(!formData.policy){
|
||||
createMessage.warn(t('sys.login.policyPlaceholder'));
|
||||
return;
|
||||
}
|
||||
registerAccount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册账号
|
||||
*/
|
||||
async function registerAccount() {
|
||||
try {
|
||||
const resultInfo = await register(
|
||||
toRaw({
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
phone: formData.mobile,
|
||||
smscode: formData.smscode,
|
||||
})
|
||||
);
|
||||
if (resultInfo && resultInfo.data.success) {
|
||||
notification.success({
|
||||
description: resultInfo.data.message || t('sys.api.registerMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
emit('success', { username: formData.username, password: formData.password });
|
||||
initForm();
|
||||
} else {
|
||||
notification.warning({
|
||||
message: t('sys.api.errorTip'),
|
||||
description: resultInfo.data.message || t('sys.api.networkExceptionMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('sys.api.errorTip'),
|
||||
description: error.message || t('sys.api.networkExceptionMsg'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单
|
||||
*/
|
||||
function initForm() {
|
||||
Object.assign(formData,{username:'',mobile: '', smscode: '', password: '', confirmPassword: '', policy: false})
|
||||
if(!unref(timer)){
|
||||
showInterval.value = true;
|
||||
clearInterval(unref(timer));
|
||||
timer.value = null;
|
||||
}
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码打开或关闭
|
||||
* @param value
|
||||
*/
|
||||
function pwdClick(value) {
|
||||
pwdIndex.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认密码打开或关闭
|
||||
* @param value
|
||||
*/
|
||||
function confirmPwdClick(value) {
|
||||
confirmPwdIndex.value = value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initForm
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '/@/assets/loginmini/style/home.less';
|
||||
@import '/@/assets/loginmini/style/base.less';
|
||||
.aui-input-line .aui-icon{
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
</style>
|
||||
87
jeecgboot-vue3/src/views/system/loginmini/OAuth2Login.vue
Normal file
87
jeecgboot-vue3/src/views/system/loginmini/OAuth2Login.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div> </div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { isOAuth2AppEnv, sysOAuth2Login } from '/@/views/sys/login/useLogin';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { router } from '/@/router';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import {getTenantId} from "/@/utils/auth";
|
||||
|
||||
const isOAuth = ref<boolean>(isOAuth2AppEnv());
|
||||
const env = ref<any>({ thirdApp: false, wxWork: false, dingtalk: false });
|
||||
const { currentRoute } = useRouter();
|
||||
const route = currentRoute.value;
|
||||
if (!isOAuth2AppEnv()) {
|
||||
router.replace({ path: PageEnum.BASE_LOGIN, query: route.query });
|
||||
}
|
||||
|
||||
if (isOAuth.value) {
|
||||
checkEnv();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前的环境
|
||||
*/
|
||||
function checkEnv() {
|
||||
// 判断当时是否是企业微信环境
|
||||
if (/wxwork/i.test(navigator.userAgent)) {
|
||||
env.value.thirdApp = true;
|
||||
env.value.wxWork = true;
|
||||
}
|
||||
// 判断当时是否是钉钉环境
|
||||
if (/dingtalk/i.test(navigator.userAgent)) {
|
||||
env.value.thirdApp = true;
|
||||
env.value.dingtalk = true;
|
||||
}
|
||||
doOAuth2Login();
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行OAuth2登录操作
|
||||
*/
|
||||
function doOAuth2Login() {
|
||||
if (env.value.thirdApp) {
|
||||
// 判断是否携带了Token,是就说明登录成功
|
||||
if (route.query.oauth2LoginToken) {
|
||||
let token = route.query.oauth2LoginToken;
|
||||
//执行登录操作
|
||||
thirdLogin({ token, thirdType: route.query.thirdType,tenantId: getTenantId });
|
||||
} else if (env.value.wxWork) {
|
||||
sysOAuth2Login('wechat_enterprise');
|
||||
} else if (env.value.dingtalk) {
|
||||
sysOAuth2Login('dingtalk');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
* @param params
|
||||
*/
|
||||
function thirdLogin(params) {
|
||||
const userStore = useUserStore();
|
||||
const { notification } = useMessage();
|
||||
const { t } = useI18n();
|
||||
userStore.ThirdLogin(params).then((res) => {
|
||||
if (res && res.userInfo) {
|
||||
notification.success({
|
||||
message: t('sys.login.loginSuccessTitle'),
|
||||
description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: t('sys.login.errorTip'),
|
||||
description: ((res.response || {}).data || {}).message || res.message || t('sys.login.networkExceptionMsg'),
|
||||
duration: 4,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
122
jeecgboot-vue3/src/views/system/menu/DataRuleList.vue
Normal file
122
jeecgboot-vue3/src/views/system/menu/DataRuleList.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" title="数据权限规则" :width="adaptiveWidth">
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" @click="handleCreate"> 新增</a-button>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicDrawer>
|
||||
<DataRuleModal @register="registerModal" @success="reload" :permissionId="permissionId" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import DataRuleModal from './DataRuleModal.vue';
|
||||
import { dataRuleColumns, dataRuleSearchFormSchema } from './menu.data';
|
||||
import { dataRuleList, deleteRule } from './menu.api';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
|
||||
const permissionId = ref('');
|
||||
const { adaptiveWidth } = useDrawerAdaptiveWidth();
|
||||
//权限规则model
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerDrawer] = useDrawerInner(async (data) => {
|
||||
permissionId.value = data.id;
|
||||
setProps({ searchInfo: { permissionId: unref(permissionId) } });
|
||||
reload();
|
||||
});
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 8, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
const [registerTable, { reload, setProps }] = useTable({
|
||||
api: dataRuleList,
|
||||
columns: dataRuleColumns,
|
||||
size: 'small',
|
||||
formConfig: {
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'right',
|
||||
labelCol: {
|
||||
offset: 1,
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 8,
|
||||
xl: 8,
|
||||
xxl: 8,
|
||||
},
|
||||
wrapperCol: {},
|
||||
schemas: dataRuleSearchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
},
|
||||
striped: true,
|
||||
useSearchForm: true,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
showTableSetting: false,
|
||||
canResize: false,
|
||||
immediate: false,
|
||||
actionColumn: {
|
||||
width: 100,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleCreate() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteRule({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
54
jeecgboot-vue3/src/views/system/menu/DataRuleModal.vue
Normal file
54
jeecgboot-vue3/src/views/system/menu/DataRuleModal.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" width="700px" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { dataRuleFormSchema } from './menu.data';
|
||||
import { saveOrUpdateRule } from './menu.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({ permissionId: String });
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: dataRuleFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增规则' : '编辑规则'));
|
||||
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
values.permissionId = props.permissionId;
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateRule(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
141
jeecgboot-vue3/src/views/system/menu/MenuDrawer.vue
Normal file
141
jeecgboot-vue3/src/views/system/menu/MenuDrawer.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" showFooter :width="adaptiveWidth" :title="getTitle" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" class="menuForm" />
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref, useAttrs } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema, ComponentTypes } from './menu.data';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { list, saveOrUpdateMenu } from './menu.api';
|
||||
import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
|
||||
import { useI18n } from "/@/hooks/web/useI18n";
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const { adaptiveWidth } = useDrawerAdaptiveWidth();
|
||||
const attrs = useAttrs();
|
||||
const isUpdate = ref(true);
|
||||
const menuType = ref(0);
|
||||
const isButton = (type) => type === 2;
|
||||
const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate }] = useForm({
|
||||
labelCol: {
|
||||
md: { span: 4 },
|
||||
sm: { span: 6 },
|
||||
},
|
||||
wrapperCol: {
|
||||
md: { span: 20 },
|
||||
sm: { span: 18 },
|
||||
},
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
|
||||
await resetFields();
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
menuType.value = data?.record?.menuType;
|
||||
|
||||
//获取下拉树信息
|
||||
const treeData = await list();
|
||||
updateSchema([
|
||||
{
|
||||
field: 'parentId',
|
||||
// update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
|
||||
componentProps: { treeData: translateMenu(treeData, 'name') },
|
||||
// update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: isButton(unref(menuType)) ? '按钮/权限' : '菜单名称',
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
required: !isButton(unref(menuType)),
|
||||
componentProps: {
|
||||
onChange: (e) => onUrlChange(e.target.value),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 无论新增还是编辑,都可以设置表单值
|
||||
if (typeof data.record === 'object') {
|
||||
let values = { ...data.record };
|
||||
setFieldsValue(values);
|
||||
onUrlChange(values.url);
|
||||
}
|
||||
//按钮类型情况下,编辑时候清除一下地址的校验
|
||||
if (menuType.value == 2) {
|
||||
clearValidate();
|
||||
}
|
||||
//禁用表单
|
||||
setProps({ disabled: !attrs.showFooter });
|
||||
});
|
||||
//获取弹窗标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增菜单' : '编辑菜单'));
|
||||
//提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
// iframe兼容
|
||||
if (ComponentTypes.IFrame === values.component) {
|
||||
values.component = values.frameSrc;
|
||||
}
|
||||
setDrawerProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateMenu(values, unref(isUpdate));
|
||||
closeDrawer();
|
||||
emit('success');
|
||||
} finally {
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/** url 变化时,动态设置组件名称placeholder */
|
||||
function onUrlChange(url) {
|
||||
let placeholder = '';
|
||||
let httpUrl = url;
|
||||
if (url != null && url != '') {
|
||||
if (url.startsWith('/')) {
|
||||
url = url.substring(1);
|
||||
}
|
||||
url = url.replaceAll('/', '-');
|
||||
// 特殊标记
|
||||
url = url.replaceAll(':', '@');
|
||||
placeholder = `${url}`;
|
||||
} else {
|
||||
placeholder = '请输入组件名称';
|
||||
}
|
||||
updateSchema([{ field: 'componentName', componentProps: { placeholder } }]);
|
||||
//update-begin---author:wangshuai ---date:20230204 for:[QQYUN-4058]菜单添加智能化处理------------
|
||||
if (httpUrl != null && httpUrl != '') {
|
||||
if (httpUrl.startsWith('http://') || httpUrl.startsWith('https://')) {
|
||||
setFieldsValue({ component: httpUrl });
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20230204 for:[QQYUN-4058]菜单添加智能化处理------------
|
||||
}
|
||||
|
||||
/**
|
||||
* 2024-03-06
|
||||
* liaozhiyang
|
||||
* 翻译菜单名称
|
||||
*/
|
||||
function translateMenu(data, key) {
|
||||
if (data?.length) {
|
||||
const { t } = useI18n();
|
||||
data.forEach((item) => {
|
||||
if (item[key]) {
|
||||
if (item[key].includes("t('") && t) {
|
||||
item[key] = new Function('t', `return ${item[key]}`)(t);
|
||||
}
|
||||
}
|
||||
if (item.children?.length) {
|
||||
translateMenu(item.children, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
</script>
|
||||
259
jeecgboot-vue3/src/views/system/menu/index.vue
Normal file
259
jeecgboot-vue3/src/views/system/menu/index.vue
Normal file
@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增菜单</a-button>
|
||||
<a-button type="primary" preIcon="ic:round-expand" @click="expandAll">展开全部</a-button>
|
||||
<a-button type="primary" preIcon="ic:round-compress" @click="collapseAll">折叠全部</a-button>
|
||||
|
||||
<a-dropdown v-if="checkedKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined" />
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="ant-design:down-outlined" />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<MenuDrawer @register="registerDrawer" @success="handleSuccess" :showFooter="showFooter" />
|
||||
<DataRuleList @register="registerDrawer1" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="system-menu" setup>
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import MenuDrawer from './MenuDrawer.vue';
|
||||
import DataRuleList from './DataRuleList.vue';
|
||||
import { columns,searchFormSchema } from './menu.data';
|
||||
import { list, deleteMenu, batchDeleteMenu } from './menu.api';
|
||||
import { useDefIndexStore } from "@/store/modules/defIndex";
|
||||
import { useI18n } from "/@/hooks/web/useI18n";
|
||||
|
||||
const checkedKeys = ref<Array<string | number>>([]);
|
||||
const showFooter = ref(true);
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
const [registerDrawer1, { openDrawer: openDataRule }] = useDrawer();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 自定义菜单名称列渲染
|
||||
columns[0].customRender = function ({text, record}) {
|
||||
const isDefIndex = checkDefIndex(record)
|
||||
if (isDefIndex) {
|
||||
text += '(默认首页)'
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
|
||||
if (text.includes("t('") && t) {
|
||||
return new Function('t', `return ${text}`)(t);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
|
||||
return text
|
||||
}
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
tableProps: {
|
||||
title: '菜单列表',
|
||||
api: list,
|
||||
columns: columns,
|
||||
size: 'small',
|
||||
pagination: false,
|
||||
isTreeTable: true,
|
||||
striped: true,
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
formConfig: {
|
||||
// update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
|
||||
labelWidth:60,
|
||||
owProps: { gutter: 24 },
|
||||
// update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
|
||||
schemas: searchFormSchema,
|
||||
autoAdvancedCol: 4,
|
||||
baseColProps: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
|
||||
actionColOptions: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
|
||||
},
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
},
|
||||
},
|
||||
});
|
||||
//注册table数据
|
||||
const [registerTable, { reload, expandAll, collapseAll }] = tableContext;
|
||||
|
||||
/**
|
||||
* 选择列配置
|
||||
*/
|
||||
const rowSelection = {
|
||||
type: 'checkbox',
|
||||
columnWidth: 30,
|
||||
selectedRowKeys: checkedKeys,
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择事件
|
||||
*/
|
||||
function onSelectChange(selectedRowKeys: (string | number)[]) {
|
||||
checkedKeys.value = selectedRowKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleCreate() {
|
||||
showFooter.value = true;
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
showFooter.value = true;
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
showFooter.value = false;
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 添加下级
|
||||
*/
|
||||
function handleAddSub(record) {
|
||||
openDrawer(true, {
|
||||
record: { parentId: record.id, menuType: 1 },
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 数据权限弹窗
|
||||
*/
|
||||
function handleDataRule(record) {
|
||||
openDataRule(true, { id: record.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteMenu({ id: record.id }, reload);
|
||||
}
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteMenu({ ids: checkedKeys.value }, reload);
|
||||
}
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
reload();
|
||||
reloadDefIndex();
|
||||
}
|
||||
|
||||
function onFetchSuccess() {
|
||||
// 演示默认展开所有表项
|
||||
nextTick(expandAll);
|
||||
}
|
||||
|
||||
// --------------- begin 默认首页配置 ------------
|
||||
|
||||
const defIndexStore = useDefIndexStore()
|
||||
|
||||
// 设置默认主页
|
||||
async function handleSetDefIndex(record: Recordable) {
|
||||
defIndexStore.update(record.url, record.component, record.route)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为默认主页
|
||||
* @param record
|
||||
*/
|
||||
function checkDefIndex(record: Recordable) {
|
||||
return defIndexStore.check(record.url)
|
||||
}
|
||||
|
||||
// 重新加载默认首页配置
|
||||
function reloadDefIndex() {
|
||||
try {
|
||||
defIndexStore.query();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
reloadDefIndex()
|
||||
|
||||
// --------------- end 默认首页配置 ------------
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
// {
|
||||
// label: '详情',
|
||||
// onClick: handleDetail.bind(null, record),
|
||||
// },
|
||||
{
|
||||
label: '添加下级',
|
||||
onClick: handleAddSub.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '数据规则',
|
||||
onClick: handleDataRule.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '设为默认首页',
|
||||
onClick: handleSetDefIndex.bind(null, record),
|
||||
ifShow: () => !record.internalOrExternal && record.component && !checkDefIndex(record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
122
jeecgboot-vue3/src/views/system/menu/menu.api.ts
Normal file
122
jeecgboot-vue3/src/views/system/menu/menu.api.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/permission/list',
|
||||
save = '/sys/permission/add',
|
||||
edit = '/sys/permission/edit',
|
||||
delete = '/sys/permission/delete',
|
||||
deleteBatch = '/sys/permission/deleteBatch',
|
||||
ruleList = '/sys/permission/queryPermissionRule',
|
||||
ruleSave = '/sys/permission/addPermissionRule',
|
||||
ruleEdit = '/sys/permission/editPermissionRule',
|
||||
ruleDelete = '/sys/permission/deletePermissionRule',
|
||||
checkPermDuplication = '/sys/permission/checkPermDuplication',
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*/
|
||||
export const deleteMenu = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 批量删除菜单
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteMenu = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 保存或者更新菜单
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateMenu = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
/**
|
||||
* 菜单数据权限列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const dataRuleList = (params) => defHttp.get({ url: Api.ruleList, params });
|
||||
/**
|
||||
* 保存或者更新数据规则
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdateRule = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.ruleEdit : Api.ruleSave;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除数据权限
|
||||
*/
|
||||
export const deleteRule = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.ruleDelete, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 根据code获取字典数值
|
||||
* @param params
|
||||
*/
|
||||
export const ajaxGetDictItems = (params) => defHttp.get({ url: `/sys/dict/getDictItems/${params.code}` });
|
||||
|
||||
/**
|
||||
* 唯一校验
|
||||
* @param params
|
||||
*/
|
||||
export const getCheckPermDuplication = (params) => defHttp.get({ url: Api.checkPermDuplication, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 校验菜单是否存在
|
||||
* @param model
|
||||
* @param schema
|
||||
* @param required
|
||||
*/
|
||||
export const checkPermDuplication=(model, schema, required?)=>{
|
||||
return [
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!required) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (!value && required) {
|
||||
return Promise.reject(`请输入${schema.label}`);
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
getCheckPermDuplication({
|
||||
id: model.id,
|
||||
url:model.url,
|
||||
alwaysShow:model.alwaysShow
|
||||
}).then((res) => {
|
||||
res.success ? resolve() : reject(res.message || '校验失败');
|
||||
}).catch((err) => {
|
||||
reject(err.message || '验证失败');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
420
jeecgboot-vue3/src/views/system/menu/menu.data.ts
Normal file
420
jeecgboot-vue3/src/views/system/menu/menu.data.ts
Normal file
@ -0,0 +1,420 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
import { h } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { duplicateCheck } from '../user/user.api';
|
||||
import { ajaxGetDictItems ,checkPermDuplication } from './menu.api';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
|
||||
const isDir = (type) => type === 0;
|
||||
const isMenu = (type) => type === 1;
|
||||
const isButton = (type) => type === 2;
|
||||
|
||||
// 定义可选择的组件类型
|
||||
export enum ComponentTypes {
|
||||
Default = 'layouts/default/index',
|
||||
IFrame = 'sys/iframe/FrameBlank',
|
||||
}
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '菜单类型',
|
||||
dataIndex: 'menuType',
|
||||
width: 150,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDict(text, 'menu_type');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
width: 50,
|
||||
customRender: ({ record }) => {
|
||||
return h(Icon, { icon: record.icon });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '组件',
|
||||
dataIndex: 'component',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
dataIndex: 'url',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNo',
|
||||
width: 50,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '菜单名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: 'id',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'menuType',
|
||||
label: '菜单类型',
|
||||
component: 'RadioButtonGroup',
|
||||
defaultValue: 0,
|
||||
componentProps: ({ formActionType, formModel }) => {
|
||||
return {
|
||||
options: [
|
||||
{ label: '一级菜单', value: 0 },
|
||||
{ label: '子菜单', value: 1 },
|
||||
{ label: '按钮/权限', value: 2 },
|
||||
],
|
||||
onChange: (e) => {
|
||||
const { updateSchema, clearValidate } = formActionType;
|
||||
const label = isButton(e) ? '按钮/权限' : '菜单名称';
|
||||
//清除校验
|
||||
clearValidate();
|
||||
updateSchema([
|
||||
{
|
||||
field: 'name',
|
||||
label: label,
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
required: !isButton(e),
|
||||
},
|
||||
]);
|
||||
//update-begin---author:wangshuai ---date:20220729 for:[VUEN-1834]只有一级菜单,才默认值,子菜单的时候,清空------------
|
||||
if (isMenu(e) && !formModel.id && (formModel.component=='layouts/default/index' || formModel.component=='layouts/RouteView')) {
|
||||
formModel.component = '';
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20220729 for:[VUEN-1834]只有一级菜单,才默认值,子菜单的时候,清空------------
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '菜单名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'parentId',
|
||||
label: '上级菜单',
|
||||
component: 'TreeSelect',
|
||||
required: true,
|
||||
componentProps: {
|
||||
//update-begin---author:wangshuai ---date:20230829 for:replaceFields已过期,使用fieldNames代替------------
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
key: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
//update-end---author:wangshuai ---date:20230829 for:replaceFields已过期,使用fieldNames代替------------
|
||||
dropdownStyle: {
|
||||
maxHeight: '50vh',
|
||||
},
|
||||
getPopupContainer: (node) => node?.parentNode,
|
||||
},
|
||||
ifShow: ({ values }) => !isDir(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
label: '访问路径',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
//update-begin-author:liusq date:2023-06-06 for: [issues/5008]子表数据权限设置不生效
|
||||
ifShow: ({ values }) => !(values.component === ComponentTypes.IFrame && values.internalOrExternal),
|
||||
//update-begin-author:zyf date:2022-11-02 for: 聚合路由允许路径重复
|
||||
dynamicRules: ({ model, schema,values }) => {
|
||||
return checkPermDuplication(model, schema, values.menuType !== 2?true:false);
|
||||
},
|
||||
//update-end-author:zyf date:2022-11-02 for: 聚合路由允许路径重复
|
||||
//update-end-author:liusq date:2022-06-06 for: [issues/5008]子表数据权限设置不生效
|
||||
},
|
||||
{
|
||||
field: 'component',
|
||||
label: '前端组件',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入前端组件',
|
||||
},
|
||||
defaultValue:'layouts/default/index',
|
||||
required: true,
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'componentName',
|
||||
label: '组件名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入组件名称',
|
||||
},
|
||||
helpMessage: [
|
||||
'此处名称应和vue组件的name属性保持一致。',
|
||||
'组件名称不能重复,主要用于路由缓存功能。',
|
||||
'如果组件名称和vue组件的name属性不一致,则会导致路由缓存失效。',
|
||||
'非必填,留空则会根据访问路径自动生成。',
|
||||
],
|
||||
defaultValue: '',
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'frameSrc',
|
||||
label: 'Iframe地址',
|
||||
component: 'Input',
|
||||
rules: [
|
||||
{ required: true, message: '请输入Iframe地址' },
|
||||
{ type: 'url', message: '请输入正确的url地址' },
|
||||
],
|
||||
ifShow: ({ values }) => !isButton(values.menuType) && values.component === ComponentTypes.IFrame,
|
||||
},
|
||||
{
|
||||
field: 'redirect',
|
||||
label: '默认跳转地址',
|
||||
component: 'Input',
|
||||
ifShow: ({ values }) => isDir(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'perms',
|
||||
label: '授权标识',
|
||||
component: 'Input',
|
||||
ifShow: ({ values }) => isButton(values.menuType),
|
||||
// dynamicRules: ({ model }) => {
|
||||
// return [
|
||||
// {
|
||||
// required: false,
|
||||
// validator: (_, value) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// let params = {
|
||||
// tableName: 'sys_permission',
|
||||
// fieldName: 'perms',
|
||||
// fieldVal: value,
|
||||
// dataId: model.id,
|
||||
// };
|
||||
// duplicateCheck(params)
|
||||
// .then((res) => {
|
||||
// res.success ? resolve() : reject(res.message || '校验失败');
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// reject(err.message || '校验失败');
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
// },
|
||||
// ];
|
||||
// },
|
||||
},
|
||||
{
|
||||
field: 'permsType',
|
||||
label: '授权策略',
|
||||
component: 'RadioGroup',
|
||||
defaultValue: '1',
|
||||
helpMessage: ['可见/可访问(授权后可见/可访问)', '可编辑(未授权时禁用)'],
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '可见/可访问', value: '1' },
|
||||
{ label: '可编辑', value: '2' },
|
||||
],
|
||||
},
|
||||
ifShow: ({ values }) => isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
component: 'RadioGroup',
|
||||
defaultValue: '1',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '有效', value: '1' },
|
||||
{ label: '无效', value: '0' },
|
||||
],
|
||||
},
|
||||
ifShow: ({ values }) => isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'icon',
|
||||
label: '菜单图标',
|
||||
component: 'IconPicker',
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'sortNo',
|
||||
label: '排序',
|
||||
component: 'InputNumber',
|
||||
defaultValue: 1,
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'route',
|
||||
label: '是否路由菜单',
|
||||
component: 'Switch',
|
||||
defaultValue: true,
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'hidden',
|
||||
label: '隐藏路由',
|
||||
component: 'Switch',
|
||||
defaultValue: 0,
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'hideTab',
|
||||
label: '隐藏Tab',
|
||||
component: 'Switch',
|
||||
defaultValue: 0,
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'keepAlive',
|
||||
label: '是否缓存路由',
|
||||
component: 'Switch',
|
||||
defaultValue: false,
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'alwaysShow',
|
||||
label: '聚合路由',
|
||||
component: 'Switch',
|
||||
defaultValue: false,
|
||||
componentProps: {
|
||||
checkedChildren: '是',
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
{
|
||||
field: 'internalOrExternal',
|
||||
label: '打开方式',
|
||||
component: 'Switch',
|
||||
defaultValue: false,
|
||||
componentProps: {
|
||||
checkedChildren: '外部',
|
||||
unCheckedChildren: '内部',
|
||||
},
|
||||
ifShow: ({ values }) => !isButton(values.menuType),
|
||||
},
|
||||
];
|
||||
|
||||
export const dataRuleColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '规则名称',
|
||||
dataIndex: 'ruleName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '规则字段',
|
||||
dataIndex: 'ruleColumn',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '规则值',
|
||||
dataIndex: 'ruleValue',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
export const dataRuleSearchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
// colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
field: 'ruleValue',
|
||||
label: '规则值',
|
||||
component: 'Input',
|
||||
// colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
|
||||
export const dataRuleFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: 'id',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'ruleName',
|
||||
label: '规则名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'ruleColumn',
|
||||
label: '规则字段',
|
||||
component: 'Input',
|
||||
ifShow: ({ values }) => {
|
||||
const ruleConditions = Array.isArray(values.ruleConditions) ? values.ruleConditions[0] : values.ruleConditions;
|
||||
return ruleConditions !== 'USE_SQL_RULES';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ruleConditions',
|
||||
label: '条件规则',
|
||||
required: true,
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: ajaxGetDictItems,
|
||||
params: { code: 'rule_conditions' },
|
||||
labelField: 'text',
|
||||
valueField: 'value',
|
||||
getPopupContainer: (node) => document.body,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ruleValue',
|
||||
label: '规则值',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
component: 'RadioButtonGroup',
|
||||
defaultValue: '1',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '无效', value: '0' },
|
||||
{ label: '有效', value: '1' },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<a-list item-layout="horizontal" :data-source="messageList">
|
||||
<template #loadMore>
|
||||
<div
|
||||
v-if="messageList && messageList.length > 0 && !loadEndStatus && !loadingMoreStatus"
|
||||
:style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }"
|
||||
>
|
||||
<a-button @click="onLoadMore">加载更多</a-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="messageList && messageList.length > 0 && loadEndStatus"
|
||||
:style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }"
|
||||
>
|
||||
没有更多了
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a-rate :value="item.starFlag=='1'?1:0" :count="1" @click="clickStar(item)" style="cursor: pointer" disabled />
|
||||
</template>
|
||||
|
||||
<a-list-item-meta :description="item.createTime">
|
||||
<template #title>
|
||||
<div style="position: relative">
|
||||
<!-- <span style="display: inline-block; position: absolute; left: -16px">
|
||||
<exclamation-outlined v-if="noRead(item)" title="未读消息" style="color: red" />
|
||||
</span>-->
|
||||
|
||||
<span>{{ getMsgCategory(item) }}</span>
|
||||
<span v-if="item.busType == 'bpm'" class="bpm-cuiban-content" v-html="item.msgContent">
|
||||
</span>
|
||||
<a-tooltip v-else>
|
||||
<template #title>
|
||||
<div v-html="item.msgContent"></div>
|
||||
</template>
|
||||
{{ item.titile }}
|
||||
</a-tooltip>
|
||||
|
||||
<a @click="showMessageDetail(item)" style="margin-left: 16px">查看详情</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<template v-if="item.busType=='email'">
|
||||
<a-badge dot v-if="noRead(item)" class="msg-no-read">
|
||||
<a-avatar style="background: #79919d"><mail-outlined style="font-size: 16px" title="未读消息"/></a-avatar>
|
||||
</a-badge>
|
||||
<a-avatar v-else style="background: #79919d"><mail-outlined style="font-size: 16px"/></a-avatar>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.busType=='bpm_task'">
|
||||
<a-badge dot v-if="noRead(item)" class="msg-no-read">
|
||||
<a-avatar style="background: #79919d"><interaction-outlined style="font-size: 16px" title="未读消息"/></a-avatar>
|
||||
</a-badge>
|
||||
<a-avatar v-else style="background: #79919d"><interaction-outlined style="font-size: 16px"/></a-avatar>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.busType=='bpm'">
|
||||
<a-badge dot v-if="noRead(item)" class="msg-no-read">
|
||||
<a-avatar style="background: #79919d"><alert-outlined style="font-size: 16px" title="未读消息"/></a-avatar>
|
||||
</a-badge>
|
||||
<a-avatar v-else style="background: #79919d"><alert-outlined style="font-size: 16px"/></a-avatar>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<a-badge dot v-if="noRead(item)" class="msg-no-read">
|
||||
<a-avatar style="background: #79919d"><bell-filled style="font-size: 16px" title="未读消息"/></a-avatar>
|
||||
</a-badge>
|
||||
<a-avatar v-else style="background: #79919d"><bell-filled style="font-size: 16px" /></a-avatar>
|
||||
</template>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { FilterOutlined, CloseOutlined, BellFilled, ExclamationOutlined, MailOutlined,InteractionOutlined, AlertOutlined } from '@ant-design/icons-vue';
|
||||
import { useSysMessage, useMessageHref } from './useSysMessage';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'SysMessageList',
|
||||
components: {
|
||||
FilterOutlined,
|
||||
CloseOutlined,
|
||||
BellFilled,
|
||||
ExclamationOutlined,
|
||||
MailOutlined,
|
||||
InteractionOutlined,
|
||||
AlertOutlined
|
||||
},
|
||||
props:{
|
||||
star: {
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits:['close', 'detail'],
|
||||
setup(props, {emit}){
|
||||
const { messageList,loadEndStatus,loadingMoreStatus,onLoadMore,noRead, getMsgCategory, searchParams, reset, loadData, updateStarMessage } = useSysMessage();
|
||||
|
||||
function reload(params){
|
||||
let { fromUser, rangeDateKey, rangeDate } = params;
|
||||
searchParams.fromUser = fromUser||'';
|
||||
searchParams.rangeDateKey = rangeDateKey||'';
|
||||
searchParams.rangeDate = rangeDate||[];
|
||||
if(props.star===true){
|
||||
searchParams.starFlag = '1'
|
||||
}else{
|
||||
searchParams.starFlag = ''
|
||||
}
|
||||
reset();
|
||||
loadData();
|
||||
}
|
||||
|
||||
function clickStar(item){
|
||||
console.log(item)
|
||||
updateStarMessage(item);
|
||||
if(item.starFlag=='1'){
|
||||
item.starFlag = '0'
|
||||
}else{
|
||||
item.starFlag = '1'
|
||||
}
|
||||
}
|
||||
|
||||
const { goPage } = useMessageHref(emit);
|
||||
|
||||
function showMessageDetail(record){
|
||||
record.readFlag = '1'
|
||||
goPage(record);
|
||||
emit('close', record.id)
|
||||
}
|
||||
|
||||
return {
|
||||
messageList,
|
||||
loadEndStatus,
|
||||
loadingMoreStatus,
|
||||
onLoadMore,
|
||||
noRead,
|
||||
getMsgCategory,
|
||||
reload,
|
||||
clickStar,
|
||||
showMessageDetail
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.msg-no-read{
|
||||
:deep(.ant-badge-dot){
|
||||
top: 5px;
|
||||
right: 3px;
|
||||
}
|
||||
}
|
||||
:deep(.bpm-cuiban-content) p{
|
||||
display: inherit;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/** QQYUN-5688 【样式】鼠标放上去怎么不是手 */
|
||||
:deep(.ant-rate-disabled){
|
||||
.ant-rate-star{
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
:canFullscreen="false"
|
||||
:draggable="false"
|
||||
:closable="false"
|
||||
@register="registerModal"
|
||||
wrapClassName="sys-msg-modal"
|
||||
:width="800"
|
||||
:footer="null"
|
||||
destroyOnClose
|
||||
>
|
||||
<template #title>
|
||||
<div class="sys-msg-modal-title">
|
||||
<div class="title"></div>
|
||||
<div class="ant-tabs-nav-wrap">
|
||||
<div class="ant-tabs-nav-scroll">
|
||||
<div class="ant-tabs-nav ant-tabs-nav-animated">
|
||||
<div>
|
||||
<div
|
||||
@click="(e) => handleChangeTab(e, 'all')"
|
||||
role="tab"
|
||||
aria-disabled="false"
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab"
|
||||
:class="{ 'ant-tabs-tab-active': activeKey == 'all' }"
|
||||
>
|
||||
全部消息
|
||||
</div>
|
||||
<div
|
||||
@click="(e) => handleChangeTab(e, 'star')"
|
||||
role="tab"
|
||||
aria-disabled="false"
|
||||
aria-selected="true"
|
||||
class="ant-tabs-tab"
|
||||
:class="{ 'ant-tabs-tab-active': activeKey == 'star' }"
|
||||
>
|
||||
标星消息
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
|
||||
:style="{
|
||||
transform: activeKey == 'all' ? 'translate3d(130px, 0px, 0px)' : 'translate3d(215px, 0px, 0px)',
|
||||
display: 'block',
|
||||
width: '88px',
|
||||
height: '1px'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 头部图标 -->
|
||||
<div class="icon-right">
|
||||
<div class="icons">
|
||||
<a-popover placement="bottomRight" :overlayStyle="{ width: '400px' }" trigger="click" v-model:open="showSearch">
|
||||
<template #content>
|
||||
<div>
|
||||
<span class="search-label">回复、提到我的人?:</span>
|
||||
<span style="display: inline-block;">
|
||||
<div v-if="searchParams.fromUser" class="selected-user">
|
||||
<span>{{searchParams.realname}}</span>
|
||||
<span class="clear-user-icon"><close-outlined style="font-size: 12px" @click="clearSearchParamsUser"/></span>
|
||||
</div>
|
||||
<a-button v-else type="dashed" shape="circle" @click="openSelectPerson">
|
||||
<plus-outlined />
|
||||
</a-button>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
<div class="search-date">
|
||||
<div class="date-label">时间:</div>
|
||||
<div class="date-tags">
|
||||
<div class="tags-container">
|
||||
<div v-for="item in dateTags" :class="item.active == true ? 'tag active' : 'tag'" @click="handleClickDateTag(item)">{{
|
||||
item.text
|
||||
}}</div>
|
||||
</div>
|
||||
<div class="cust-range-date" v-if="showRangeDate">
|
||||
<a-range-picker v-model:value="searchRangeDate" @change="handleChangeSearchDate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span v-if="conditionStr" class="anticon filtera">
|
||||
<filter-outlined />
|
||||
<span style="font-size:12px;margin-left: 3px">{{conditionStr}}</span>
|
||||
<span style="display: flex;margin:0 5px;"><close-outlined style="font-size: 12px" @click="clearAll"/></span>
|
||||
</span>
|
||||
<filter-outlined v-else/>
|
||||
</a-popover>
|
||||
<close-outlined @click="closeModal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="sys-message-card">
|
||||
<a-tabs :activeKey="activeKey" center @tabClick="handleChangePanel" animated>
|
||||
<template #renderTabBar>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<a-tab-pane tab="全部消息" key="all" forceRender>
|
||||
<sys-message-list ref="allMessageRef" @close="hrefThenClose" @detail="showDetailModal"/>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 标星 -->
|
||||
<a-tab-pane tab="标星消息" key="star" forceRender>
|
||||
<sys-message-list ref="starMessageRef" star @close="hrefThenClose" @detail="showDetailModal"/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</BasicModal>
|
||||
|
||||
<user-select-modal isRadioSelection :showButton="false" labelKey="realname" rowKey="username" @register="regModal" @getSelectResult="getSelectedUser"></user-select-modal>
|
||||
|
||||
<DetailModal @register="registerDetail" :zIndex="1001"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BasicModal, useModalInner, useModal } from '/@/components/Modal';
|
||||
import { FilterOutlined, CloseOutlined, BellFilled, ExclamationOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import JSelectUser from '/@/components/Form/src/jeecg/components/JSelectUser.vue';
|
||||
import { ref, unref, reactive, computed } from 'vue';
|
||||
// import SysMessageList from './SysMessageList.vue'
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue'
|
||||
import DetailModal from '/@/views/monitor/mynews/DetailModal.vue';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
export default {
|
||||
name: 'SysMessageModal',
|
||||
components: {
|
||||
BasicModal,
|
||||
FilterOutlined,
|
||||
CloseOutlined,
|
||||
BellFilled,
|
||||
ExclamationOutlined,
|
||||
JSelectUser,
|
||||
SysMessageList: createAsyncComponent(() => import('./SysMessageList.vue')),
|
||||
UserSelectModal,
|
||||
PlusOutlined,
|
||||
DetailModal
|
||||
},
|
||||
emits:['register', 'refresh'],
|
||||
setup(_p, {emit}) {
|
||||
const allMessageRef = ref();
|
||||
const starMessageRef = ref();
|
||||
const activeKey = ref('all');
|
||||
function handleChangeTab(e, key) {
|
||||
activeKey.value = key;
|
||||
loadData();
|
||||
}
|
||||
function handleChangePanel(key) {
|
||||
activeKey.value = key;
|
||||
}
|
||||
|
||||
// 查询区域存储值
|
||||
const searchParams = reactive({
|
||||
fromUser: '',
|
||||
realname: '',
|
||||
rangeDateKey: '7day',
|
||||
rangeDate: [],
|
||||
});
|
||||
|
||||
function loadData(){
|
||||
let params = {
|
||||
fromUser: searchParams.fromUser,
|
||||
rangeDateKey: searchParams.rangeDateKey,
|
||||
rangeDate: searchParams.rangeDate,
|
||||
}
|
||||
if(activeKey.value == 'all'){
|
||||
getRefPromise(allMessageRef).then(() => {
|
||||
allMessageRef.value.reload(params);
|
||||
});
|
||||
}else{
|
||||
starMessageRef.value.reload(params);
|
||||
}
|
||||
}
|
||||
|
||||
//useModalInner
|
||||
const [registerModal, { closeModal }] = useModalInner(async (data) => {
|
||||
//每次弹窗打开 加载最新的数据
|
||||
loadData();
|
||||
});
|
||||
|
||||
const showSearch = ref(false);
|
||||
|
||||
function handleChangeSearchPerson(value, a) {
|
||||
console.log('选择改变', value, a);
|
||||
showSearch.value = true;
|
||||
}
|
||||
|
||||
const dateTags = reactive([
|
||||
{ key: 'jt', text: '今天', active: false },
|
||||
{ key: 'zt', text: '昨天', active: false },
|
||||
{ key: 'qt', text: '前天', active: false },
|
||||
{ key: 'bz', text: '本周', active: false },
|
||||
{ key: 'sz', text: '上周', active: false },
|
||||
{ key: 'by', text: '本月', active: false },
|
||||
{ key: 'sy', text: '上月', active: false },
|
||||
{ key: '7day', text: '七日', active: true },
|
||||
{ key: 'zdy', text: '自定义', active: false },
|
||||
]);
|
||||
function handleClickDateTag(item) {
|
||||
for (let a of dateTags) {
|
||||
if(a.key != item.key){
|
||||
a.active = false;
|
||||
}
|
||||
}
|
||||
item.active = !item.active;
|
||||
if(item.active == false){
|
||||
searchParams.rangeDateKey = ''
|
||||
}else{
|
||||
searchParams.rangeDateKey = item.key;
|
||||
}
|
||||
if (item.key == 'zdy') {
|
||||
// 自定义日期查询走的是 handleChangeSearchDate
|
||||
if(item.active == false){
|
||||
searchParams.rangeDate = []
|
||||
loadData();
|
||||
}
|
||||
}else{
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
const showRangeDate = computed(() => {
|
||||
let temp = dateTags.filter((i) => i.active == true);
|
||||
if (temp && temp.length > 0) {
|
||||
if (temp[0].text == '自定义') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const searchRangeDate = ref([]);
|
||||
function handleChangeSearchDate(_value, dateStringArray) {
|
||||
searchParams.rangeDate = [...dateStringArray]
|
||||
loadData();
|
||||
}
|
||||
|
||||
function hrefThenClose(id){
|
||||
emit('refresh', id)
|
||||
// closeModal();
|
||||
}
|
||||
|
||||
// 有查询条件值的时候显示该字符串
|
||||
const conditionStr = computed(()=>{
|
||||
const { fromUser, rangeDateKey, realname } = searchParams;
|
||||
if(!fromUser && !rangeDateKey){
|
||||
return ''
|
||||
}
|
||||
let arr = [];
|
||||
if(fromUser){
|
||||
arr.push(realname)
|
||||
}
|
||||
if(rangeDateKey){
|
||||
let rangDates = dateTags.filter(item=>item.key == rangeDateKey);
|
||||
if(rangDates && rangDates.length>0){
|
||||
arr.push(rangDates[0].text)
|
||||
}
|
||||
}
|
||||
return arr.join('、')
|
||||
});
|
||||
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
|
||||
function getSelectedUser(options, value){
|
||||
if(options && options.length>0){
|
||||
searchParams.fromUser = value
|
||||
searchParams.realname = options[0].label;
|
||||
}
|
||||
}
|
||||
|
||||
function openSelectPerson(){
|
||||
openModal(true, {})
|
||||
}
|
||||
|
||||
function clearSearchParamsUser(){
|
||||
searchParams.fromUser = ''
|
||||
searchParams.realname = ''
|
||||
}
|
||||
|
||||
function getRefPromise(componentRef) {
|
||||
return new Promise((resolve) => {
|
||||
(function next() {
|
||||
let ref = componentRef.value;
|
||||
if (ref) {
|
||||
resolve(ref);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
next();
|
||||
}, 100);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function clearAll(){
|
||||
searchParams.fromUser='';
|
||||
searchParams.realname='';
|
||||
searchParams.rangeDateKey='';
|
||||
searchParams.rangeDate=[];
|
||||
for (let a of dateTags) {
|
||||
a.active = false;
|
||||
}
|
||||
loadData();
|
||||
}
|
||||
|
||||
const [registerDetail, { openModal: openDetailModal }] = useModal();
|
||||
function showDetailModal(record){
|
||||
openDetailModal(true, {record: unref(record), isUpdate: true})
|
||||
}
|
||||
return {
|
||||
conditionStr,
|
||||
regModal,
|
||||
getSelectedUser,
|
||||
openSelectPerson,
|
||||
clearSearchParamsUser,
|
||||
clearAll,
|
||||
|
||||
registerModal,
|
||||
activeKey,
|
||||
handleChangePanel,
|
||||
handleChangeTab,
|
||||
|
||||
showSearch,
|
||||
searchParams,
|
||||
handleChangeSearchPerson,
|
||||
dateTags,
|
||||
handleClickDateTag,
|
||||
showRangeDate,
|
||||
searchRangeDate,
|
||||
handleChangeSearchDate,
|
||||
closeModal,
|
||||
hrefThenClose,
|
||||
|
||||
allMessageRef,
|
||||
starMessageRef,
|
||||
registerDetail,
|
||||
showDetailModal
|
||||
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@keyframes move22{
|
||||
0%{ transform:translateY(0px); }
|
||||
100%{transform:translateY(600px);}
|
||||
}
|
||||
|
||||
|
||||
.sys-msg-modal {
|
||||
.ant-modal-header {
|
||||
padding-bottom: 0;
|
||||
padding-top: 6px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.ant-modal-body {
|
||||
height: 550px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ant-modal {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: calc(100% - 600px);
|
||||
/* animation-name: move22;
|
||||
animation-duration:0.8s;
|
||||
animation-timing-function:ease;*/
|
||||
}
|
||||
}
|
||||
.sys-msg-modal-title {
|
||||
.title {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
.icon-right {
|
||||
display: inline-block;
|
||||
width: 220px;
|
||||
vertical-align: top;
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
> span.anticon {
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> span.filtera{
|
||||
//background-color: #d3eafd;
|
||||
background-color: #eff1f2;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
height: 27px;
|
||||
padding-top: 7px;
|
||||
margin-top: 3px;
|
||||
line-height: 25px;
|
||||
//color: #2196f3;
|
||||
display: flex;
|
||||
|
||||
>span.anticon{
|
||||
height: auto;
|
||||
line-height: 9px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
.ant-tabs-nav-wrap {
|
||||
display: inline-block;
|
||||
width: calc(100% - 340px);
|
||||
.ant-tabs-tab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ant-tabs-tab+.ant-tabs-tab {
|
||||
margin: 0 0 0 32px;
|
||||
}
|
||||
.ant-tabs-ink-bar {
|
||||
background: @primary-color;
|
||||
}
|
||||
}
|
||||
.ant-tabs-nav-scroll {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.sys-message-card {
|
||||
}
|
||||
|
||||
.search-label {
|
||||
font-weight: 500;
|
||||
font-size: 14px !important;
|
||||
color: #757575 !important;
|
||||
display: inline-block;
|
||||
margin-right: 15px !important;
|
||||
}
|
||||
.search-date {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
margin-top: 15px;
|
||||
.date-label {
|
||||
margin-top: 4px;
|
||||
font-weight: 500;
|
||||
font-size: 14px !important;
|
||||
color: #757575 !important;
|
||||
margin-right: 15px !important;
|
||||
}
|
||||
|
||||
.date-tags {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-ms-flex: 1;
|
||||
.tags-container {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex-wrap: wrap;
|
||||
.tag {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 17px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
background-color: #d3eafd !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-user{
|
||||
background: #f5f5f5;
|
||||
padding: 2px 14px;
|
||||
border-radius: 30px;
|
||||
.clear-user-icon{
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,239 @@
|
||||
import { ref, reactive } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { getDictItemsByCode } from '/@/utils/dict/index';
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
import { useTabs } from '/@/hooks/web/useTabs';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import {useMessage} from "/@/hooks/web/useMessage";
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
const queryMessageList = (params) => {
|
||||
const url = '/sys/annountCement/vue3List';
|
||||
return defHttp.get({ url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取消息列表数据
|
||||
*/
|
||||
export function useSysMessage() {
|
||||
const { createMessage } = useMessage();
|
||||
const rangeDateArray = getDictItemsByCode('rangeDate');
|
||||
console.log('+++++++++++++++++++++');
|
||||
console.log('rangeDateArray', rangeDateArray);
|
||||
console.log('+++++++++++++++++++++');
|
||||
|
||||
const messageList = ref<any[]>([]);
|
||||
const pageNo = ref(1)
|
||||
let pageSize = 10;
|
||||
|
||||
const searchParams = reactive({
|
||||
fromUser: '',
|
||||
rangeDateKey: '',
|
||||
rangeDate: [],
|
||||
starFlag: ''
|
||||
});
|
||||
|
||||
|
||||
function getQueryParams() {
|
||||
let { fromUser, rangeDateKey, rangeDate, starFlag } = searchParams;
|
||||
let params = {
|
||||
fromUser,
|
||||
starFlag,
|
||||
rangeDateKey,
|
||||
beginDate: '',
|
||||
endDate: '',
|
||||
pageNo: pageNo.value,
|
||||
pageSize
|
||||
};
|
||||
if (rangeDateKey == 'zdy') {
|
||||
params.beginDate = rangeDate[0]+' 00:00:00';
|
||||
params.endDate = rangeDate[1]+' 23:59:59';
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// 数据是否加载完了
|
||||
const loadEndStatus = ref(false);
|
||||
|
||||
//请求数据
|
||||
async function loadData() {
|
||||
if(loadEndStatus.value === true){
|
||||
return;
|
||||
}
|
||||
let params = getQueryParams();
|
||||
const data = await queryMessageList(params);
|
||||
console.log('获取结果', data);
|
||||
if(!data || data.length<=0){
|
||||
loadEndStatus.value = true;
|
||||
return;
|
||||
}
|
||||
if(data.length<pageSize){
|
||||
loadEndStatus.value = true;
|
||||
}
|
||||
pageNo.value = pageNo.value+1
|
||||
let temp:any[] = messageList.value;
|
||||
temp.push(...data);
|
||||
messageList.value = temp;
|
||||
}
|
||||
|
||||
//重置
|
||||
function reset(){
|
||||
messageList.value = []
|
||||
pageNo.value = 1;
|
||||
loadEndStatus.value = false;
|
||||
}
|
||||
|
||||
//标星
|
||||
async function updateStarMessage(item){
|
||||
const url = '/sys/sysAnnouncementSend/edit';
|
||||
let starFlag = '1';
|
||||
if(item.starFlag==starFlag){
|
||||
starFlag = '0'
|
||||
}
|
||||
const params = {
|
||||
starFlag,
|
||||
id: item.sendId
|
||||
}
|
||||
//update-begin-author:taoyan date:2023-3-6 for: QQYUN-4491【应用】一些小问题 4、标星不需要提示吧
|
||||
const data:any = await defHttp.put({url, params}, {isTransformResponse: false});
|
||||
if(data.success === true){
|
||||
}else{
|
||||
createMessage.warning(data.message)
|
||||
}
|
||||
//update-end-author:taoyan date:2023-3-6 for: QQYUN-4491【应用】一些小问题 4、标星不需要提示吧
|
||||
}
|
||||
|
||||
|
||||
const loadingMoreStatus = ref(false);
|
||||
async function onLoadMore() {
|
||||
loadingMoreStatus.value = true;
|
||||
await loadData();
|
||||
loadingMoreStatus.value = false;
|
||||
}
|
||||
|
||||
function noRead(item) {
|
||||
if (item.readFlag === '1') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 消息类型
|
||||
function getMsgCategory(item) {
|
||||
if(item.busType=='email'){
|
||||
return '邮件提醒:';
|
||||
} else if(item.busType=='bpm'){
|
||||
return '流程催办:';
|
||||
} else if(item.busType=='bpm_cc'){
|
||||
return '流程抄送:';
|
||||
}else if(item.busType=='bpm_task'){
|
||||
return '流程任务:';
|
||||
} else if (item.msgCategory == '2') {
|
||||
return '系统消息:';
|
||||
} else if (item.msgCategory == '1') {
|
||||
return '通知公告:';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return {
|
||||
messageList,
|
||||
reset,
|
||||
loadData,
|
||||
loadEndStatus,
|
||||
searchParams,
|
||||
updateStarMessage,
|
||||
|
||||
onLoadMore,
|
||||
noRead,
|
||||
getMsgCategory,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于消息跳转
|
||||
*/
|
||||
export function useMessageHref(emit, props){
|
||||
const messageHrefArray: any[] = getDictItemsByCode('messageHref');
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const rt = useRoute();
|
||||
const { close: closeTab, closeSameRoute } = useTabs();
|
||||
// const defaultPath = '/monitor/mynews';
|
||||
//const bpmPath = '/task/handle/'
|
||||
|
||||
async function goPage(record, openModalFun?){
|
||||
if(!record.busType || record.busType == 'msg_node'){
|
||||
if(!openModalFun){
|
||||
// 从首页的消息通知跳转
|
||||
await goPageFromOuter(record);
|
||||
}else{
|
||||
// 从消息页面列表点击详情查看 直接打开modal
|
||||
openModalFun()
|
||||
}
|
||||
}else{
|
||||
await goPageWithBusType(record)
|
||||
}
|
||||
/* busId: "1562035005173587970"
|
||||
busType: "email"
|
||||
openPage: "modules/eoa/email/modals/EoaEmailInForm"
|
||||
openType: "component"*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据busType不同跳转不同页面
|
||||
* @param record
|
||||
*/
|
||||
async function goPageWithBusType(record){
|
||||
const { busType, busId, msgAbstract } = record;
|
||||
let temp = messageHrefArray.filter(item=>item.value === busType);
|
||||
if(!temp || temp.length==0){
|
||||
console.error('当前业务类型不识别', busType);
|
||||
return;
|
||||
}
|
||||
let path = temp[0].text;
|
||||
path = path.replace('{DETAIL_ID}', busId)
|
||||
//固定参数 detailId 用于查询表单数据
|
||||
let query:any = {
|
||||
detailId: busId
|
||||
};
|
||||
// 额外参数处理
|
||||
if(msgAbstract){
|
||||
try {
|
||||
let json = JSON.parse(msgAbstract);
|
||||
Object.keys(json).map(k=>{
|
||||
query[k] = json[k]
|
||||
});
|
||||
}catch (e) {
|
||||
console.error('msgAbstract参数不是JSON格式', msgAbstract)
|
||||
}
|
||||
}
|
||||
// 跳转路由
|
||||
appStore.setMessageHrefParams(query);
|
||||
if(rt.path.indexOf(path)>=0){
|
||||
await closeTab();
|
||||
await router.replace({ path: path, query:{ time: new Date().getTime() } });
|
||||
}else{
|
||||
closeSameRoute(path)
|
||||
await router.push({ path: path });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从首页的消息通知跳转消息列表打开modal
|
||||
* @param record
|
||||
*/
|
||||
async function goPageFromOuter(record){
|
||||
//没有定义业务类型 直接跳转我的消息页面
|
||||
emit('detail', record)
|
||||
}
|
||||
|
||||
return {
|
||||
goPage
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<BasicDrawer @register="registerModal" title="详情" :width="600" v-bind="$attrs" @ok="closeDrawer">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { formSchemas } from './manage.data';
|
||||
|
||||
// 声明 emits
|
||||
const emit = defineEmits(['register']);
|
||||
// 注册 form
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: formSchemas,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
// 注册 modal
|
||||
const [registerModal, { closeDrawer }] = useDrawerInner(async (data) => {
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...data.record });
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,5 @@
|
||||
//noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-message-manage';
|
||||
|
||||
.@{prefix-cls} {
|
||||
}
|
||||
129
jeecgboot-vue3/src/views/system/message/manage/index.vue
Normal file
129
jeecgboot-vue3/src/views/system/message/manage/index.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-dropdown v-if="showBatchBtn">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="onDeleteBatch">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作</span>
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<ManageDrawer @register="registerDrawer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="message-manage">
|
||||
import { unref, computed } from 'vue';
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import ManageDrawer from './ManageDrawer.vue';
|
||||
import { Api, list, deleteBatch } from './manage.api';
|
||||
import { columns, searchFormSchema } from './manage.data';
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'message-manage',
|
||||
tableProps: {
|
||||
title: '消息中心模板列表数据',
|
||||
api: list,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
url: Api.exportXls,
|
||||
name: '消息中心模板列表',
|
||||
},
|
||||
importConfig: {
|
||||
url: Api.importXls,
|
||||
success: () => reload(),
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setLoading }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const showBatchBtn = computed(() => selectedRowKeys.value.length > 0);
|
||||
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
|
||||
function onDetail(record) {
|
||||
openDrawer(true, { record: record });
|
||||
}
|
||||
|
||||
function onDelete(record) {
|
||||
if (record) {
|
||||
doDeleteDepart([record.id], false);
|
||||
}
|
||||
}
|
||||
|
||||
async function onDeleteBatch() {
|
||||
try {
|
||||
await doDeleteDepart(selectedRowKeys);
|
||||
selectedRowKeys.value = [];
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ids 批量删除
|
||||
* @param idListRef array
|
||||
* @param confirm 是否显示确认提示框
|
||||
*/
|
||||
async function doDeleteDepart(idListRef, confirm = true) {
|
||||
const idList = unref(idListRef);
|
||||
if (idList.length > 0) {
|
||||
try {
|
||||
setLoading(true);
|
||||
await deleteBatch({ ids: idList.join(',') }, confirm);
|
||||
await reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [{ label: '详情', onClick: onDetail.bind(null, record) }];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认要删除吗?',
|
||||
confirm: onDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import 'index';
|
||||
</style>
|
||||
52
jeecgboot-vue3/src/views/system/message/manage/manage.api.ts
Normal file
52
jeecgboot-vue3/src/views/system/message/manage/manage.api.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { unref } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
export enum Api {
|
||||
list = '/sys/message/sysMessage/list',
|
||||
delete = '/sys/message/sysMessage/delete',
|
||||
deleteBatch = '/sys/message/sysMessage/deleteBatch',
|
||||
exportXls = 'sys/message/sysMessage/exportXls',
|
||||
importXls = 'sys/message/sysMessage/importExcel',
|
||||
save = '/sys/message/sysMessage/add',
|
||||
edit = '/sys/message/sysMessage/edit',
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param confirm
|
||||
*/
|
||||
export const deleteBatch = (params, confirm = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const doDelete = () => {
|
||||
resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }));
|
||||
};
|
||||
if (confirm) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '删除',
|
||||
content: '确定要删除吗?',
|
||||
onOk: () => doDelete(),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
doDelete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更改消息模板
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
if (unref(isUpdate)) {
|
||||
return defHttp.put({ url: Api.edit, params });
|
||||
} else {
|
||||
return defHttp.post({ url: Api.save, params });
|
||||
}
|
||||
};
|
||||
134
jeecgboot-vue3/src/views/system/message/manage/manage.data.ts
Normal file
134
jeecgboot-vue3/src/views/system/message/manage/manage.data.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '消息标题',
|
||||
dataIndex: 'esTitle',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '发送内容',
|
||||
dataIndex: 'esContent',
|
||||
width: 200,
|
||||
// slots: { customRender: 'esContent' },
|
||||
},
|
||||
{
|
||||
title: '接收人',
|
||||
dataIndex: 'esReceiver',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '发送次数',
|
||||
dataIndex: 'esSendNum',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '发送状态',
|
||||
dataIndex: 'esSendStatus_dictText',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
dataIndex: 'esSendTime',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '发送方式',
|
||||
dataIndex: 'esType_dictText',
|
||||
width: 120,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '消息标题',
|
||||
field: 'esTitle',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '发送状态',
|
||||
field: 'esSendStatus',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'msgSendStatus',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '发送方式',
|
||||
field: 'esType',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'messageType',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchemas: FormSchema[] = [
|
||||
{
|
||||
label: 'ID',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '消息标题',
|
||||
field: 'esTitle',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '发送内容',
|
||||
field: 'esContent',
|
||||
component: 'InputTextArea',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '发送参数',
|
||||
field: 'esParam',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
|
||||
{
|
||||
label: '接收人',
|
||||
field: 'esReceiver',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '发送方式',
|
||||
field: 'esType',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { disabled: true, dictCode: 'messageType' },
|
||||
},
|
||||
{
|
||||
label: '发送时间',
|
||||
field: 'esSendTime',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '发送状态',
|
||||
field: 'esSendStatus',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: { disabled: true, dictCode: 'msgSendStatus' },
|
||||
},
|
||||
{
|
||||
label: '发送次数',
|
||||
field: 'esSendNum',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '发送失败原因',
|
||||
field: 'esResult',
|
||||
component: 'Input',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
component: 'InputTextArea',
|
||||
componentProps: { readOnly: true },
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<BasicModal @register="registerModal" :title="title" :width="800" v-bind="$attrs" @ok="onSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { formSchemas } from './template.data';
|
||||
import { saveOrUpdate } from './template.api';
|
||||
|
||||
// 声明 emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const title = ref<string>('');
|
||||
const isUpdate = ref<boolean>(false);
|
||||
// 注册 form
|
||||
//update-begin---author:wangshuai ---date:20221123 for:[VUEN-2807]消息模板加一个查看功能------------
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema, setProps }] = useForm({
|
||||
//update-end---author:wangshuai ---date:20221123 for:[VUEN-2807]消息模板加一个查看功能--------------z
|
||||
schemas: formSchemas,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
// 注册 modal
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
setModalProps({confirmLoading: false,showCancelBtn:!!data?.showFooter,showOkBtn:!!data?.showFooter});
|
||||
isUpdate.value = unref(data.isUpdate);
|
||||
title.value = unref(data.title);
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...data.record });
|
||||
// 隐藏底部时禁用整个表单
|
||||
setProps({ disabled: !data?.showFooter })
|
||||
});
|
||||
|
||||
//表单提交事件
|
||||
async function onSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
// 提交表单
|
||||
await saveOrUpdate(values, isUpdate);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<BasicModal @register="registerModal" title="发送测试" :width="800" v-bind="$attrs" @ok="onSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { sendTestFormSchemas } from './template.data';
|
||||
import { sendMessageTest } from './template.api';
|
||||
|
||||
// 声明 emits
|
||||
const emit = defineEmits(['register']);
|
||||
// 注册 form
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: sendTestFormSchemas,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
// 注册 modal
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...unref(data.record) });
|
||||
});
|
||||
|
||||
//表单提交事件
|
||||
async function onSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
// 提交表单
|
||||
await sendMessageTest(values);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,5 @@
|
||||
//noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-message-template';
|
||||
|
||||
.@{prefix-cls} {
|
||||
}
|
||||
209
jeecgboot-vue3/src/views/system/message/template/index.vue
Normal file
209
jeecgboot-vue3/src/views/system/message/template/index.vue
Normal file
@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAdd">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="showBatchBtn">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="onDeleteBatch">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
<span>删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
<span>批量操作</span>
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<TemplateModal @register="registerModal" @success="reload" />
|
||||
<TemplateTestModal @register="registerTestModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="message-template">
|
||||
import { unref, computed, toRaw } from 'vue';
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import TemplateModal from './TemplateModal.vue';
|
||||
import TemplateTestModal from './TemplateTestModal.vue';
|
||||
import { Api, saveOrUpdate, list, deleteBatch } from './template.api';
|
||||
import { columns, searchFormSchema } from './template.data';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, onExportXls, onImportXls, tableContext } = useListPage({
|
||||
designScope: 'message-template',
|
||||
tableProps: {
|
||||
title: '消息中心模板列表数据',
|
||||
api: list,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
url: Api.exportXls,
|
||||
name: '消息中心模板列表',
|
||||
},
|
||||
importConfig: {
|
||||
url: Api.importXls,
|
||||
success: () => reload(),
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setLoading }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerTestModal, testModal] = useModal();
|
||||
const showBatchBtn = computed(() => selectedRowKeys.value.length > 0);
|
||||
|
||||
function onAdd() {
|
||||
openModal(true, {
|
||||
title: '新增消息模板',
|
||||
isUpdate: false,
|
||||
showFooter: true,
|
||||
record: {},
|
||||
});
|
||||
}
|
||||
|
||||
function onEdit(record) {
|
||||
if (record.useStatus === '1') {
|
||||
createMessage.warning('此模板已被应用,禁止编辑!');
|
||||
return;
|
||||
}
|
||||
openModal(true, {
|
||||
title: '修改消息模板',
|
||||
isUpdate: true,
|
||||
record: record,
|
||||
showFooter: true,
|
||||
});
|
||||
}
|
||||
|
||||
function onDelete(record) {
|
||||
if (record) {
|
||||
//update-begin-author:taoyan date:2022-7-14 for: VUEN-1652【bug】应用状态下不允许删除
|
||||
if(record.useStatus == '1'){
|
||||
createMessage.warning('该模板已被应用禁止删除!');
|
||||
return;
|
||||
}
|
||||
//update-end-author:taoyan date:2022-7-14 for: VUEN-1652【bug】应用状态下不允许删除
|
||||
doDeleteDepart([record.id], false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ids 批量删除
|
||||
* @param idListRef array
|
||||
* @param confirm 是否显示确认提示框
|
||||
*/
|
||||
async function doDeleteDepart(idListRef, confirm = true) {
|
||||
const idList = unref(idListRef);
|
||||
if (idList.length > 0) {
|
||||
try {
|
||||
setLoading(true);
|
||||
await deleteBatch({ ids: idList.join(',') }, confirm);
|
||||
await reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onDeleteBatch() {
|
||||
try {
|
||||
//update-begin-author:taoyan date:2022-7-14 for: VUEN-1652【bug】应用状态下不允许删除
|
||||
let arr = toRaw(selectedRows.value);
|
||||
let temp = arr.filter(item=>item.useStatus=='1')
|
||||
if(temp.length>0){
|
||||
createMessage.warning('选中的模板已被应用禁止删除!');
|
||||
return;
|
||||
}
|
||||
//update-end-author:taoyan date:2022-7-14 for: VUEN-1652【bug】应用状态下不允许删除
|
||||
await doDeleteDepart(selectedRowKeys);
|
||||
selectedRowKeys.value = [];
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息测试
|
||||
function onSendTest(record) {
|
||||
testModal.openModal(true, { record });
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
//update-begin---author:wangshuai ---date:20221123 for:[VUEN-2807]消息模板加一个查看功能------------
|
||||
return [{ label: '查看', onClick: handleDetail.bind(null, record)}, { label: '编辑', onClick: onEdit.bind(null, record) }];
|
||||
//update-end---author:wangshuai ---date:20221123 for:[VUEN-2807]消息模板加一个查看功能------------
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '应用', onClick: handleUse.bind(null, record) },
|
||||
{ label: '停用', onClick: handleNotUse.bind(null, record) },
|
||||
{ label: '发送测试', onClick: onSendTest.bind(null, record) },
|
||||
{
|
||||
label: '删除',
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: '确认要删除吗?',
|
||||
confirm: onDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用
|
||||
*/
|
||||
async function handleUse(record) {
|
||||
let param = { id: record.id, useStatus: '1' };
|
||||
await saveOrUpdate(param, true);
|
||||
await reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用
|
||||
*/
|
||||
async function handleNotUse(record) {
|
||||
let param = { id: record.id, useStatus: '0' };
|
||||
await saveOrUpdate(param, true);
|
||||
await reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @param record
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
openModal(true,{
|
||||
title: "消息模板详情",
|
||||
isUpdate: true,
|
||||
showFooter: false,
|
||||
record:record
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import 'index';
|
||||
</style>
|
||||
@ -0,0 +1,60 @@
|
||||
import { unref } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
export enum Api {
|
||||
list = '/sys/message/sysMessageTemplate/list',
|
||||
delete = '/sys/message/sysMessageTemplate/delete',
|
||||
deleteBatch = '/sys/message/sysMessageTemplate/deleteBatch',
|
||||
exportXls = 'sys/message/sysMessageTemplate/exportXls',
|
||||
importXls = 'sys/message/sysMessageTemplate/importExcel',
|
||||
save = '/sys/message/sysMessageTemplate/add',
|
||||
edit = '/sys/message/sysMessageTemplate/edit',
|
||||
// 发送测试
|
||||
send = '/sys/message/sysMessageTemplate/sendMsg',
|
||||
}
|
||||
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param confirm
|
||||
*/
|
||||
export const deleteBatch = (params, confirm = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const doDelete = () => {
|
||||
resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }));
|
||||
};
|
||||
if (confirm) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '删除',
|
||||
content: '确定要删除吗?',
|
||||
onOk: () => doDelete(),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
doDelete();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更改消息模板
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
if (unref(isUpdate)) {
|
||||
return defHttp.put({ url: Api.edit, params });
|
||||
} else {
|
||||
return defHttp.post({ url: Api.save, params });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送消息测试
|
||||
* @param params
|
||||
*/
|
||||
export const sendMessageTest = (params) => defHttp.post({ url: Api.send, params });
|
||||
@ -0,0 +1,185 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { rules } from '/@/utils/helper/validator';
|
||||
import { filterDictTextByCache } from '/@/utils/dict/JDictSelectUtil';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '模板标题',
|
||||
dataIndex: 'templateName',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '模板编码',
|
||||
dataIndex: 'templateCode',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '通知模板',
|
||||
dataIndex: 'templateContent',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '模板类型',
|
||||
dataIndex: 'templateType',
|
||||
width: 100,
|
||||
customRender: ({ text }) => filterDictTextByCache('msgType', text),
|
||||
},
|
||||
{
|
||||
title: '是否应用',
|
||||
dataIndex: 'useStatus',
|
||||
width: 90,
|
||||
customRender: function ({ text }) {
|
||||
if (text == '1') {
|
||||
return '是';
|
||||
} else {
|
||||
return '否';
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '模板标题',
|
||||
field: 'templateName',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '模板编码',
|
||||
field: 'templateCode',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '模板类型',
|
||||
field: 'templateType',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'msgType',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchemas: FormSchema[] = [
|
||||
{
|
||||
label: 'ID',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '模板标题',
|
||||
field: 'templateName',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '模板编码',
|
||||
field: 'templateCode',
|
||||
component: 'Input',
|
||||
dynamicRules: ({ model, schema }) => {
|
||||
return [ ...rules.duplicateCheckRule('sys_sms_template', 'template_code', model, schema, true)];
|
||||
},
|
||||
// 编辑模式下不可修改编码
|
||||
dynamicDisabled: (params) => !!params.values.id,
|
||||
},
|
||||
{
|
||||
label: '模板类型',
|
||||
field: 'templateType',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'msgType',
|
||||
placeholder: '请选择模板类型',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '是否应用',
|
||||
field: 'useStatus',
|
||||
component: 'JSwitch',
|
||||
componentProps: {
|
||||
options: ['1', '0'],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '模板内容',
|
||||
field: 'templateContent',
|
||||
component: 'InputTextArea',
|
||||
componentProps: {
|
||||
autoSize: {
|
||||
minRows: 8,
|
||||
maxRows: 8,
|
||||
},
|
||||
},
|
||||
ifShow: ({ values }) => {
|
||||
return !['2', '4', '5'].includes(values.templateType);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
label: '模板内容',
|
||||
field: 'templateContent',
|
||||
component: 'JEditor',
|
||||
ifShow: ({ values }) => {
|
||||
return ['2', '4'].includes(values.templateType);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '模板内容',
|
||||
field: 'templateContent',
|
||||
component: 'JMarkdownEditor',
|
||||
ifShow: ({ values }) => {
|
||||
return ['5'].includes(values.templateType);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const sendTestFormSchemas: FormSchema[] = [
|
||||
{
|
||||
label: '模板编码',
|
||||
field: 'templateCode',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
label: '模板标题',
|
||||
field: 'templateName',
|
||||
component: 'Input',
|
||||
componentProps: { disabled: true },
|
||||
},
|
||||
{
|
||||
label: '模板内容',
|
||||
field: 'templateContent',
|
||||
component: 'InputTextArea',
|
||||
componentProps: { disabled: true, rows: 5 },
|
||||
},
|
||||
{
|
||||
label: '测试数据',
|
||||
field: 'testData',
|
||||
component: 'InputTextArea',
|
||||
required: true,
|
||||
helpMessage: 'JSON数据',
|
||||
defaultValue: '{}',
|
||||
componentProps: {
|
||||
placeholder: '请输入JSON格式测试数据',
|
||||
rows: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '消息类型',
|
||||
field: 'msgType',
|
||||
component: 'JDictSelectTag',
|
||||
required: true,
|
||||
defaultValue:'system',
|
||||
componentProps: { dictCode: 'messageType',type:'radio' },
|
||||
},
|
||||
{
|
||||
label: '消息接收方',
|
||||
field: 'receiver',
|
||||
required: true,
|
||||
component: 'JSelectUser',
|
||||
componentProps: {
|
||||
labelKey: 'username',
|
||||
rowKey: 'username',
|
||||
},
|
||||
},
|
||||
];
|
||||
24
jeecgboot-vue3/src/views/system/notice/DetailModal.vue
Normal file
24
jeecgboot-vue3/src/views/system/notice/DetailModal.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" title="查看详情" :showCancelBtn="false" :showOkBtn="false" :maxHeight="500">
|
||||
<iframe :src="frameSrc" class="detail-iframe" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
// 获取props
|
||||
defineProps({
|
||||
frameSrc: propTypes.string.def(''),
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal] = useModalInner();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.detail-iframe {
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
||||
60
jeecgboot-vue3/src/views/system/notice/NoticeModal.vue
Normal file
60
jeecgboot-vue3/src/views/system/notice/NoticeModal.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="900px" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './notice.data';
|
||||
import { saveOrUpdate } from './notice.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
if (data.record.userIds) {
|
||||
data.record.userIds = data.record.userIds.substring(0, data.record.userIds.length - 1);
|
||||
}
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
|
||||
//表单提交事件
|
||||
async function handleSubmit(v) {
|
||||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
//update-begin-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined ---
|
||||
if(values.msgType==='ALL'){
|
||||
values.userIds = '';
|
||||
}else{
|
||||
values.userIds += ',';
|
||||
}
|
||||
//update-end-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined ---
|
||||
await saveOrUpdate(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
168
jeecgboot-vue3/src/views/system/notice/index.vue
Normal file
168
jeecgboot-vue3/src/views/system/notice/index.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新建</a-button>
|
||||
<!-- <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>-->
|
||||
<!-- <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>-->
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon style="fontsize: 12px" icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<NoticeModal @register="registerModal" @success="reload" />
|
||||
<DetailModal @register="register" :frameSrc="iframeUrl" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="system-notice" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import NoticeModal from './NoticeModal.vue';
|
||||
import DetailModal from './DetailModal.vue';
|
||||
import { useMethods } from '/@/hooks/system/useMethods';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
import { columns, searchFormSchema } from './notice.data';
|
||||
import { getList, deleteNotice, batchDeleteNotice, getExportUrl, getImportUrl, doReleaseData, doReovkeData } from './notice.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
const glob = useGlobSetting();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [register, { openModal: openDetail }] = useModal();
|
||||
const iframeUrl = ref('');
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, onExportXls, onImportXls, tableContext, doRequest } = useListPage({
|
||||
designScope: 'notice-template',
|
||||
tableProps: {
|
||||
title: '消息通知',
|
||||
api: getList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '消息通知列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteNotice({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
doRequest(() => batchDeleteNotice({ ids: selectedRowKeys.value }));
|
||||
}
|
||||
/**
|
||||
* 发布
|
||||
*/
|
||||
async function handleRelease(id) {
|
||||
await doReleaseData({ id });
|
||||
reload();
|
||||
}
|
||||
/**
|
||||
* 撤销
|
||||
*/
|
||||
async function handleReovke(id) {
|
||||
await doReovkeData({ id });
|
||||
reload();
|
||||
}
|
||||
/**
|
||||
* 查看
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
iframeUrl.value = `${glob.uploadUrl}/sys/annountCement/show/${record.id}?token=${getToken()}`;
|
||||
openDetail(true);
|
||||
}
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
ifShow: record.sendStatus == 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '删除',
|
||||
ifShow: record.sendStatus != 1,
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '发布',
|
||||
ifShow: record.sendStatus == 0,
|
||||
onClick: handleRelease.bind(null, record.id),
|
||||
},
|
||||
{
|
||||
label: '撤销',
|
||||
ifShow: record.sendStatus == 1,
|
||||
popConfirm: {
|
||||
title: '确定要撤销吗?',
|
||||
confirm: handleReovke.bind(null, record.id),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '查看',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
65
jeecgboot-vue3/src/views/system/notice/notice.api.ts
Normal file
65
jeecgboot-vue3/src/views/system/notice/notice.api.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/annountCement/list',
|
||||
save = '/sys/annountCement/add',
|
||||
edit = '/sys/annountCement/edit',
|
||||
delete = '/sys/annountCement/delete',
|
||||
deleteBatch = '/sys/annountCement/deleteBatch',
|
||||
exportXls = '/sys/annountCement/exportXls',
|
||||
importExcel = '/sys/annountCement/importExcel',
|
||||
releaseData = '/sys/annountCement/doReleaseData',
|
||||
reovkeData = '/sys/annountCement/doReovkeData',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出url
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
/**
|
||||
* 导入url
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
/**
|
||||
* 查询租户列表
|
||||
* @param params
|
||||
*/
|
||||
export const getList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新通告
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除通告
|
||||
* @param params
|
||||
*/
|
||||
export const deleteNotice = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量消息公告
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeleteNotice = (params) => defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true });
|
||||
|
||||
/**
|
||||
* 发布
|
||||
* @param id
|
||||
*/
|
||||
export const doReleaseData = (params) => defHttp.get({ url: Api.releaseData, params });
|
||||
/**
|
||||
* 撤销
|
||||
* @param id
|
||||
*/
|
||||
export const doReovkeData = (params) => defHttp.get({ url: Api.reovkeData, params });
|
||||
156
jeecgboot-vue3/src/views/system/notice/notice.data.ts
Normal file
156
jeecgboot-vue3/src/views/system/notice/notice.data.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { rules } from '/@/utils/helper/validator';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '标题',
|
||||
width: 150,
|
||||
dataIndex: 'titile',
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'msgCategory',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDict(text, 'msg_category');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发布人',
|
||||
width: 100,
|
||||
dataIndex: 'sender',
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'priority',
|
||||
width: 70,
|
||||
customRender: ({ text }) => {
|
||||
const color = text == 'L' ? 'blue' : text == 'M' ? 'yellow' : 'red';
|
||||
return render.renderTag(render.renderDict(text, 'priority'), color);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通告对象',
|
||||
dataIndex: 'msgType',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
return render.renderDict(text, 'msg_type');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发布状态',
|
||||
dataIndex: 'sendStatus',
|
||||
width: 70,
|
||||
customRender: ({ text }) => {
|
||||
const color = text == '0' ? 'red' : text == '1' ? 'green' : 'gray';
|
||||
return render.renderTag(render.renderDict(text, 'send_status'), color);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
width: 100,
|
||||
dataIndex: 'sendTime',
|
||||
},
|
||||
{
|
||||
title: '撤销时间',
|
||||
width: 100,
|
||||
dataIndex: 'cancelTime',
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'titile',
|
||||
label: '标题',
|
||||
component: 'JInput',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
label: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
field: 'msgCategory',
|
||||
label: '消息类型',
|
||||
required: true,
|
||||
component: 'JDictSelectTag',
|
||||
defaultValue: '1',
|
||||
componentProps: {
|
||||
type: 'radio',
|
||||
dictCode: 'msg_category',
|
||||
placeholder: '请选择类型',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'titile',
|
||||
label: '标题',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'msgAbstract',
|
||||
label: '摘要',
|
||||
component: 'InputTextArea',
|
||||
required: true,
|
||||
},
|
||||
// {
|
||||
// field: 'endTime',
|
||||
// label: '截至日期',
|
||||
// component: 'DatePicker',
|
||||
// componentProps: {
|
||||
// showTime: true,
|
||||
// valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
// placeholder: '请选择截至日期',
|
||||
// },
|
||||
// dynamicRules: ({ model }) => rules.endTime(model.startTime, true),
|
||||
// },
|
||||
{
|
||||
field: 'msgType',
|
||||
label: '接收用户',
|
||||
defaultValue: 'ALL',
|
||||
component: 'JDictSelectTag',
|
||||
required: true,
|
||||
componentProps: {
|
||||
type: 'radio',
|
||||
dictCode: 'msg_type',
|
||||
placeholder: '请选择发布范围',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'userIds',
|
||||
label: '指定用户',
|
||||
component: 'JSelectUser',
|
||||
required: true,
|
||||
componentProps: {
|
||||
rowKey: 'id',
|
||||
labelKey: 'username',
|
||||
},
|
||||
ifShow: ({ values }) => values.msgType == 'USER',
|
||||
},
|
||||
{
|
||||
field: 'priority',
|
||||
label: '优先级',
|
||||
defaultValue: 'H',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: 'priority',
|
||||
type: 'radio',
|
||||
placeholder: '请选择优先级',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'msgContent',
|
||||
label: '内容',
|
||||
component: 'Input',
|
||||
render: render.renderTinymce,
|
||||
},
|
||||
];
|
||||
20
jeecgboot-vue3/src/views/system/onlineuser/OnlineUser.api.ts
Normal file
20
jeecgboot-vue3/src/views/system/onlineuser/OnlineUser.api.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/online/list',
|
||||
forceLogout = '/sys/online/forceLogout'
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 批量删除角色
|
||||
* @param params
|
||||
*/
|
||||
export const forceLogout = (params) => {
|
||||
return defHttp.post({url:Api.forceLogout,params},{isTransformResponse:false})
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
import { render } from "/@/utils/common/renderUtils";
|
||||
import { getToken } from '/@/utils/auth';
|
||||
|
||||
//列表
|
||||
export const columns = [
|
||||
{
|
||||
title:'用户账号',
|
||||
align:"center",
|
||||
dataIndex: 'username',
|
||||
customRender: ( {text,record} ) => {
|
||||
let token = getToken();
|
||||
if(record.token === token) {
|
||||
return text + '(我)'
|
||||
}
|
||||
return text
|
||||
},
|
||||
},{
|
||||
title:'用户姓名',
|
||||
align:"center",
|
||||
dataIndex: 'realname'
|
||||
},{
|
||||
title: '头像',
|
||||
align: "center",
|
||||
width: 120,
|
||||
dataIndex: 'avatar',
|
||||
customRender: render.renderAvatar,
|
||||
},{
|
||||
title:'生日',
|
||||
align:"center",
|
||||
dataIndex: 'birthday'
|
||||
},{
|
||||
title: '性别',
|
||||
align: "center",
|
||||
dataIndex: 'sex',
|
||||
customRender: ({text}) => {
|
||||
return render.renderDict(text, 'sex');
|
||||
}
|
||||
},{
|
||||
title:'手机号',
|
||||
align:"center",
|
||||
dataIndex: 'phone'
|
||||
}
|
||||
];
|
||||
|
||||
//查询区域
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'username',
|
||||
label: '用户账号',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="online-user" setup>
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { columns, searchFormSchema } from './OnlineUser.data';
|
||||
import { list, forceLogout } from './OnlineUser.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onImportXls, onExportXls } = useListPage({
|
||||
designScope: 'online-user',
|
||||
tableProps: {
|
||||
//在线用户rowKey默认id会造成key重复,导致页面出现重复数据
|
||||
rowKey:'token',
|
||||
title: '在线用户',
|
||||
api: list,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
},
|
||||
rowSelection: null,
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const $message = useMessage();
|
||||
|
||||
//操作栏
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '强退',
|
||||
popConfirm: {
|
||||
title: '强制退出用户?',
|
||||
confirm: handleForce.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 强退
|
||||
* @param record
|
||||
*/
|
||||
function handleForce(record) {
|
||||
forceLogout({ token: record.token }).then((res)=>{
|
||||
if(res.success){
|
||||
reload();
|
||||
$message.createMessage.success('强制退出用户”'+record.realname+'“成功!');
|
||||
}else{
|
||||
$message.createMessage.warn(res.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
159
jeecgboot-vue3/src/views/system/ossfile/index.vue
Normal file
159
jeecgboot-vue3/src/views/system/ossfile/index.vue
Normal file
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-upload name="file" :showUploadList="false" :action="ossAction" :headers="tokenHeader" :beforeUpload="beforeUpload" @change="handleChange">
|
||||
<a-button type="primary" preIcon="ant-design:upload-outlined">OSS文件上传</a-button>
|
||||
</a-upload>
|
||||
<a-upload
|
||||
name="file"
|
||||
:showUploadList="false"
|
||||
:action="minioAction"
|
||||
:headers="tokenHeader"
|
||||
:beforeUpload="beforeUpload"
|
||||
@change="handleChange"
|
||||
>
|
||||
<a-button type="primary" preIcon="ant-design:upload-outlined">MINIO文件上传</a-button>
|
||||
</a-upload>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="system-ossfile" setup>
|
||||
//ts语法
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { columns, searchFormSchema } from './ossfile.data';
|
||||
import { list, deleteFile, getOssUrl, getMinioUrl } from './ossfile.api';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
import {encryptByBase64} from "@/utils/cipher";
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const glob = useGlobSetting();
|
||||
const tokenHeader = { 'X-Access-Token': getToken() };
|
||||
//注册table数据
|
||||
const [registerTable, { reload }] = useTable({
|
||||
api: list,
|
||||
rowKey: 'id',
|
||||
columns,
|
||||
formConfig: {
|
||||
labelWidth: 120,
|
||||
schemas: searchFormSchema,
|
||||
autoSubmitOnEnter: true,
|
||||
},
|
||||
striped: true,
|
||||
useSearchForm: true,
|
||||
showTableSetting: true,
|
||||
clickToRowSelect: false,
|
||||
bordered: true,
|
||||
showIndexColumn: false,
|
||||
tableSetting: { fullScreen: true },
|
||||
beforeFetch: (params) => {
|
||||
return Object.assign({ column: 'createTime', order: 'desc' }, params);
|
||||
},
|
||||
actionColumn: {
|
||||
width: 80,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
fixed: undefined,
|
||||
},
|
||||
});
|
||||
/**
|
||||
* 上传url
|
||||
*/
|
||||
const ossAction = computed(() => `${glob.uploadUrl}${getOssUrl}`);
|
||||
const minioAction = computed(() => `${glob.uploadUrl}${getMinioUrl}`);
|
||||
|
||||
/**
|
||||
* 预览
|
||||
*/
|
||||
function handleView(record) {
|
||||
if (record && record.url) {
|
||||
console.log('glob.onlineUrl', glob.viewUrl);
|
||||
//update-begin---author:scott ---date:2024-06-03 for:【TV360X-952】升级到kkfileview4.1.0---
|
||||
// let filePath = encodeURIComponent(record.url);
|
||||
let url = encodeURIComponent(encryptByBase64(record.url));
|
||||
// //文档采用pdf预览高级模式
|
||||
// if(filePath.endsWith(".pdf") || filePath.endsWith(".doc") || filePath.endsWith(".docx")){
|
||||
// filePath = filePath
|
||||
// }
|
||||
let previewUrl = `${glob.viewUrl}?url=` + url;
|
||||
//update-end---author:scott ---date:2024-06-03 for:【TV360X-952】升级到kkfileview4.1.0---
|
||||
|
||||
window.open(previewUrl, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteFile({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传前事件
|
||||
*/
|
||||
function beforeUpload(file) {
|
||||
var fileType = file.type;
|
||||
if (fileType === 'image') {
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
createMessage.warning('请上传图片');
|
||||
return false;
|
||||
}
|
||||
} else if (fileType === 'file') {
|
||||
if (fileType.indexOf('image') >= 0) {
|
||||
createMessage.warning('请上传文件');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传事件
|
||||
*/
|
||||
function handleChange(info) {
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file.response.success) {
|
||||
reload();
|
||||
createMessage.success(`${info.file.name} 上传成功!`);
|
||||
} else {
|
||||
createMessage.error(`${info.file.response.message}`);
|
||||
}
|
||||
} else if (info.file.status === 'error') {
|
||||
createMessage.error(`${info.file.response.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '预览',
|
||||
onClick: handleView.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
33
jeecgboot-vue3/src/views/system/ossfile/ossfile.api.ts
Normal file
33
jeecgboot-vue3/src/views/system/ossfile/ossfile.api.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/oss/file/list',
|
||||
deleteFile = '/sys/oss/file/delete',
|
||||
ossUpload = '/sys/oss/file/upload',
|
||||
minioUpload = '/sys/upload/uploadMinio',
|
||||
}
|
||||
|
||||
/**
|
||||
* oss上传
|
||||
* @param params
|
||||
*/
|
||||
export const getOssUrl = Api.ossUpload;
|
||||
/**
|
||||
* minio上传
|
||||
* @param params
|
||||
*/
|
||||
export const getMinioUrl = Api.minioUpload;
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
export const deleteFile = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteFile, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
30
jeecgboot-vue3/src/views/system/ossfile/ossfile.data.ts
Normal file
30
jeecgboot-vue3/src/views/system/ossfile/ossfile.data.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { BasicColumn } from '/@/components/Table';
|
||||
import { FormSchema } from '/@/components/Table';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'fileName',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '文件地址',
|
||||
dataIndex: 'url',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
label: '文件名称',
|
||||
field: 'fileName',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
{
|
||||
label: '文件地址',
|
||||
field: 'url',
|
||||
component: 'Input',
|
||||
colProps: { span: 6 },
|
||||
},
|
||||
];
|
||||
53
jeecgboot-vue3/src/views/system/position/PositionModal.vue
Normal file
53
jeecgboot-vue3/src/views/system/position/PositionModal.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" :width="700">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formSchema } from './position.data';
|
||||
import { saveOrUpdatePosition, getPositionById } from './position.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const isUpdate = ref(true);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
//labelWidth: 150,
|
||||
schemas: formSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
//获取详情
|
||||
data.record = await getPositionById({ id: data.record.id });
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增职务' : '编辑职务'));
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdatePosition(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
125
jeecgboot-vue3/src/views/system/position/index.vue
Normal file
125
jeecgboot-vue3/src/views/system/position/index.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleAdd">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<PositionModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="system-position" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { getPositionList, deletePosition, batchDeletePosition, customUpload, getExportUrl, getImportUrl } from './position.api';
|
||||
import { columns, searchFormSchema } from './position.data';
|
||||
import PositionModal from './PositionModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
const { createMessage } = useMessage();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, onExportXls, onImportXls, tableContext } = useListPage({
|
||||
designScope: 'position-template',
|
||||
tableProps: {
|
||||
title: '职务列表',
|
||||
api: getPositionList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 180,
|
||||
},
|
||||
showIndexColumn: true,
|
||||
},
|
||||
exportConfig: {
|
||||
name: '职务列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deletePosition({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeletePosition({ ids: selectedRowKeys.value }, () => {
|
||||
// update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8334】批量删除之后,按钮未隐藏,选中记录还在
|
||||
selectedRowKeys.value = [];
|
||||
reload();
|
||||
// update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8334】批量删除之后,按钮未隐藏,选中记录还在
|
||||
});
|
||||
}
|
||||
</script>
|
||||
79
jeecgboot-vue3/src/views/system/position/position.api.ts
Normal file
79
jeecgboot-vue3/src/views/system/position/position.api.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/position/list',
|
||||
save = '/sys/position/add',
|
||||
edit = '/sys/position/edit',
|
||||
get = '/sys/position/queryById',
|
||||
delete = '/sys/position/delete',
|
||||
importExcel = '/sys/position/importExcel',
|
||||
exportXls = '/sys/position/exportXls',
|
||||
deleteBatch = '/sys/position/deleteBatch',
|
||||
}
|
||||
/**
|
||||
* 导出api
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
export const getImportUrl = Api.importExcel;
|
||||
/**
|
||||
* 查询列表
|
||||
* @param params
|
||||
*/
|
||||
export const getPositionList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
*/
|
||||
export const saveOrUpdatePosition = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
* @param params
|
||||
*/
|
||||
export const getPositionById = (params) => {
|
||||
return defHttp.get({ url: Api.get, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 单条删除
|
||||
* @param params
|
||||
*/
|
||||
export const deletePosition = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
*/
|
||||
export const batchDeletePosition = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 自定义上传
|
||||
* @param customUpload
|
||||
*/
|
||||
export const customUpload = (params) => {
|
||||
defHttp.uploadFile({ url: Api.importExcel }, params);
|
||||
};
|
||||
71
jeecgboot-vue3/src/views/system/position/position.data.ts
Normal file
71
jeecgboot-vue3/src/views/system/position/position.data.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { BasicColumn, FormSchema } from '/@/components/Table';
|
||||
import { rules } from '/@/utils/helper/validator';
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
// {
|
||||
// title: '职务编码',
|
||||
// dataIndex: 'code',
|
||||
// width: 200,
|
||||
// align: 'left',
|
||||
// },
|
||||
{
|
||||
title: '职务名称',
|
||||
dataIndex: 'name',
|
||||
align: 'left'
|
||||
// width: 200,
|
||||
},
|
||||
// {
|
||||
// title: '职务等级',
|
||||
// dataIndex: 'postRank_dictText',
|
||||
// width: 100,
|
||||
// },
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '职务名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
];
|
||||
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '主键',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
// {
|
||||
// label: '职级',
|
||||
// field: 'postRank',
|
||||
// component: 'JDictSelectTag',
|
||||
// required: true,
|
||||
// componentProps: {
|
||||
// dictCode: 'position_rank',
|
||||
// dropdownStyle: {
|
||||
// maxHeight: '100vh',
|
||||
// },
|
||||
// getPopupContainer: () => document.body,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
field: 'name',
|
||||
label: '职务名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
// {
|
||||
// field: 'code',
|
||||
// label: '职务编码',
|
||||
// component: 'Input',
|
||||
// required: true,
|
||||
// dynamicDisabled: ({ values }) => {
|
||||
// return !!values.id;
|
||||
// },
|
||||
// dynamicRules: ({ model, schema }) => {
|
||||
// return rules.duplicateCheckRule('sys_position', 'code', model, schema, true);
|
||||
// },
|
||||
// },
|
||||
];
|
||||
184
jeecgboot-vue3/src/views/system/role/TenantRoleList.vue
Normal file
184
jeecgboot-vue3/src/views/system/role/TenantRoleList.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<div style="margin-left: 10px;margin-top: 5px">当前登录租户: <span class="tenant-name">{{loginTenantName}}</span> </div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!--角色用户表格-->
|
||||
<RoleUserTable @register="roleUserDrawer" :disableUserEdit="true"/>
|
||||
<!--角色编辑抽屉-->
|
||||
<RoleDrawer @register="registerDrawer" @success="reload" :showFooter="showFooter" />
|
||||
<!--角色详情-->
|
||||
<RoleDesc @register="registerDesc"></RoleDesc>
|
||||
</template>
|
||||
<script lang="ts" name="tenant-role-list" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import RoleDesc from './components/RoleDesc.vue';
|
||||
import RoleDrawer from './components/RoleDrawer.vue';
|
||||
import RoleUserTable from './components/RoleUserTable.vue';
|
||||
import { columns, searchFormSchema } from './role.data';
|
||||
import { listByTenant, deleteRole, batchDeleteRole, getExportUrl, getImportUrl } from './role.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { getLoginTenantName } from "/@/views/system/tenant/tenant.api";
|
||||
import { tenantSaasMessage } from "@/utils/common/compUtils";
|
||||
|
||||
const showFooter = ref(true);
|
||||
const [roleUserDrawer, { openDrawer: openRoleUserDrawer }] = useDrawer();
|
||||
const [registerDrawer, { openDrawer }] = useDrawer();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerDesc, { openDrawer: openRoleDesc }] = useDrawer();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onImportXls, onExportXls } = useListPage({
|
||||
designScope: 'role-template',
|
||||
tableProps: {
|
||||
title: '租户角色列表',
|
||||
api: listByTenant,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
},
|
||||
rowSelection: null,
|
||||
//自定义默认排序
|
||||
defSort: {
|
||||
column: 'id',
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '角色列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleCreate() {
|
||||
showFooter.value = true;
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function handleEdit(record: Recordable) {
|
||||
showFooter.value = true;
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
showFooter.value = false;
|
||||
openRoleDesc(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteRole({ id: record.id }, reload);
|
||||
}
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeleteRole({ ids: selectedRowKeys.value }, reload);
|
||||
}
|
||||
/**
|
||||
* 角色用户
|
||||
*/
|
||||
function handleUser(record) {
|
||||
//onSelectChange(selectedRowKeys)
|
||||
openRoleUserDrawer(true, record);
|
||||
}
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '用户',
|
||||
onClick: handleUser.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const loginTenantName = ref<string>('');
|
||||
|
||||
getTenantName();
|
||||
|
||||
async function getTenantName(){
|
||||
loginTenantName.value = await getLoginTenantName();
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
tenantSaasMessage('租户角色')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tenant-name{
|
||||
text-decoration:underline;
|
||||
margin: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" title="数据规则配置" width="450px" destroyOnClose>
|
||||
<a-tabs defaultActiveKey="1">
|
||||
<a-tab-pane tab="数据规则" key="1">
|
||||
<a-checkbox-group v-model:value="dataRuleChecked" v-if="dataRuleList.length > 0">
|
||||
<a-row>
|
||||
<a-col :span="24" v-for="(item, index) in dataRuleList" :key="'dr' + index">
|
||||
<a-checkbox :value="item.id">{{ item.ruleName }}</a-checkbox>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<div style="width: 100%; margin-top: 15px">
|
||||
<a-button @click="saveDataRuleForRole" type="primary" size="small"> <Icon icon="ant-design:save-outlined"></Icon>点击保存</a-button>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<div v-else><h3>无配置信息!</h3></div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/src/components/Drawer';
|
||||
import { useMessage } from '/src/hooks/web/useMessage';
|
||||
import { queryDataRule, saveDataRule } from '../role.api';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const { createMessage } = useMessage();
|
||||
// 声明数据
|
||||
const functionId = ref('');
|
||||
const roleId = ref('');
|
||||
const dataRuleList = ref([]);
|
||||
const dataRuleChecked = ref([]);
|
||||
|
||||
/**
|
||||
* 数据
|
||||
*/
|
||||
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
|
||||
await reset();
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
//权限的id
|
||||
functionId.value = data.functionId;
|
||||
//角色的id
|
||||
roleId.value = data.roleId;
|
||||
//查询数据
|
||||
const res = await queryDataRule({ functionId: unref(functionId), roleId: unref(roleId) });
|
||||
if (res.success) {
|
||||
dataRuleList.value = res.result.datarule;
|
||||
if (res.result.drChecked) {
|
||||
dataRuleChecked.value = res.result.drChecked.split(',');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function reset() {
|
||||
functionId.value = '';
|
||||
roleId.value = '';
|
||||
dataRuleList.value = [];
|
||||
dataRuleChecked.value = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
async function saveDataRuleForRole() {
|
||||
if (!unref(dataRuleChecked) || unref(dataRuleChecked).length == 0) {
|
||||
createMessage.warning('请注意,现未勾选任何数据权限!');
|
||||
}
|
||||
let params = {
|
||||
permissionId: unref(functionId),
|
||||
roleId: unref(roleId),
|
||||
dataRuleIds: unref(dataRuleChecked).join(','),
|
||||
};
|
||||
await saveDataRule(params);
|
||||
//关闭弹窗
|
||||
closeDrawer();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
}
|
||||
</script>
|
||||
18
jeecgboot-vue3/src/views/system/role/components/RoleDesc.vue
Normal file
18
jeecgboot-vue3/src/views/system/role/components/RoleDesc.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" @register="registerDrawer" title="角色详情" width="500px" destroyOnClose>
|
||||
<Description :column="1" :data="roleData" :schema="formDescSchema" />
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, useAttrs } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/src/components/Drawer';
|
||||
import { formDescSchema } from '../role.data';
|
||||
import { Description, useDescription } from '/@/components/Description/index';
|
||||
const emit = defineEmits(['register']);
|
||||
const attrs = useAttrs();
|
||||
const roleData = ref({});
|
||||
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
|
||||
setDrawerProps({ confirmLoading: false });
|
||||
roleData.value = data.record;
|
||||
});
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user