init
This commit is contained in:
2025-06-27 17:42:38 +08:00
commit bd6402478b
5317 changed files with 785994 additions and 0 deletions
+19
View 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
View 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"
/>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-card>
</template>
<script lang="ts" setup>
import { inject, nextTick, ref, unref, defineExpose } 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>
+10
View File
@@ -0,0 +1,10 @@
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-depart-manage';
.@{prefix-cls} {
&--box {
.ant-tabs-nav {
padding: 0 20px;
}
}
}
+95
View File
@@ -0,0 +1,95 @@
<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['code']] = position['name'];
});
positionInfo.value = obj;
}
}
queryPositionInfo();
</script>
<style lang="less" scoped>
@import './index.less';
/*begin 兼容暗夜模式*/
.address-book {
background-color: @component-background !important;
}
/*end 兼容暗夜模式*/
</style>
+78
View 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) => {
const 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 });
@@ -0,0 +1,65 @@
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: {
replaceFields: {
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,94 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="getTitle" @ok="handleSubmit">
<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';
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;
isSubAdd.value = !!!data?.isUpdate && data.record.id;
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) {
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 });
}
}
</script>
+295
View File
@@ -0,0 +1,295 @@
<template>
<div>
<!--引用表格-->
<BasicTable
@register="registerTable"
:rowSelection="rowSelection"
:expandedRowKeys="expandedRowKeys"
@expand="handleExpand"
@fetch-success="onFetchSuccess"
>
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleCreate"> 新增</a-button>
<a-button preIcon="ant-design:export-outlined" type="primary" @click="onExportXls"> 导出</a-button>
<j-upload-button preIcon="ant-design:import-outlined" type="primary" @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" />
删除
</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)" />
</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 '/@/components/Table';
import { useDrawer } from '/@/components/Drawer';
import CategoryModal from './components/CategoryModal.vue';
import { useModal } from '/@/components/Modal';
import { useMethods } from '/@/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,
},
},
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 {
expandedRowKeys.value = [];
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>
@@ -0,0 +1,252 @@
<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>
@@ -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);
const 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 });
};
@@ -0,0 +1,152 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { render } from '/@/utils/common/renderUtils';
import { duplicateCheck } 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('请输入规则编码!');
}
const params = {
tableName: 'sys_check_rule',
fieldName: 'rule_code',
fieldVal: value,
dataId: model.id,
};
duplicateCheck(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
View 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 preIcon="ant-design:export-outlined" type="primary" @click="onExportXls"> 导出</a-button>
<j-upload-button preIcon="ant-design:import-outlined" type="primary" @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" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作</span>
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<CheckRuleTestModal @register="testModal" />
<CheckRuleModal @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 { 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,84 @@
<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,131 @@
<template>
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
<BasicForm @register="registerForm">
<template #addressInfo="{ model }">
<a-input style="width: calc(100% - 100px)" v-model:value="model['address']" :disabled="true" />
<a-button style="margin-left: 10px" @click="viewMap"> 查看地图</a-button>
</template>
</BasicForm>
</BasicModal>
<Map @register="registerMap" :state="state" ref="map" @get-position="getPosition" />
</template>
<script lang="ts" setup>
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { BasicModal, useModal, useModalInner } from '/@/components/Modal';
import { saveOrUpdateDepart } from '../depart.api';
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
import Map from '/@/views/consult/resource/components/Map.vue';
const [registerMap, { openModal }] = useModal();
const state = ref({});
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, getFieldsValue }] = useForm({
schemas: useBasicFormSchema().basicFormSchema,
showActionButtonGroup: false,
});
// 打开地图弹窗
function viewMap() {
openModal(true, {
record: { ...getFieldsValue(), ...state.value },
});
}
async function getPosition(val) {
let { pname, cityname, adname, address, name } = val.handleItem || '';
let nameList = [pname, cityname, adname, address, name];
let str = '';
nameList.map((item) => {
if (item !== undefined) {
str += item;
}
});
await setFieldsValue({
lat: val.lat,
lng: val.lng,
address: str,
});
state.value = {
...state.value,
latitude: val.lat,
longitude: val.lng,
address: str,
};
}
// 注册弹窗
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,188 @@
<template>
<a-spin :spinning="loading">
<BasicForm @register="registerForm">
<template #addressInfo="{ model }">
<a-input style="width: calc(100% - 100px)" v-model:value="model['address']" :disabled="true" />
<a-button style="margin-left: 10px" @click="viewMap" :disabled="!hasPer"> 查看地图</a-button>
</template>
</BasicForm>
<div class="j-box-bottom-button offset-20" style="margin-top: 30px" v-if="hasPer">
<div class="j-box-bottom-button-float">
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
<a-button preIcon="ant-design:save-filled" type="primary" @click="onSubmit">保存</a-button>
</div>
</div>
<Map @register="registerMap" :state="state" ref="map" @get-position="getPosition" />
</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 Map from '/@/views/consult/resource/components/Map.vue';
import { useModal } from '/@/components/Modal';
import { useUserStore } from '/@/store/modules/user';
import { orgManageApi } from '/@/utils/auth/buttonAuth/system';
import { usePermission } from '/@/hooks/web/usePermission';
const { hasPermission } = usePermission();
const [registerMap, { openModal }] = useModal();
const emit = defineEmits(['success']);
const props = defineProps({
data: { type: Object, default: () => ({}) },
rootTreeData: { type: Array, default: () => [] },
});
const { userInfo } = useUserStore();
const prefixCls = inject('prefixCls');
const loading = ref<boolean>(false);
// 当前是否是更新模式
const isUpdate = ref<boolean>(true);
// 当前的弹窗数据
const model = ref<object>({});
const state = ref({});
//注册表单
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema, getFieldsValue, clearValidate, setProps }] = useForm({
labelWidth: 130,
schemas: useBasicFormSchema().basicFormSchema,
showActionButtonGroup: false,
});
const categoryOptions = computed(() => {
if (!!props?.data?.parentId) {
return orgCategoryOptions.child;
} else {
return orgCategoryOptions.root;
}
});
onMounted(async () => {
// 禁用字段
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 });
updateState(record);
await setProps({ disabled: !hasPer.value });
},
{ 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 }
);
await clearValidate();
});
const hasPer = computed(() => {
// 修改此字段
return (
(!!userInfo?.roleCodes && (userInfo?.roleCodes.includes('admin') || userInfo?.roleCodes.includes('system'))) ||
(hasPermission(orgManageApi.departManageList) && props.data.manager)
);
});
function updateState(val) {
state.value = {
latitude: val.lat,
longitude: val.lng,
address: val.address,
};
}
// 打开地图弹窗
function viewMap() {
openModal(true, {
record: { ...getFieldsValue(), ...state.value },
});
}
// 关闭地图
async function getPosition(val) {
let { pname, cityname, adname, address, name } = val.handleItem || '';
let nameList = [pname, cityname, adname, address, name];
let str = '';
nameList.map((item) => {
if (item !== undefined) {
str += item;
}
});
await setFieldsValue({
lat: val.lat,
lng: val.lng,
address: str,
});
state.value = {
...state.value,
latitude: val.lat,
longitude: val.lng,
address: str,
};
}
// 重置表单
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" scoped>
/*begin 兼容暗夜模式*/
.j-box-bottom-button-float {
background-color: @component-background;
border-top: 1px solid @border-color-base;
}
/*end 兼容暗夜模式*/
</style>
@@ -0,0 +1,361 @@
<template>
<a-card :bordered="false" style="height: 100%">
<div class="j-table-operator" style="width: 100%">
<a-button preIcon="ant-design:plus-outlined" type="primary" v-auth="'system:depart:add'" @click="onAddDepart" v-if="hasPer"
>新增</a-button
>
<a-button preIcon="ant-design:plus-outlined" type="primary" v-auth="'system:depart:add'" @click="onAddChildDepart()">添加下级</a-button>
<!-- 2024年3月15日09:26:36 暂时注掉导入导出-->
<!-- <a-upload name="file" :showUploadList="false" :customRequest="onImportXls">-->
<!-- <a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button>-->
<!-- </a-upload>-->
<!-- <a-button preIcon="ant-design:export-outlined" type="primary" @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" class="spin-tree">
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" />
<!--组织机构树-->
<!-- checkable-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
: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
:visible="visibleTreeKey === treeKey"
title="确定要删除吗?"
ok-text="确定"
cancel-text="取消"
placement="rightTop"
@confirm="onDelete(dataRef)"
@visibleChange="onVisibleChange"
>
<span>{{ title }}</span>
</Popconfirm>
<template
#overlay
v-if="
dataRef?.manager ||
(!!userInfo?.roleCodes && (userInfo?.roleCodes.includes('admin') || userInfo?.roleCodes.includes('system')))
"
>
<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 { defineExpose, inject, nextTick, ref, unref, computed } 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 { message, Popconfirm } from 'ant-design-vue';
const prefixCls = inject('prefixCls');
const emit = defineEmits(['select', 'rootTreeData']);
const { createMessage } = useMessage();
const { handleImportXls, handleExportXls } = useMethods();
import { useUserStore } from '/@/store/modules/user';
import { orgManageApi } from '/@/utils/auth/buttonAuth/system';
const { userInfo } = useUserStore();
import { usePermission } from '/@/hooks/web/usePermission';
const { hasPermission } = usePermission();
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 && 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() {
if (!hasPer.value) return message.info('无权限');
openModal(true, { isUpdate: false, isChild: false });
}
// 添加子级部门
function onAddChildDepart(data = currentDepart.value) {
if (!hasPer.value) return message.info('无权限');
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) {
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 (!hasPer.value) return message.info('无权限');
if (data) {
onVisibleChange(false);
doDeleteDepart([data.id], false);
}
}
// 批量删除部门
async function onDeleteBatch() {
try {
if (!hasPer.value) return message.info('无权限');
await doDeleteDepart(checkedKeys);
checkedKeys.value = [];
} finally {
}
}
function onVisibleChange(visible) {
if (!visible) {
visibleTreeKey.value = null;
}
}
function onImportXls(d) {
if (!hasPer.value) return message.info('无权限');
handleImportXls(d, Api.importExcelUrl, () => {
loadRootTreeData();
});
}
function onExportXls() {
if (!hasPer.value) return message.info('无权限');
handleExportXls('部门信息', Api.exportXlsUrl);
}
defineExpose({
loadRootTreeData,
});
const hasPer = computed(() => {
// 修改此字段
return (
(!!userInfo?.roleCodes && (userInfo?.roleCodes.includes('admin') || userInfo?.roleCodes.includes('system'))) ||
(hasPermission(orgManageApi.departManageList) && currentDepart.value?.manager)
);
});
</script>
@@ -0,0 +1,176 @@
<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">
<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 preIcon="ant-design:save-filled" type="primary" @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';
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
// 当前选中的部门ID,可能会为空,代表未选择部门
const departId = computed(() => props.data?.id);
const prefixCls = inject('prefixCls');
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();
treeData.value = treeList;
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;
}
/*begin 兼容暗夜模式*/
.j-box-bottom-button-float {
background-color: @component-background;
border-top: 1px solid @border-color-base;
}
/*end 兼容暗夜模式*/
</style>
@@ -0,0 +1,147 @@
<template>
<BasicDrawer width="800px" :title="isUpdate ? '修改部门管理员' : '添加部门管理员'" @register="registerModal" destroyOnClose @ok="handleSubmit">
<BasicForm @register="registerForm">
<template #roles="{ model, field }">
<a-input v-model:value="model[field]" readonly style="cursor: pointer" @click="clickShowTransfer" />
</template>
</BasicForm>
<transfer-modal @register="registerTransferModal" :orgInfo="{ orgCode: props.orgCode, departId: props.departId }" @success="handleSuccess" />
</BasicDrawer>
</template>
<script setup lang="ts">
import BasicForm from '/@/components/Form/src/BasicForm.vue';
import { useForm } from '/@/components/Form';
import { ref } from 'vue';
import { formSchema } from '/@/views/system/depart/components/departManage/departManageList.data';
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue';
import { useDrawerInner } from '/@/components/Drawer';
import { addUserEndowDepartManager } from '/@/views/system/depart/components/departManage/departManageList.api';
import TransferModal from '/@/views/system/depart/components/departManage/components/transferModal.vue';
import { useModal } from '/@/components/Modal';
import { dealPassword } from '/@/views/system/user/user.data';
const props = defineProps({
orgCode: { type: String, default: () => '' },
departId: { type: String, default: () => '' },
});
const [registerTransferModal, { openModal }] = useModal();
const roleCodes = ref<string>('');
const listData = ref<Array<any>>([]);
const isUpdate = ref<boolean>(false);
const emit = defineEmits(['success']);
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
//labelWidth: 150,
schemas: formSchema(isUpdate.value),
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
const [registerModal, { closeDrawer, setDrawerProps }] = useDrawerInner(async (data) => {
await resetFields();
roleCodes.value = '';
listData.value = [];
setDrawerProps({ confirmLoading: false, showFooter: data.showFooter, showOkBtn: data.showFooter, showCancelBtn: data.showFooter });
// listData.value = [];
// isUpdate.value = data.isUpdate;
//
// if (unref(isUpdate)) {
// await setFieldsValue({
// ...data.record,
// });
// await clearValidate();
// }
});
function clickShowTransfer() {
openModal(true, {
list: listData.value,
});
}
function preSubmit(values) {
return {
...values,
extension: values,
};
}
async function handleSubmit() {
try {
const values = await validate();
setDrawerProps({ confirmLoading: true });
// if (!values.birthday) {
// values.birthday = values.idCard.substring(6, 10) + '-' + values.idCard.substring(10, 12) + '-' + values.idCard.substring(12, 14);
// }
// values['orgCode'] = props.orgCode;
// setDrawerProps({ confirmLoading: true });
// //提交表单
values['roleCodes'] = roleCodes.value;
values['orgCode'] = props.orgCode;
values['password'] = dealPassword(values['password']);
await addUserEndowDepartManager(values);
closeDrawer();
//刷新列表
emit('success');
} finally {
setDrawerProps({ confirmLoading: false });
}
}
function handleSuccess(list) {
listData.value = list;
let ids = '';
let names = '';
list.map((item, index) => {
item = JSON.parse(item);
if (index === 0) {
ids += item.id;
names += item.roleCode;
} else {
ids += ',' + item.id;
names += ',' + item.roleCode;
}
});
setFieldsValue({
roles: names,
});
roleCodes.value = ids;
}
</script>
<style scoped lang="less">
:deep(.ant-input-number) {
width: 100%;
}
.question-choose {
text-align: center;
position: relative;
&:before {
content: '';
width: 36%;
height: 1px;
background-color: #000000;
position: absolute;
left: 0;
top: 50%;
}
&:after {
content: '';
width: 36%;
height: 1px;
background-color: #000000;
position: absolute;
right: 0;
top: 50%;
}
}
:deep(.ant-col-sm-18) {
max-width: 100%;
}
:deep(.input-number .ant-input-number-input-wrap input) {
text-align: center;
}
</style>
@@ -0,0 +1,125 @@
<template>
<BasicDrawer width="50%" title="选择用户" @register="registerModal" destroyOnClose @close="handleClose">
<BasicTable @register="registerTable">
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
</BasicTable>
<transfer-modal @register="registerTransferModal" :orgInfo="{ orgCode: props.orgCode, departId: props.departId }" @success="handleSuccess" />
</BasicDrawer>
</template>
<script setup lang="ts">
import BasicTable from '/@/components/Table/src/BasicTable.vue';
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue';
import { useListPage } from '/@/hooks/system/useListPage';
import { noManagerUsers } from '/@/views/system/depart/components/departManage/departManageList.api';
import { selectColumns, searchFormSchemaSelect } from '/@/views/system/depart/components/departManage/departManageList.data';
import { useDrawerInner } from '/@/components/Drawer';
import { TableAction } from '/@/components/Table';
import TransferModal from '/@/views/system/depart/components/departManage/components/transferModal.vue';
import { useModal } from '/@/components/Modal';
const props = defineProps({
orgCode: { type: String, default: () => '' },
departId: { type: String, default: () => '' },
});
const emit = defineEmits(['reloadTable']);
const [registerTransferModal, { openModal }] = useModal();
//注册table数据
const { tableContext } = useListPage({
tableProps: {
title: '员工',
api: noManagerUsers,
columns: selectColumns,
canResize: false,
clickToRowSelect: false,
beforeFetch: (params) => {
if (params['orgCode2']) {
params['orgCode'] = params['orgCode2'];
} else {
params['orgCode'] = params['orgCode1'];
}
return params;
},
formConfig: {
//labelWidth: 120,
schemas: searchFormSchemaSelect,
autoSubmitOnEnter: true,
showAdvancedButton: false,
fieldMapToNumber: [],
fieldMapToTime: [],
labelWidth: 100,
baseColProps: {
xs: 12,
sm: 12,
md: 8,
lg: 8,
xl: 8,
xxl: 8,
},
actionColOptions: {
style: {
paddingLeft: '104px',
},
span: 24,
offset: 0,
xs: 12,
sm: 12,
md: 8,
lg: 8,
xl: 8,
xxl: 8,
},
},
actionColumn: {
width: 80,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
function getTableAction(record: Recordable) {
return [
{
label: record.departId && record.departId.includes(props.departId) ? '修改' : '绑定',
onClick: handleBind.bind(null, record),
},
];
}
const [registerModal, { closeDrawer }] = useDrawerInner(async () => {
selectedRowKeys.value = [];
});
function handleBind(record: Recordable) {
openModal(true, {
record,
});
// if (!props.orgCode) {
// return message.error('请选择需要管理的部门');
// }
//
// relevanceDepartManager(props.orgCode, data).then(() => {
// closeDrawer();
// emit('reloadTable');
// });
}
function handleSuccess() {
reload();
}
function handleClose() {
emit('reloadTable');
}
</script>
<style scoped lang="less">
:deep(.ant-form-item-control-input-content) {
//display: flex;
}
</style>
@@ -0,0 +1,87 @@
<template>
<BasicModal @register="transferModal" destroyOnClose @ok="handleSubmit" zIndex="1002" width="700px" title="选择角色">
<div style="display: flex; align-items: center; justify-content: center">
<a-transfer
v-model:target-keys="targetKeys"
v-model:selected-keys="selectedKeys"
:data-source="mockData"
show-search
:list-style="{
width: '250px',
height: '500px',
}"
:filter-option="filterOption"
:titles="['无权限', '已有权限']"
:render="(item) => item.roleName"
@change="handleChange"
@select-change="handleSelectChange"
@scroll="handleScroll"
/>
</div>
</BasicModal>
</template>
<script setup lang="ts">
import BasicModal from '/@/components/Modal/src/BasicModal.vue';
import { ref } from 'vue';
import { addManagerDepartAndEndowRole, listCustom, userRoles } from '/@/views/system/depart/components/departManage/departManageList.api';
import { useModalInner } from '/@/components/Modal';
import { propTypes } from '/@/utils/propTypes';
const props = defineProps({
orgInfo: propTypes.object.def({ orgCode: '', departId: '' }),
});
const userId = ref<string>('');
const targetKeys = ref<Array<any>>([]);
const selectedKeys = ref<Array<any>>([]);
const mockData = ref<Array<any>>([]);
const emit = defineEmits(['success']);
const [transferModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
selectedKeys.value = [];
mockData.value = [];
userId.value = data?.record?.userId;
targetKeys.value = userId.value ? [] : data.list;
const { records } = await listCustom({ pageNo: 1, pageSize: 9999, systemFlag: false });
let haveList = [];
if (userId.value) {
haveList = await userRoles({ userId: data.record.userId, pageNo: 1, pageSize: 9999 });
}
mockData.value = records.map((item: any) => {
// targetKeys.value?.push(item.roleCode);
if (haveList.includes(item.roleCode)) {
targetKeys.value?.push(item.id);
}
// selectedKeys.value?.push(item.id);
return { ...{ key: userId.value ? item.id : JSON.stringify({ id: item.id, roleCode: item.roleName }) }, ...item };
});
});
function handleChange() {}
function handleSelectChange() {}
function handleScroll() {}
async function handleSubmit() {
try {
if (userId.value) {
let params = {
...props.orgInfo,
userId: userId.value,
roleIds: targetKeys.value,
};
setModalProps({ confirmLoading: true });
await addManagerDepartAndEndowRole(params);
closeModal();
emit('success');
} else {
closeModal();
emit('success', targetKeys.value);
}
} finally {
setModalProps({ confirmLoading: false });
}
}
function filterOption(inputValue: string, option) {
return option.roleName.indexOf(inputValue) > -1;
}
</script>
<style scoped lang="less"></style>
@@ -0,0 +1,28 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
list = '/health-system/sys/sysDepart/departManagers',
addDepartManager = '/health-system/sys/healthUserEmployeeEx/addDepartManager',
// noManagerUsers = '/health-system/sys/user/noManagerUsers',
noManagerUsers = '/health-system/sys/user/listUser',
listCustom = '/health-system/sys/role/listCustom',
userRoles = '/health-system/sys/user/userRoles',
relevanceDepartManager = '/health-system/sys/sysDepart/relevanceDepartManager',
cancelDepartManager = '/health-system/sys/sysDepart/cancelDepartManager',
addManagerDepartAndEndowRole = '/health-system/sys/role/addManagerDepartAndEndowRole',
addUserEndowDepartManager = '/health-system/sys/user/addUserEndowDepartManager',
}
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
export const noManagerUsers = (params) => defHttp.get({ url: Api.noManagerUsers, params });
export const listCustom = (params) => defHttp.get({ url: Api.listCustom, params });
export const userRoles = (params) => defHttp.get({ url: Api.userRoles, params });
export const addManagerDepartAndEndowRole = (params) => defHttp.post({ url: Api.addManagerDepartAndEndowRole, params });
export const addUserEndowDepartManager = (params) => defHttp.post({ url: Api.addUserEndowDepartManager, params });
export const addDepartManager = (params) => defHttp.post({ url: Api.addDepartManager, params });
export const relevanceDepartManager = (orgCode, params) => defHttp.post({ url: Api.relevanceDepartManager + '?orgCode=' + orgCode, params });
export const cancelDepartManager = (orgCode, params) => defHttp.post({ url: Api.cancelDepartManager + '?orgCode=' + orgCode, params });
@@ -0,0 +1,250 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { getSecondaryDepartmentList, getThirdDepartmentList } from '/@/views/system/user/user.api';
import { message } from 'ant-design-vue';
import { checkPassword } from '/@/hooks/checkPassword/checkPassword';
//列表数据
export const columns: BasicColumn[] = [
{
title: '员工账号',
align: 'center',
dataIndex: 'userName',
},
{
title: '员工姓名',
align: 'center',
dataIndex: 'realName',
},
// {
// title: '员工编号',
// align: 'center',
// dataIndex: 'workNo',
// },
{
title: '单位名称',
align: 'center',
dataIndex: 'secondDepart',
},
{
title: '部门名称',
align: 'center',
dataIndex: 'depart',
},
];
export const selectColumns: BasicColumn[] = [
{
title: '员工账号',
align: 'center',
fixed: 'left',
dataIndex: 'userName',
},
{
title: '员工姓名',
align: 'center',
fixed: 'left',
dataIndex: 'realName',
},
// {
// title: '员工编号',
// align: 'center',
// dataIndex: 'workNo',
// },
{
title: '单位名称',
align: 'center',
dataIndex: 'secondDepart',
},
{
title: '部门名称',
align: 'center',
dataIndex: 'depart',
},
];
//查询数据
export const searchFormSchema: FormSchema[] = [
{
label: '员工姓名',
field: 'name',
component: 'Input',
},
];
export const searchFormSchemaSelect: FormSchema[] = [
{
label: '单位',
field: 'orgCode1',
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: getSecondaryDepartmentList,
resultField: 'result',
labelField: 'departName',
valueField: 'orgCode',
immediate: true,
onChange: (_value, option) => {
formModel.secondOrgId = option?.id || '';
formModel.secondOrgCode = option?.orgCode || '';
if (formModel?.thirdOrgId) {
formModel.thirdOrgId = '';
formModel.thirdOrgCode = '';
}
},
onDeselect: () => {
formModel.thirdOrgId = '';
formModel.thirdOrgCode = '';
},
showSearch: true,
filterOption: (input: string, option: any): boolean => {
const str: string = input.toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
};
},
},
{
label: '部门',
field: 'orgCode2',
component: 'ApiSelect',
componentProps: ({ formModel }) => {
return {
api: getThirdDepartmentList,
resultField: 'list',
labelField: 'departName',
params: {
secondDepartId: formModel?.secondOrgId || 'defghjkl',
},
valueField: 'orgCode',
immediate: false,
onChange: (_value, option) => {
formModel.thirdOrgId = option?.id || '';
formModel.thirdOrgCode = option?.orgCode || '';
},
onDeselect: () => {
formModel.thirdOrgId = '';
formModel.thirdOrgCode = '';
},
onFocus: () => {
if (!formModel.secondOrgId) {
return message.warn('请先选择单位!');
}
},
showSearch: true,
filterOption: (input: string, option: any): boolean => {
const str: string = input.toLowerCase();
return option.label.toLowerCase().indexOf(str) >= 0;
},
};
},
},
{
label: '员工姓名',
field: 'realName',
component: 'Input',
},
{
label: '员工编号',
field: 'workNo',
component: 'Input',
},
];
//表单数据
export const schema: FormSchema[] = [
{
label: '员工姓名',
field: 'name',
component: 'Input',
},
];
export const formSchema: (isUpdate: boolean) => FormSchema[] = (isUpdate) => {
return [
{
label: '用户账号',
field: 'username',
component: 'Input',
componentProps: ({ formModel }) => {
return {
onInput: () => {
formModel.username = formModel.username.replace(/[^a-zA-Z0-9_]/g, '');
},
autocomplete: 'off',
};
},
required: true,
dynamicDisabled: isUpdate,
},
{
label: '密码',
field: 'password',
component: 'StrengthMeter',
componentProps: {
autocomplete: 'new-password',
},
dynamicRules: () => {
return [
{
required: true,
validator: (_, value) => {
let { message } = checkPassword(value);
if (message === 'ok') {
return Promise.resolve();
} else {
return Promise.reject(message);
}
},
trigger: 'blur',
},
];
},
required: !isUpdate,
show: !isUpdate,
},
{
label: '姓名',
field: 'realname',
component: 'Input',
required: true,
},
{
label: '员工编号', // 原来为工号
field: 'workNo',
component: 'Input',
required: true,
ifShow: false,
},
{
label: '角色',
field: 'roles',
component: 'Input',
slot: 'roles',
required: true,
},
// {
// label: '部门',
// field: 'orgCode',
// component: 'JSelectDeptStaff',
// componentProps: ({ formModel }) => {
// return {
// sync: true,
// checkStrictly: true,
// defaultExpandLevel: 1,
// initOptions: isUpdate
// ? [
// {
// value: formModel.departId,
// label: ' ' + formModel.secondDepartName + ' - ' + formModel.departName + ' ',
// },
// ]
// : [],
// };
// },
// required: true,
// },
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
];
};
@@ -0,0 +1,175 @@
<template>
<div>
<!--引用表格-->
<BasicTable :rowSelection="rowSelection" @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" v-auth="'system:depart-manage-list:add'" @click="handleAdd">
新增
</a-button>
<a-button type="primary" @click="selectUser">选择用户</a-button>
<a-button type="primary" @click="largeUnbind">批量解绑</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getAction(record)" />
</template>
</BasicTable>
<!-- 表单区域 -->
<depart-manage-list-drawer @register="registerModal" @success="handleSuccess" :orgCode="props.data?.orgCode" />
<select-no-manager @register="registerModal1" @reloadTable="handleSuccess" :orgCode="props.data?.orgCode" :departId="props.data?.id" />
<RestPass @register="restPassModal" />
<transfer-modal
@register="registerTransferModal"
:orgInfo="{ orgCode: props.data?.orgCode, departId: props.data?.id }"
@success="handleSuccess"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useDrawer } from '/@/components/Drawer';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, searchFormSchema } from './departManageList.data';
import { list, cancelDepartManager } from './departManageList.api';
import DepartManageListDrawer from '/@/views/system/depart/components/departManage/components/departManageListDrawer.vue';
import SelectNoManager from '/@/views/system/depart/components/departManage/components/selectNoManager.vue';
import { message } from 'ant-design-vue';
import RestPass from '/@/views/system/user/restPass/RestPass.vue';
import { useModal } from '/@/components/Modal';
import TransferModal from '/@/views/system/depart/components/departManage/components/transferModal.vue';
const [registerModal, { openDrawer }] = useDrawer();
const [registerModal1, { openDrawer: openDrawer1 }] = useDrawer();
const [restPassModal, { openModal: restPassModel }] = useModal();
const [registerTransferModal, { openModal: openModal1 }] = useModal();
const showFooter = ref(true);
function selectUser() {
openDrawer1(true);
}
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
watch(
() => props.data?.orgCode,
() => {
reload({ page: 1 });
}
);
//注册table数据
const { tableContext } = useListPage({
tableProps: {
title: '员工',
api: list,
rowKey: 'userId',
columns,
canResize: false,
clickToRowSelect: false,
beforeFetch: (params) => {
params['orgCode'] = props.data?.orgCode;
return params;
},
formConfig: {
labelWidth: 100,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: false,
fieldMapToNumber: [],
fieldMapToTime: [],
baseColProps: {
xs: 8, // <576px
sm: 8, // ≥576px
md: 8, // ≥768px
lg: 8, // ≥992px
xl: 12, // ≥1200px
xxl: 12, // ≥1600px
},
actionColOptions: {
offset: 0,
span: 8,
xs: 8, // <576px
sm: 8, // ≥576px
md: 8, // ≥768px
lg: 8, // ≥992px
xl: 12, // ≥1200px
xxl: 12, // ≥1600px
},
},
actionColumn: {
width: 170,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
/**
* 新增事件
*/
function handleAdd() {
showFooter.value = true;
openDrawer(true, {
isUpdate: false,
showFooter: true,
type: '新增',
});
}
function handleUnBind(d) {
let orgCode = props.data?.orgCode;
if (!orgCode) {
return message.error('请选择需要管理的部门');
}
cancelDepartManager(orgCode, d).then(() => {
reload();
});
}
function largeUnbind() {
if (selectedRowKeys.value.length === 0) return message.info('请至少选择一条数据');
handleUnBind(selectedRowKeys.value);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
// 修改密码弹窗
function restPass(record) {
restPassModel(true, {
record: record.userId,
isUpdate: true,
showFooter: false,
});
}
/**
* 下拉操作栏
*/
function getAction(record) {
return [
{
label: '修改',
onClick: handleEditBind.bind(null, record),
},
{
label: '解绑',
onClick: handleUnBind.bind(null, [record.userId]),
},
{
label: '重置密码',
onClick: restPass.bind(null, record),
},
];
}
function handleEditBind(record: Recordable) {
openModal1(true, {
record,
});
}
</script>
<style scoped></style>
@@ -0,0 +1,95 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/health-system/sys/sysDepart/departUsers',
save = '/sys/healthUserEmployeeEx/add',
edit = '/sys/healthUserEmployeeEx/edit',
deleteOne = '/sys/healthUserEmployeeEx/delete',
deleteBatch = '/sys/healthUserEmployeeEx/deleteBatch',
importExcel = '/sys/healthUserEmployeeEx/importExcel',
exportXls = '/sys/healthUserEmployeeEx/exportXls',
queryById = '/sys/healthUserEmployeeEx/queryById',
}
/**
* 导出api
* @param params
*/
export const getExportUrl = Api.exportXls;
export const queryByIdUrl = Api.queryById;
/**
* 请求编辑数据
*/
export const resEditData = async (params) => {
return await defHttp.get({ url: Api.queryById, params }, { joinParamsToUrl: true });
};
/**
* 导入api
*/
export const getImportUrl = Api.importExcel;
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除单个
*/
export const deleteOne = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否确认删除',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp
.delete(
{
url: Api.deleteOne,
data: params,
},
{ joinParamsToUrl: true }
)
.then(() => {
handleSuccess();
});
},
});
};
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
@@ -0,0 +1,48 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { h } from 'vue';
import { Image, message } from 'ant-design-vue';
import { getDefaultImage, getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getSecondaryDepartmentList, getThirdDepartmentList } from '/@/views/system/user/user.api';
//列表数据
export const columns: BasicColumn[] = [
{
title: '用户账号',
align: 'center',
fixed: 'left',
dataIndex: 'username',
},
{
title: '姓名',
align: 'center',
fixed: 'left',
dataIndex: 'realname',
},
{
title: '性别',
align: 'center',
dataIndex: 'sex_dictText',
width: 64,
},
{
title: '身份证号',
align: 'center',
dataIndex: 'idCard',
width: 160,
},
];
//查询数据
export const searchFormSchema: FormSchema[] = [
{
label: '用户账号',
field: 'username',
component: 'Input',
},
{
label: '用户姓名',
field: 'realname',
component: 'Input',
},
];
//表单数据
@@ -0,0 +1,212 @@
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 表单区域 -->
<!-- 重置密码 -->
<RestPass @register="restPassModal" />
</div>
</template>
<script lang="ts" setup>
import { ref, unref, watch } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useDrawer } from '/@/components/Drawer';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns, searchFormSchema } from './departUserList.data';
import { batchDelete, deleteOne, getExportUrl, getImportUrl, list, resEditData } from './departUserList.api';
import { message } from 'ant-design-vue';
import RestPass from '/@/views/system/user/restPass/RestPass.vue';
import { useModal } from '/@/components/Modal';
import { lockUser } from '/@/views/system/user/hospitalDoctor/HospitalDoctor.api';
const [registerDrawer, { openDrawer }] = useDrawer();
const [restPassModal, { openModal: restPaddModel }] = useModal();
const showFooter = ref(true);
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
watch(
() => props.data?.orgCode,
() => {
reload();
}
);
//注册table数据
const { tableContext } = useListPage({
tableProps: {
title: '员工',
api: list,
columns,
canResize: false,
clickToRowSelect: false,
formConfig: {
//labelWidth: 120,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: false,
fieldMapToNumber: [],
fieldMapToTime: [],
actionColOptions: {
offset: 0,
span: 8,
xs: 8, // <576px
sm: 8, // ≥576px
md: 8, // ≥768px
lg: 8, // ≥992px
xl: 8, // ≥1200px
xxl: 8, // ≥1600px
},
},
actionColumn: {
width: 80,
fixed: 'right',
},
},
exportConfig: {
name: '员工',
url: getExportUrl,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { selectedRowKeys }] = tableContext;
/**
* 新增事件
*/
function handleAdd() {
showFooter.value = true;
openDrawer(true, {
isUpdate: false,
type: '新增',
});
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable, type) {
showFooter.value = true;
getEdit(record, type);
}
/**
* 详情
*/
function handleDetail(record: Recordable, type) {
showFooter.value = false;
getEdit(record, type);
}
/**
* 获取编辑数据
*/
function getEdit(record, type) {
resEditData({ id: record.id }).then((res) => {
openDrawer(true, {
record: {
...res.extension,
...res,
orgCode: res?.depart?.id,
departId: res?.depart?.id,
departName: res?.depart?.departName,
secondDepartName: res?.secondDepart?.departName,
},
type,
isUpdate: true,
showFooter: unref(showFooter),
});
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
if (selectedRowKeys.value.length === 0) {
message.warning('未选中任何数据');
return;
}
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [];
}
// 重置密码
function restPass(record) {
restPaddModel(true, {
record: record.id,
isUpdate: true,
showFooter: false,
});
}
// 1 锁定用户 2 激活用户
async function handleUserOpt(record, type) {
const params = {
userId: record.id,
status: type,
};
await lockUser(params, type, handleSuccess);
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
// {
// label: '编辑',
// onClick: handleEdit.bind(null, record, '编辑'),
// auth: 'system:health_user_employee_ex:edit',
// },
// {
// label: '详情',
// onClick: handleDetail.bind(null, record, '详情'),
// },
// {
// label: '删除',
// onClick: handleDelete.bind(null, record),
// auth: 'system:health_user_employee_ex:delete',
// },
{
label: '重置密码',
onClick: restPass.bind(null, record),
},
{
label: '锁定用户',
auth: 'system:user:frozen',
onClick: handleUserOpt.bind(null, record, 2),
},
{
label: '激活用户',
auth: 'system:user:frozen',
onClick: handleUserOpt.bind(null, record, 1),
},
];
}
</script>
<style scoped>
:deep(.ant-table-title) {
display: none;
}
</style>
+125
View File
@@ -0,0 +1,125 @@
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',
departTree = '/sys/core/getDepartTree',
}
/**
* 获取部门树列表
*/
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?) => {
const 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 });
export const departTree = (params) => defHttp.get({ url: Api.departTree, params });
+219
View File
@@ -0,0 +1,219 @@
import { FormSchema } from '/@/components/Form';
import { getDictCache } from '/@/utils/dict';
import { isQH } from '/@/utils/getEnv';
function checkPhone(_, value) {
const rule = /^[0-9,]+$/;
if (value && !rule.test(value)) {
return Promise.reject('只能输入数字和英文逗号!');
}
if (value?.length > 11) {
const reg = /^1[3456789]\d{9}$/;
const strs = value.split(',');
for (let i = 0; i < strs.length; i++) {
if (!reg.test(strs[i])) {
return Promise.reject('第' + (i + 1) + '个手机号码格式不正确!');
}
}
}
return Promise.resolve();
}
// 部门基础表单
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: {
disabled: true,
},
},
{
field: 'orgClass',
label: '机构分类',
component: 'JDictSelectTag',
defaultValue: '1',
componentProps: () => {
return {
type: 'radio',
dictCode: 'orgclass',
allowClear: false,
};
},
},
{
field: 'departOrder',
label: '排序',
component: 'InputNumber',
componentProps: {},
},
{
field: 'mobile',
label: '电话',
component: 'Input',
componentProps: {
placeholder: '请输入电话',
},
},
{
field: 'fax',
label: '传真',
component: 'Input',
componentProps: {
placeholder: '请输入传真',
},
},
{
label: '健康管理员姓名',
field: 'healthManageUserName',
component: 'Input',
},
{
label: '健康管理员电话',
field: 'healthManageUserMobile',
component: 'Input',
componentProps: () => {
return {
placeholder: '请输入健康管理员电话,多个电话请用英文逗号隔开',
};
},
dynamicRules: () => {
return [
{
validator: checkPhone,
trigger: 'blur',
},
];
},
},
{
label: '健康管理员地址',
field: 'healthManageUserAddress',
component: 'Input',
},
{
label: '职业体检管理员姓名',
field: 'medialManageUserName',
component: 'Input',
show: !isQH(),
},
{
label: '职业体检管理员电话',
field: 'medialManageUserMobile',
component: 'Input',
show: !isQH(),
componentProps: () => {
return {
placeholder: '请输入体检管理员电话,多个电话请用英文逗号隔开',
};
},
dynamicRules: () => {
return [
{
validator: checkPhone,
trigger: 'blur',
},
];
},
},
{
label: '职业体检管理员地址',
field: 'medialManageUserAddress',
component: 'Input',
show: !isQH(),
},
{
label: '健康监测管理员姓名',
field: 'monitorManageUserName',
component: 'Input',
show: !isQH(),
},
{
label: '健康监测管理员电话',
field: 'monitorManageUserMobile',
component: 'Input',
componentProps: () => {
return {
placeholder: '请输入健康监测管理员电话,多个电话请用英文逗号隔开',
};
},
show: !isQH(),
dynamicRules: () => {
return [
{
validator: checkPhone,
trigger: 'blur',
},
];
},
},
{
label: '详细地址',
field: 'address',
component: 'Input',
slot: 'addressInfo',
// required: true,
},
{
label: '经度',
field: 'lng',
component: 'InputNumber',
show: false,
},
{
label: '纬度',
field: 'lat',
component: 'InputNumber',
show: false,
},
{
field: 'memo',
label: '备注',
component: 'InputTextArea',
componentProps: {
placeholder: '请输入备注',
},
},
];
return { basicFormSchema };
}
// 机构类型选项
export const orgCategoryOptions = {
// 一级部门
root: [{ value: '1', label: '公司' }],
// 子级部门
child: [
{ value: '2', label: '部门' },
{ value: '3', label: '岗位' },
],
};
+10
View File
@@ -0,0 +1,10 @@
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-depart-manage';
.@{prefix-cls} {
&--box {
.ant-tabs-nav {
padding: 0 20px;
}
}
}
+137
View File
@@ -0,0 +1,137 @@
<template>
<div class="outer">
<div class="inner">
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10" style="height: 100%">
<a-col :xl="12" :lg="24" :md="24" class="col-l">
<DepartLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
</a-col>
<a-col :xl="12" :lg="24" :md="24" class="col-r">
<div style="height: 100%" class="depart-manage">
<a-tabs v-show="departData != null" v-model:activeKey="activeKey" defaultActiveKey="base-info">
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
<div style="padding: 20px; overflow: hidden; background-color: #ffffff">
<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-tab-pane
tab="部门管理员"
key="depart-manage"
v-if="(hasPermission(orgManageApi.departManageList) && manager) || hasPer"
>
<div style="padding: 0 20px 20px">
<depart-manage-list :data="departData" />
</div>
</a-tab-pane>
<a-tab-pane tab="部门用户" key="depart-user" v-if="hasPermission(orgManageApi.departUserList)">
<div style="padding: 0 20px 20px">
<depart-user-list :activeKey="activeKey" :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>
</div>
</div>
</template>
<script lang="ts" setup name="system-depart">
import { provide, ref, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import DepartLeftTree from './components/DepartLeftTree.vue';
import DepartFormTab from './components/DepartFormTab.vue';
import DepartUserList from './components/departUser/departUserList.vue';
import DepartManageList from './components/departManage/departManageList.vue';
import { usePermission } from '/@/hooks/web/usePermission';
import { orgManageApi } from '/@/utils/auth/buttonAuth/system';
import { useUserStore } from '/@/store/modules/user';
const { hasPermission } = usePermission();
const { prefixCls } = useDesign('depart-manage');
provide('prefixCls', prefixCls);
// 给子组件定义一个ref变量
const leftTree = ref();
const activeKey = ref('base-info');
// 当前选中的部门信息
const departData = ref({});
const rootTreeData = ref<any[]>([]);
const { userInfo } = useUserStore();
// 左侧树选择后触发
function onTreeSelect(data) {
departData.value = data;
manager.value = data?.manager;
if (data?.orgType == 1) {
activeKey.value = 'base-info';
}
}
const manager = ref();
// 左侧树rootTreeData触发
function onRootTreeData(data) {
rootTreeData.value = data;
}
function onSuccess() {
leftTree.value.loadRootTreeData();
}
const hasPer = computed(() => {
// 修改此字段
return !!userInfo?.roleCodes && (userInfo?.roleCodes.includes('admin') || userInfo?.roleCodes.includes('system'));
});
</script>
<style lang="less">
@import './index.less';
</style>
<style lang="less" scoped>
/*begin 兼容暗夜模式*/
.depart-manage {
background-color: @component-background;
}
/*end 兼容暗夜模式*/
.outer {
position: relative;
width: 100%;
height: 100%;
}
.inner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
:deep(.spin-tree .ant-tree .ant-tree-icon-hide) {
height: 100px !important;
}
.col-l,
.col-r {
height: 100%;
overflow: auto;
}
.col-l {
background-color: #ffffff;
}
:deep(.col-l, .col-r) {
width: calc(50% - 20px);
padding: 0 !important;
}
</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,158 @@
<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 :loading="loading" ghost type="primary" @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';
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;
treeData.value = treeList;
// 查询角色授权
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,88 @@
<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,196 @@
<template>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @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' },
},
},
// 请求之前对参数做处理
beforeFetch(params) {
params.deptId = departId.value;
},
},
});
// 注册 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(() => {
reload();
});
// 清空选择的行
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,229 @@
<template>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="selectAddUser">添加已有用户</a-button>
<a-button preIcon="ant-design:plus-outlined" type="primary" @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' },
},
},
// 请求之前对参数做处理
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>
@@ -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?) => {
const 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 });
@@ -0,0 +1,195 @@
import { Ref } from 'vue';
import { duplicateCheck } 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) => {
const params = {
tableName: 'sys_depart_role',
fieldName: 'role_code',
fieldVal: value,
dataId: model.id,
};
duplicateCheck(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) {
const 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
View 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
View 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>
@@ -0,0 +1,132 @@
<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 #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
</BasicTable>
</BasicDrawer>
<DictItemModal @register="registerModal" @success="reload" :dictId="dictId" />
</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 { 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({
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,
},
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,63 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" width="800px">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { defineProps, ref, computed, unref, reactive } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form';
import { itemFormSchema } from '../dict.data';
import { saveOrUpdateDictItem } from '../dict.api';
// 声明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 });
}
}
</script>
@@ -0,0 +1,52 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" width="500px" @ok="handleSubmit">
<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';
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 '/@/components/Modal';
import { BasicTable, useTable, TableAction } from '/@/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>
+145
View File
@@ -0,0 +1,145 @@
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',
fakeSwitch = '/health-system/test/fake',
fakeRestore = '/health-system/test/restore',
fakeUser = '/health-system/test/fakeUser',
}
/**
* 导出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) => {
const 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) => {
const 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 });
export const fakeSwitch = (userId) => defHttp.post({ url: Api.fakeSwitch + '?userId=' + userId }, { isTransformResponse: false });
export const fakeRestore = () => defHttp.post({ url: Api.fakeRestore }, { isTransformResponse: false });
export const fakeUser = () => defHttp.post({ url: Api.fakeUser });
+184
View File
@@ -0,0 +1,184 @@
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { dictItemCheck } from './dict.api';
import { rules } from '/@/utils/helper/validator';
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,
},
];
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) => {
const 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: '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,
},
},
];
+192
View File
@@ -0,0 +1,192 @@
<template>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleCreate"> 新增</a-button>
<a-button preIcon="ant-design:export-outlined" type="primary" @click="onExportXls"> 导出</a-button>
<j-upload-button preIcon="ant-design:import-outlined" type="primary" @click="onImportXls">导入</j-upload-button>
<a-button preIcon="ant-design:sync-outlined" type="primary" @click="handlerRefreshCache"> 刷新缓存</a-button>
<a-button preIcon="ant-design:hdd-outlined" type="primary" @click="openRecycleModal(true)"> 回收站</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" />
删除
</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)" />
</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 { BasicTable, TableAction } from '/@/components/Table';
import { useDrawer } from '/@/components/Drawer';
import { useModal } from '/@/components/Modal';
import DictItemList from './components/DictItemList.vue';
import DictModal from './components/DictModal.vue';
import DictRecycleBinModal from './components/DictRecycleBinModal.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { columns, searchFormSchema } from './dict.data';
import { batchDeleteDict, deleteDict, getExportUrl, getImportUrl, list, queryAllDictItems, refreshCache } from './dict.api';
import { useListPage } from '/@/hooks/system/useListPage';
import { clearDictCache, setDictCache } from '/@/utils/dict';
const { createMessage } = useMessage();
//字典model
const [registerModal, { openModal }] = useModal();
//字典配置drawer
const [registerDrawer, { openDrawer }] = useDrawer();
//回收站model
const [registerModal1, { openModal: openRecycleModal }] = useModal();
// 列表页面公共参数、方法
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
designScope: 'dict-template',
tableProps: {
title: '数据字典',
api: list,
columns: columns,
beforeFetch: (params) => {
params['dictName'] = params['dictName'] ? `*${params['dictName']}*` : '';
params['dictCode'] = params['dictCode'] ? `*${params['dictCode']}*` : '';
return params;
},
formConfig: {
schemas: searchFormSchema,
},
actionColumn: {
width: 200,
fixed: 'right',
},
},
//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();
clearDictCache();
const dictItems = res.result;
setDictCache('', dictItems, true);
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>
@@ -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, defineProps } 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>
@@ -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) => {
const 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();
});
},
});
};
+220
View File
@@ -0,0 +1,220 @@
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',
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',
},
];
+315
View File
@@ -0,0 +1,315 @@
<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 v-model:value="queryParam.name" placeholder="请输入名称模糊查询" />
</a-form-item>
</a-col>
<a-col :lg="8">
<a-form-item label="年龄">
<a-input v-model:value="queryParam.age_begin" placeholder="最小年龄" style="width: calc(50% - 15px)" type="ge" />
<span>~</span>
<a-input v-model:value="queryParam.age_end" placeholder="最大年龄" style="width: calc(50% - 15px)" type="le" />
</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 v-model:value="min" placeholder="最小年龄" style="width: calc(50% - 15px)" type="ge" @change="ageChange(model, field)" />
<span>~</span>
<a-input v-model:value="max" placeholder="最大年龄" style="width: calc(50% - 15px)" type="le" @change="ageChange(model, field)" />
</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" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button
>批量操作
<Icon icon="ant-design:down-outlined" style="fontsize: 12px" />
</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>
@@ -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>
@@ -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
View File
@@ -0,0 +1,112 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { duplicateCheck } 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('请输入规则编码!');
}
const params = {
tableName: 'sys_fill_rule',
fieldName: 'rule_code',
fieldVal: value,
dataId: model.id,
};
duplicateCheck(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
View 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 preIcon="ant-design:export-outlined" type="primary" @click="onExportXls"> 导出</a-button>
<j-upload-button preIcon="ant-design:import-outlined" type="primary" @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" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作</span>
<Icon icon="mdi:chevron-down" />
</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>
@@ -0,0 +1,167 @@
<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" />
</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>
@@ -0,0 +1,292 @@
<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>
</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';
//下一步控制
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;
}
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD });
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>
+303
View File
@@ -0,0 +1,303 @@
<template>
<div :class="prefixCls" class="login-background-img">
<!-- <AppLocalePicker :showText="false" class="absolute top-4 right-4 enter-x xl:text-gray-600" />-->
<!-- <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />-->
<div v-show="type === 'login'">
<div class="aui-content">
<div class="aui-top">
<div class="aui-title">{{ t('sys.login.loginTitle') }}</div>
<div class="aui-title-image"></div>
</div>
<div class="aui-container">
<div class="aui-form">
<div class="aui-formBox">
<div class="aui-formWell">
<div class="form-title">{{ t('sys.login.formTitle') }}</div>
<div class="aui-form-box" style="height: 180px">
<a-form ref="loginRef" :model="formData" @keyup.enter.native="loginHandleClick">
<div class="aui-account">
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input
v-model:value="formData.username"
:placeholder="t('sys.login.userName')"
class="fix-auto-fill"
/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-password"></i>
<a-form-item>
<a-input
v-model:value="formData.password"
:placeholder="t('sys.login.password')"
class="fix-auto-fill"
type="password"
/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input
v-model:value="formData.inputCode"
:placeholder="t('sys.login.inputCode')"
class="fix-auto-fill"
type="text"
/>
</a-form-item>
<div class="aui-code">
<img
v-if="randCodeData.requestCodeSuccess"
:src="randCodeData.randCodeImage"
@click="handleChangeCheckCode"
/>
<img
v-else
:src="codeImg"
style="margin-top: 2px; max-width: initial"
@click="handleChangeCheckCode"
/>
</div>
</div>
<div class="aui-flex remember">
<div class="aui-flex-box">
<div class="aui-choice">
<a-input v-model:value="rememberMe" class="fix-auto-fill" type="checkbox" />
<span class="aui-check">{{ t('sys.login.rememberMe') }}</span>
</div>
</div>
</div>
</div>
</a-form>
</div>
<div class="aui-formButton">
<div class="aui-flex">
<a-button
:loading="loginLoading"
class="aui-link-login aui-flex-box"
type="primary"
@click="loginHandleClick"
>
{{ t('sys.login.loginButton') }}
</a-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="login-mini" setup>
import { getCodeInfo } from '/@/api/sys/user';
import { onMounted, reactive, ref, toRaw, watch } from 'vue';
import codeImg from '/@/assets/images/checkcode.png';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from '/@/hooks/web/useDesign';
import { encipher } from '/@/utils/jsencrypt';
const { prefixCls } = useDesign('mini-login');
const { notification, createMessage } = useMessage();
const userStore = useUserStore();
const { t } = useI18n();
const randCodeData = reactive<any>({
randCodeImage: '',
requestCodeSuccess: false,
checkKey: null,
});
const rememberMe = ref<string>('0');
const type = ref<string>('login');
//账号登录表单字段
const formData = reactive<any>({
inputCode: '',
username: '',
password: '',
});
const loginRef = ref();
const loginLoading = ref<boolean>(false);
defineProps({
sessionTimeout: {
type: Boolean,
},
});
/**
* 获取验证码
*/
function handleChangeCheckCode() {
formData.inputCode = '';
randCodeData.checkKey = 1629428467008;
getCodeInfo(randCodeData.checkKey).then((res) => {
randCodeData.randCodeImage = res;
randCodeData.requestCodeSuccess = true;
});
}
/**
* 账号或者手机登录
*/
async function loginHandleClick() {
accountLogin();
}
watch(
() => formData.password,
() => {
formData.password = formData.password.replace(/[\u4e00-\u9fa5]/g, '');
}
);
async function accountLogin() {
if (!formData.username) {
createMessage.warn(t('sys.login.accountPlaceholder'));
return;
}
if (!formData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
if (!formData.inputCode) {
createMessage.warn(t('sys.login.inputCodePlaceholder'));
return;
}
try {
loginLoading.value = true;
const { userInfo } = await userStore.login(
toRaw({
password: encipher(formData.password) as string,
// 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;
}
}
onMounted(() => {
//加载验证码
handleChangeCheckCode();
});
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home.less';
@import '/@/assets/loginmini/style/base.less';
</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;
}
}
input.fix-auto-fill,
.fix-auto-fill input {
-webkit-text-fill-color: #c9d1d9 !important;
box-shadow: inherit !important;
}
&-sign-in-way {
.anticon {
font-size: 22px !important;
color: #888 !important;
cursor: pointer !important;
&:hover {
color: @primary-color !important;
}
}
}
.ant-divider-inner-text {
font-size: 12px !important;
color: @text-color-secondary !important;
}
.aui-third-login a {
background: transparent;
}
}
</style>
+303
View File
@@ -0,0 +1,303 @@
<template>
<div :class="prefixCls" class="login-background-img">
<!-- <AppLocalePicker :showText="false" class="absolute top-4 right-4 enter-x xl:text-gray-600" />-->
<!-- <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />-->
<div v-show="type === 'login'">
<div class="aui-content">
<div class="aui-top">
<div class="aui-title">{{ t('sys.login.loginTitle') }}</div>
<div class="aui-title-image"></div>
</div>
<div class="aui-container">
<div class="aui-form">
<div class="aui-formBox">
<div class="aui-formWell">
<div class="form-title">{{ t('sys.login.formTitle') }}</div>
<div class="aui-form-box" style="height: 180px">
<a-form ref="loginRef" :model="formData" @keyup.enter.native="loginHandleClick">
<div class="aui-account">
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input
v-model:value="formData.username"
:placeholder="t('sys.login.userName')"
class="fix-auto-fill"
/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-password"></i>
<a-form-item>
<a-input
v-model:value="formData.password"
:placeholder="t('sys.login.password')"
class="fix-auto-fill"
type="password"
/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-code"></i>
<a-form-item>
<a-input
v-model:value="formData.inputCode"
:placeholder="t('sys.login.inputCode')"
class="fix-auto-fill"
type="text"
/>
</a-form-item>
<div class="aui-code">
<img
v-if="randCodeData.requestCodeSuccess"
:src="randCodeData.randCodeImage"
@click="handleChangeCheckCode"
/>
<img
v-else
:src="codeImg"
style="margin-top: 2px; max-width: initial"
@click="handleChangeCheckCode"
/>
</div>
</div>
<div class="aui-flex remember">
<div class="aui-flex-box">
<div class="aui-choice">
<a-input v-model:value="rememberMe" class="fix-auto-fill" type="checkbox" />
<span class="aui-check">{{ t('sys.login.rememberMe') }}</span>
</div>
</div>
</div>
</div>
</a-form>
</div>
<div class="aui-formButton">
<div class="aui-flex">
<a-button
:loading="loginLoading"
class="aui-link-login aui-flex-box"
style="background: #53b173; color: #ffffff"
@click="loginHandleClick"
>
{{ t('sys.login.loginButton') }}
</a-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="login-mini" setup>
import { getCodeInfo } from '/@/api/sys/user';
import { onMounted, reactive, ref, toRaw, watch } from 'vue';
import codeImg from '/@/assets/images/checkcode.png';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from '/@/hooks/web/useDesign';
import { encipher } from '/@/utils/jsencrypt';
const { prefixCls } = useDesign('mini-login');
const { notification, createMessage } = useMessage();
const userStore = useUserStore();
const { t } = useI18n();
const randCodeData = reactive<any>({
randCodeImage: '',
requestCodeSuccess: false,
checkKey: null,
});
const rememberMe = ref<string>('0');
const type = ref<string>('login');
//账号登录表单字段
const formData = reactive<any>({
inputCode: '',
username: '',
password: '',
});
const loginRef = ref();
const loginLoading = ref<boolean>(false);
defineProps({
sessionTimeout: {
type: Boolean,
},
});
/**
* 获取验证码
*/
function handleChangeCheckCode() {
formData.inputCode = '';
randCodeData.checkKey = 1629428467008;
getCodeInfo(randCodeData.checkKey).then((res) => {
randCodeData.randCodeImage = res;
randCodeData.requestCodeSuccess = true;
});
}
/**
* 账号或者手机登录
*/
async function loginHandleClick() {
accountLogin();
}
watch(
() => formData.password,
() => {
formData.password = formData.password.replace(/[\u4e00-\u9fa5]/g, '');
}
);
async function accountLogin() {
if (!formData.username) {
createMessage.warn(t('sys.login.accountPlaceholder'));
return;
}
if (!formData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
if (!formData.inputCode) {
createMessage.warn(t('sys.login.inputCodePlaceholder'));
return;
}
try {
loginLoading.value = true;
const { userInfo } = await userStore.login(
toRaw({
password: encipher(formData.password) as string,
// 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;
}
}
onMounted(() => {
//加载验证码
handleChangeCheckCode();
});
</script>
<style lang="less" scoped>
@import '/@/assets/loginmini/style/home1.less';
@import '/@/assets/loginmini/style/base.less';
</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;
}
}
input.fix-auto-fill,
.fix-auto-fill input {
-webkit-text-fill-color: #c9d1d9 !important;
box-shadow: inherit !important;
}
&-sign-in-way {
.anticon {
font-size: 22px !important;
color: #888 !important;
cursor: pointer !important;
&:hover {
color: @primary-color !important;
}
}
}
.ant-divider-inner-text {
font-size: 12px !important;
color: @text-color-secondary !important;
}
.aui-third-login a {
background: transparent;
}
}
</style>
+298
View File
@@ -0,0 +1,298 @@
<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>
</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';
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');
/**
* 返回
*/
function goBackHandleClick() {
emit('go-back');
initForm();
}
/**
* 获取手机验证码
*/
async function getLoginCode() {
if (!formData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}
const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.REGISTER });
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>
@@ -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
View 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
View File
@@ -0,0 +1,54 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" width="700px">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { defineProps, 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>
+113
View File
@@ -0,0 +1,113 @@
<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';
// 声明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;
let treeProps = data.record?.parentId ? { treeDefaultExpandedKeys: [data.record.parentId] } : {};
//获取下拉树信息
const treeData = await list();
updateSchema([
{
field: 'parentId',
componentProps: { treeData, ...treeProps },
},
{
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);
}
await 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]菜单添加智能化处理------------
}
</script>
+202
View File
@@ -0,0 +1,202 @@
<template>
<div class="p-4">
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleCreate"> 新增菜单</a-button>
<a-button preIcon="ic:round-expand" type="primary" @click="expandAll">展开全部</a-button>
<a-button preIcon="ic:round-compress" type="primary" @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';
const checkedKeys = ref<Array<string | number>>([]);
const showFooter = ref(true);
const [registerDrawer, { openDrawer }] = useDrawer();
const [registerDrawer1, { openDrawer: openDataRule }] = useDrawer();
// 列表页面公共参数、方法
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: {
schemas: searchFormSchema,
autoAdvancedCol: 4,
baseColProps: { span: 12 },
actionColOptions: { xs: 24, sm: 12, md: 12, lg: 12, xl: 12, xxl: 12 },
},
actionColumn: {
width: 120,
fixed: 'right',
},
},
});
//注册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();
}
function onFetchSuccess() {
// 演示默认展开所有表项
nextTick(expandAll);
}
/**
* 操作栏
*/
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: '删除',
color: 'error',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
];
}
</script>
+121
View File
@@ -0,0 +1,121 @@
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) => {
const 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) => {
const 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 (!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 || '验证失败');
});
});
},
},
];
};
+415
View File
@@ -0,0 +1,415 @@
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/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: {
replaceFields: {
title: 'name',
key: 'id',
value: 'id',
},
dropdownStyle: {
maxHeight: '50vh',
},
getPopupContainer: (node) => node.parentNode,
},
ifShow: ({ values }) => !isDir(values.menuType),
},
{
field: 'url',
label: '访问路径',
component: 'Input',
required: true,
ifShow: ({ values }) => !(values.component === ComponentTypes.IFrame && values.internalOrExternal) && values.menuType !== 2,
//update-begin-author:zyf date:2022-11-02 for: 聚合路由允许路径重复
dynamicRules: ({ model, schema }) => {
return checkPermDuplication(model, schema, true);
},
//update-end-author:zyf date:2022-11-02 for: 聚合路由允许路径重复
},
{
field: 'component',
label: '前端组件',
component: 'Input',
componentProps: {
placeholder: '请输入前端组件',
},
defaultValue: 'layouts/RouteView',
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 }) => {
return values.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,181 @@
<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;
}
</style>
@@ -0,0 +1,497 @@
<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(0px, 0px, 0px)' : 'translate3d(120px, 0px, 0px)',
display: 'block',
width: '88px',
}"
></div>
</div>
</div>
</div>
<!-- 头部图标 -->
<div class="icon-right">
<div class="icons">
<a-popover placement="bottomRight" :overlayStyle="{ width: '400px' }" trigger="click" v-model:visible="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">
<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"
/>
<DetailModal @register="registerDetail" />
</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';
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';
export default {
name: 'SysMessageModal',
components: {
BasicModal,
FilterOutlined,
CloseOutlined,
BellFilled,
ExclamationOutlined,
JSelectUser,
SysMessageList,
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: '',
rangeDate: [],
});
function loadData() {
let params = {
fromUser: searchParams.fromUser,
rangeDateKey: searchParams.rangeDateKey,
rangeDate: searchParams.rangeDate,
};
if (activeKey.value == 'all') {
allMessageRef.value.reload(params);
} else {
starMessageRef.value.reload(params);
}
}
//useModalInner
const [registerModal, { closeModal }] = useModalInner(async () => {
//每次弹窗打开 加载最新的数据
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: '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 clearAll() {
searchParams.fromUser = '';
searchParams.realname = '';
searchParams.rangeDateKey = '';
searchParams.rangeDate = [];
for (let a of dateTags) {
a.active = false;
}
}
const [registerDetail, { openModal: openDetailModal }] = useModal();
function showDetailModal(record) {
console.error(123, 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;
&:hover {
color: #1890ff;
}
&:active {
color: #1890ff;
}
}
> span.filtera {
background-color: #d3eafd;
border-radius: 4px;
cursor: pointer;
height: 27px;
padding-top: 7px;
margin-top: 3px;
line-height: 25px;
color: #2196f3;
display: flex;
> span.anticon {
height: 30px;
line-height: 9px;
display: inline-block;
}
}
}
}
.ant-tabs-nav-wrap {
display: inline-block;
width: calc(100% - 340px);
}
.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;
color: #2196f3;
}
}
}
}
}
.selected-user {
background: #f5f5f5;
padding: 2px 14px;
border-radius: 30px;
.clear-user-icon {
margin-left: 5px;
.anticon {
&:hover {
color: #2196f3;
}
}
}
}
</style>
@@ -0,0 +1,226 @@
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';
/**
* 列表接口
* @param params
*/
const queryMessageList = (params) => {
const url = '/sys/annountCement/vue3List';
return defHttp.get({ url, params });
};
/**
* 获取消息列表数据
*/
export function useSysMessage() {
const rangeDateArray = getDictItemsByCode('rangeDate');
console.log('+++++++++++++++++++++');
console.log('rangeDateArray', rangeDateArray);
console.log('+++++++++++++++++++++');
const messageList = ref<any[]>([]);
const pageNo = ref(1);
const pageSize = 10;
const searchParams = reactive({
fromUser: '',
rangeDateKey: '',
rangeDate: [],
starFlag: '',
});
function getQueryParams() {
const { fromUser, rangeDateKey, rangeDate, starFlag } = searchParams;
const 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;
}
const 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;
const temp: any[] = messageList.value;
temp.push(...data);
messageList.value = temp;
}
//重置
function reset() {
messageList.value = [];
pageNo.value = 1;
loadEndStatus.value = false;
}
//标星
function updateStarMessage(item) {
const url = '/sys/sysAnnouncementSend/edit';
let starFlag = '1';
if (item.starFlag == starFlag) {
starFlag = '0';
}
const params = {
starFlag,
id: item.sendId,
};
defHttp.put({ url, params });
}
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_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) {
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) {
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;
const 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 用于查询表单数据
const query: any = {
detailId: busId,
};
// 额外参数处理
if (msgAbstract) {
try {
const 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
View 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" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作</span>
<Icon icon="mdi:chevron-down" />
</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>
@@ -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 });
}
};
@@ -0,0 +1,139 @@
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: 'esReceiver',
component: 'Input',
},
{
label: '发送状态',
field: 'esSendStatus',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'msgSendStatus',
},
},
{
label: '发送方式',
field: 'esType',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'es_type',
},
},
];
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: 'es_type' },
},
{
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} {
}
+211
View File
@@ -0,0 +1,211 @@
<template>
<div :class="prefixCls">
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="onAdd">新增</a-button>
<!-- <a-button preIcon="ant-design:export-outlined" type="primary" @click="onExportXls"> 导出</a-button>
<j-upload-button preIcon="ant-design:import-outlined" type="primary" @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" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作</span>
<Icon icon="mdi:chevron-down" />
</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, {
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/sendTestSms',
}
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,184 @@
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: '请选择模板类型',
disabled: true,
},
required: true,
defaultValue: '1',
},
{
label: '是否应用',
field: 'useStatus',
component: 'JSwitch',
componentProps: {
options: ['1', '0'],
},
defaultValue: '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: 'Input',
},
];
@@ -0,0 +1,89 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
// list = '/sys/api/messageRemind/selectUserMessageList',
list = '/sys/sysMessageRemind/list',
save = '/sys/sysMessageRemind/add',
edit = '/sys/sysMessageRemind/edit',
deleteOne = '/sys/sysMessageRemind/delete',
deleteBatch = '/sys/sysMessageRemind/deleteBatch',
importExcel = '/sys/sysMessageRemind/importExcel',
exportXls = '/sys/sysMessageRemind/exportXls',
}
/**
* 导出api
* @param params
*/
export const getExportUrl = Api.exportXls;
/**
* 导入api
*/
export const getImportUrl = Api.importExcel;
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除单个
*/
export const deleteOne = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp
.delete(
{
url: Api.deleteOne,
data: params,
},
{ joinParamsToUrl: true }
)
.then(() => {
handleSuccess();
});
},
});
};
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp
.delete(
{
url: Api.deleteBatch,
data: params,
},
{ joinParamsToUrl: true }
)
.then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或者更新
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
@@ -0,0 +1,108 @@
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
//列表数据
export const columns: BasicColumn[] = [
{
title: '标题',
align: 'center',
dataIndex: 'title',
},
// {
// title: '内容',
// align: 'center',
// dataIndex: 'content',
// slots: 'htmlSlot',
// },
{
title: '类型',
align: 'center',
dataIndex: 'type_dictText',
},
{
title: '创建时间',
align: 'center',
dataIndex: 'createTime',
},
];
//查询数据
export const searchFormSchema: FormSchema[] = [
{
label: '标题',
field: 'title',
component: 'Input',
},
{
label: '类型',
field: 'type',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'message_remind',
},
},
];
//表单数据
export const formSchema: FormSchema[] = [
// {
// label: '用户id',
// field: 'userId',
// component: 'Input',
// },
{
label: '类型',
field: 'type',
required: true,
component: 'JDictSelectTag',
componentProps: {
dictCode: 'message_remind',
},
},
{
label: '用户姓名',
field: 'userName',
component: 'Input',
required: ({ values }): boolean => values.type != '1',
// componentProps: ({ formModel, schema }) => {
// schema.required = formModel.type == '1';
// },
componentProps: {
placeholder: '请选择用户',
},
// ifShow: ({ values }): boolean => values.type == '1',
},
{
label: '标题',
field: 'title',
component: 'Input',
required: true,
},
{
label: '',
field: 'userId',
component: 'Input',
show: false,
},
{
label: '内容',
field: 'content',
component: 'JEditor',
componentProps: {
showImageUpload: true,
},
required: true,
},
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
];
/**
* 流程表单调用这个方法获取formSchema
* @param _formData
*/
export function getBpmFormSchema(_formData): FormSchema[] {
// 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
return formSchema;
}
@@ -0,0 +1,142 @@
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" @click="onAdd" preIcon="ant-design:plus-outlined"> 新增 </a-button>
<a-button type="primary" preIcon="ant-design:delete-outlined" @click="batchHandleDelete">批量删除 </a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
<!--字段回显插槽-->
<template #htmlSlot="{ text }">
<div v-html="text"></div>
</template>
</BasicTable>
<!-- 表单区域 -->
<SysMessageRemindModal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="system-sysMessageRemind" setup>
import { ref } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import SysMessageRemindModal from './components/SysMessageRemindModal.vue';
import { columns, searchFormSchema } from './SysMessageRemind.data';
import { list, deleteOne, batchDelete } from './SysMessageRemind.api';
import { message } from 'ant-design-vue';
const checkedKeys = ref<Array<string | number>>([]);
//注册model
const [registerModal, { openModal }] = useModal();
//注册table数据
const { tableContext } = useListPage({
tableProps: {
title: '消息公告',
api: list,
columns,
canResize: false,
formConfig: {
//labelWidth: 120,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: false,
fieldMapToNumber: [],
fieldMapToTime: [],
},
actionColumn: {
width: 155,
fixed: 'right',
},
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
/**
* 新增事件
*/
function onAdd() {
openModal(true, {
isUpdate: false,
showFooter: true,
title: '新增',
});
}
/**
* 编辑事件
*/
function onEdit(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: true,
title: '编辑',
});
}
/**
* 详情
*/
function onDetail(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: false,
title: '详情',
});
}
/**
* 删除事件
*/
async function onDeleteOne(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
if (selectedRowKeys.value.length === 0) {
message.warning('未选中任何数据');
return;
}
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: onEdit.bind(null, record),
},
{
label: '详情',
onClick: onDetail.bind(null, record),
},
{
label: '删除',
onClick: onDeleteOne.bind(null, record),
},
];
}
</script>
<style scoped></style>
@@ -0,0 +1,70 @@
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm"></BasicForm>
<div style="width: 100%;text-align: center" v-if="!formDisabled">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary"> </a-button>
</div>
</div>
</template>
<script lang="ts">
import {BasicForm, useForm} from '/@/components/Form/index';
import {computed, defineComponent} from 'vue';
import {defHttp} from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import {getBpmFormSchema} from '../SysMessageRemind.data';
import {saveOrUpdate} from '../SysMessageRemind.api';
export default defineComponent({
name: "SysMessageRemindForm",
components:{
BasicForm
},
props:{
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
},
setup(props){
const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: {span: 24}
});
const formDisabled = computed(()=>{
if(props.formData.disabled === false){
return false;
}
return true;
});
let formData = {};
const queryByIdUrl = '/system/sysMessageRemind/queryById';
async function initFormData(){
let params = {id: props.formData.dataId};
const data = await defHttp.get({url: queryByIdUrl, params});
formData = {...data}
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({disabled: formDisabled.value})
}
async function submitForm() {
let data = getFieldsValue();
let params = Object.assign({}, formData, data);
console.log('表单数据', params)
await saveOrUpdate(params, true)
}
initFormData();
return {
registerForm,
formDisabled,
submitForm,
}
}
});
</script>
@@ -0,0 +1,100 @@
<template>
<BasicModal
style="z-index: 999"
v-bind="$attrs"
@register="registerModal"
destroyOnClose
:title="title"
:width="700"
@ok="handleSubmit"
:maskClosable="false"
>
<replace-other title="选择用户" ref="replaceOtherRef" @choose-employ="onSelectUserOk" />
<BasicForm @register="registerForm" />
</BasicModal>
<!-- <UserSelectModal style="z-index: 1001" rowKey="id" @register="registerSelUserModal" @get-select-result="onSelectUserOk" />-->
<!-- <replace-other ref="replaceOtherRef" @choose-employ="onSelectUserOk" />-->
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../SysMessageRemind.data';
import { saveOrUpdate } from '../SysMessageRemind.api';
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModalIllness.vue';
import { useDrawer } from '/@/components/Drawer';
import ReplaceOther from '/@/views/emergency/communication/components/replaceOther.vue';
const [registerSelUserModal, { openDrawer }] = useDrawer();
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const replaceOtherRef = ref();
// 设置标题
let title = ref('');
//表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate, clearValidate }] = useForm({
//labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
// let findIndex: number = formSchema.findIndex((item) => item.field === 'userName');
// if (formSchema[findIndex].componentProps) {
formSchema[1]!.componentProps.onClick = () => {
replaceOtherRef.value.showModal();
};
// }
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
title.value = data.title;
//重置表单
await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
await clearValidate();
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
});
// 选择用户成功
async function onSelectUserOk(d) {
const json: object = JSON.parse(d);
await setFieldsValue({
userName: json.realname,
userId: json.id,
});
}
//表单提交事件
async function handleSubmit() {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>
+24
View 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"></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>
+74
View File
@@ -0,0 +1,74 @@
<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, { setProps, resetFields, setFieldsValue, validate }] = useForm({
schemas: formSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({
confirmLoading: false,
showCancelBtn: !!data?.showFooter,
showOkBtn: !!data?.showFooter,
});
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
// if (data.record.userIds) {
// data.record.userIds = data.record.userIds.substring(0, data.record.userIds.length - 1);
// }
//表单赋值
if (data.record.msgType === 'DOC') {
//如果是指定专家,这里处理一下初始值
data.record.docIds = data.record.userIds;
data.record.userIds = '';
}
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
});
//设置标题
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.msgType === 'ALL_DOC') {//全选置空
values.userIds = '';
} else {
if (values.msgType === 'DOC'){
values.userIds = values.docIds;
}
// 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>
+143
View File
@@ -0,0 +1,143 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button preIcon="ant-design:plus-outlined" type="primary" @click="handleAdd">新增</a-button>
</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 {useGlobSetting} from '/@/hooks/setting';
import {columns, searchFormSchema} from './notice.data';
import {deleteNotice, getExportUrl, getImportUrl, getList, publishData, resEditData} 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, 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,
showFooter: true,
});
}
/**
* 编辑事件
*/
function handleEdit(record) {
resEditData({ id: record.id }).then((res) => {
openModal(true, {
record: res,
isUpdate: true,
showFooter: true,
});
});
}
function handleDetail(record) {
resEditData({ id: record.id }).then((res) => {
openModal(true, {
record: res,
isUpdate: true,
showFooter: false,
});
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteNotice({ id: record.id }, reload);
}
/**
* 批量删除事件
*/
// async function batchHandleDelete() {
// doRequest(() => batchDeleteNotice({ ids: selectedRowKeys.value }));
// }
/**
* 发布
*/
async function handleRelease(id) {
await publishData({ id: id }, reload);
}
/**
* 操作列定义
* @param record
*/
function getActions(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
ifShow: record.sendStatus == '0',
},
{
label: '发布',
disabled: record.sendStatus !== '0',
onClick: handleRelease.bind(null, record.id),
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '查看',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
disabled: record.sendStatus === '3',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
];
}
</script>
+89
View File
@@ -0,0 +1,89 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
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',
publishData = '/sys/annountCement/publishData',
queryById = '/sys/annountCement/queryById',
}
/**
* 导出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 });
};
/**
* 请求编辑数据
*/
export const resEditData = async (params, handleSuccess) => {
return await defHttp.get({ url: Api.queryById, 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 });
export const publishData = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认发布',
content: '是否发布该消息?发布之后会逐步发短信给用户',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.post({ url: Api.publishData, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
+199
View File
@@ -0,0 +1,199 @@
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_dictText',
width: 100,
},
/*{
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_dictText',
width: 100,
},
{
title: '创建人',
width: 100,
dataIndex: 'createBy',
},
{
title: '发布状态',
dataIndex: 'sendStatus',
width: 70,
customRender: ({ text }) => {
const color = text == '0' ? 'red' : text == '1' ? 'green' : 'orange';
return render.renderTag(render.renderDict(text, 'send_status'), color);
},
},
{
title: '发布人',
width: 100,
dataIndex: 'sender',
},
{
title: '发布时间',
width: 100,
dataIndex: 'sendTime',
},
{
title: '发布进度',
width: 100,
dataIndex: 'msgAbstract',
},
/*{
title: '撤销时间',
width: 100,
dataIndex: 'cancelTime',
},*/
];
export const searchFormSchema: FormSchema[] = [
{
field: 'titile',
label: '标题',
component: 'JInput',
colProps: { span: 8 },
},
{
field: 'sendStatus',
label: '发布状态',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'send_status',
},
colProps: { span: 8 },
},
];
export const formSchema: FormSchema[] = [
{
field: 'id',
label: 'id',
component: 'Input',
show: false,
},
{
field: 'msgCategory',
label: '消息类型',
required: true,
component: 'JDictSelectTag',
defaultValue: '3',
componentProps: {
type: 'radio',
dictCode: 'msg_category',
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: 'radioButton',
dictCode: 'msg_type',
placeholder: '请选择发布范围',
onChange: function (arg1) {
console.log(arg1);
},
},
},
{
field: 'userIds',
label: '指定用户',
component: 'JSelectUserByDept',
required: true,
componentProps: ({ formModel }) => {
return {
rowKey: 'id',
labelKey: 'realname',
// onChange: function (arg1) {
// formModel.userIds = arg1.join(',');
// }
};
},
ifShow: ({ values }) => values.msgType == 'USER',
},
{
field: 'docIds',
label: '指定专家',
component: 'JSelectDocByType',
required: true,
componentProps: ({ formModel }) => {
return {
rowKey: 'id',
labelKey: 'doctorName',
// onChange: function (arg1) {
// formModel.userIds = arg1.join(',');
// }
};
},
ifShow: ({ values }) => values.msgType == 'DOC',
},
/*{
field: 'priority',
label: '优先级',
defaultValue: 'H',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'priority',
type: 'radio',
placeholder: '请选择优先级',
},
},*/
{
field: 'titile',
label: '标题',
component: 'Input',
required: true,
componentProps: {
placeholder: '请输入标题',
},
},
{
field: 'msgContent',
label: '短信内容',
component: 'InputTextArea',
required: true,
componentProps: {
placeholder: '请输入短信内容',
},
},
];
@@ -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,59 @@
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 }) => {
const 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,63 @@
<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: {
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>
+161
View File
@@ -0,0 +1,161 @@
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable">
<!--插槽:table标题-->
<template #tableTitle>
<a-upload
name="file"
:showUploadList="false"
:action="ossAction"
:headers="tokenHeader"
:beforeUpload="beforeUpload"
@change="handleChange"
>
<a-button preIcon="ant-design:upload-outlined" type="primary">OSS文件上传</a-button>
</a-upload>
<a-upload
name="file"
:showUploadList="false"
:action="minioAction"
:headers="tokenHeader"
:beforeUpload="beforeUpload"
@change="handleChange"
>
<a-button preIcon="ant-design:upload-outlined" type="primary">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 { computed } 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';
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);
let filePath = encodeURIComponent(record.url);
//文档采用pdf预览高级模式
if (filePath.endsWith('.pdf') || filePath.endsWith('.doc') || filePath.endsWith('.docx')) {
filePath = filePath + '&officePreviewType=pdf';
}
let url = `${glob.viewUrl}?url=` + filePath;
window.open(url, '_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
View 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();
});
};

Some files were not shown because too many files have changed in this diff Show More