init
This commit is contained in:
124
src/views/admin/system/dept/form.vue
Normal file
124
src/views/admin/system/dept/form.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<el-dialog :title="dataForm.deptId ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" width="600">
|
||||
<el-form ref="deptDialogFormRef" :model="dataForm" label-width="90px" :rules="dataRules" v-loading="loading">
|
||||
<el-form-item :label="$t('sysdept.parentId')" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="dataForm.parentId"
|
||||
:data="parentData"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
class="w100"
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:placeholder="$t('sysdept.inputparentIdTip')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdept.name')" prop="name">
|
||||
<el-input v-model="dataForm.name" :placeholder="$t('sysdept.inputnameTip')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdept.sortOrder')" prop="sortOrder">
|
||||
<el-input-number v-model="dataForm.sortOrder" :placeholder="$t('sysdept.inputsortOrderTip')" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemDeptDialog">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getObj, deptTree, addObj, putObj } from '/@/api/admin/dept';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import {rule} from "/@/utils/validate";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const deptDialogFormRef = ref();
|
||||
const dataForm = reactive({
|
||||
parentId: '',
|
||||
deptId: '',
|
||||
name: '',
|
||||
sortOrder: 9999,
|
||||
});
|
||||
const parentData = ref<any[]>([]);
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataRules = ref({
|
||||
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
|
||||
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
|
||||
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (type: string, id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.deptId = '';
|
||||
|
||||
nextTick(() => {
|
||||
deptDialogFormRef.value?.resetFields();
|
||||
dataForm.parentId = id;
|
||||
});
|
||||
|
||||
if (type === 'edit') {
|
||||
getObj(id)
|
||||
.then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
}
|
||||
|
||||
getDeptData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const valid = await deptDialogFormRef.value.validate().catch(() => {});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
dataForm.deptId ? await putObj(dataForm) : await addObj(dataForm);
|
||||
useMessage().success(t(dataForm.deptId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 从后端获取菜单信息
|
||||
const getDeptData = async () => {
|
||||
deptTree().then((res) => {
|
||||
parentData.value = [];
|
||||
const dept = {
|
||||
id: '0',
|
||||
name: '根部门',
|
||||
children: [] as any[],
|
||||
};
|
||||
dept.children = res.data;
|
||||
parentData.value.push(dept);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
21
src/views/admin/system/dept/i18n/en.ts
Normal file
21
src/views/admin/system/dept/i18n/en.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
sysdept: {
|
||||
name: 'dept name',
|
||||
parentId: 'parent dept',
|
||||
createTime: 'createTime',
|
||||
weight: 'weight',
|
||||
leaderId: 'dept leader',
|
||||
sortOrder: 'sortOrder',
|
||||
inputdeptNameTip: 'input deptName',
|
||||
inputnameTip: 'input deptName',
|
||||
inputparentIdTip: 'select deptName',
|
||||
inputLeaderIdTip: 'input leader',
|
||||
inputsortOrderTip: 'input sortOrder',
|
||||
importTip: 'import dept',
|
||||
addNodeText:'add dept',
|
||||
editNodeText:'edit dept',
|
||||
delNodeText:'delete dept',
|
||||
view: 'tree/table view',
|
||||
tenantNodeErrorText: 'The current node cannot be operated. You need to maintain it in tenant management',
|
||||
},
|
||||
};
|
||||
21
src/views/admin/system/dept/i18n/zh-cn.ts
Normal file
21
src/views/admin/system/dept/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
sysdept: {
|
||||
name: '部门名称',
|
||||
parentId: '上级部门',
|
||||
createTime: '创建时间',
|
||||
weight: '排序',
|
||||
sortOrder: '排序',
|
||||
leaderId: '部门负责人',
|
||||
inputdeptNameTip: '请输入部门名称',
|
||||
inputnameTip: '请输入部门名称',
|
||||
inputLeaderIdTip: '请输入部门领导',
|
||||
inputparentIdTip: '请选择上级部门',
|
||||
inputsortOrderTip: '请输入排序',
|
||||
importTip: '导入部门',
|
||||
addNodeText: '添加部门',
|
||||
editNodeText: '编辑部门',
|
||||
delNodeText: '删除部门',
|
||||
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
|
||||
view: '树/表视图'
|
||||
},
|
||||
};
|
||||
130
src/views/admin/system/dept/index.vue
Normal file
130
src/views/admin/system/dept/index.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="filter">
|
||||
<el-form-item prop="deptName" :label="$t('sysdept.name')">
|
||||
<el-input :placeholder="$t('sysdept.inputdeptNameTip')" style="max-width: 180px" v-model="state.queryForm.deptName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="filter">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="top-right-btn" v-if="!defaultTreeViewRef" v-auth="'sys_dept_add'" @click="handleAdd">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="handleExpand"> {{ $t('common.expandBtn') }}</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_dept_add'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('queryTree.view')" placement="top">
|
||||
<el-button circle icon="Grid" @click="handleView"></el-button>
|
||||
</el-tooltip>
|
||||
</right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<tree-view ref="treeViewRef" v-if="defaultTreeViewRef" />
|
||||
<table-view ref="tableViewRef" v-if="!defaultTreeViewRef" />
|
||||
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysdept.importTip')"
|
||||
url="/admin/dept/import"
|
||||
temp-url="/admin/sys-file/local/file/dept.xlsx"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemDept" setup>
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const TreeView = defineAsyncComponent(() => import('./tree-view.vue'));
|
||||
const TableView = defineAsyncComponent(() => import('./table-view.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 默认树视图展示
|
||||
const defaultTreeViewRef = ref(true);
|
||||
const treeViewRef = ref();
|
||||
const tableViewRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const isExpand = ref(false);
|
||||
const queryRef = ref();
|
||||
|
||||
const state = reactive({
|
||||
queryForm: {
|
||||
deptName: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 过滤节点
|
||||
*/
|
||||
const filter = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.filter(state.queryForm.deptName);
|
||||
} else {
|
||||
tableViewRef.value.state.queryForm.deptName = state.queryForm.deptName;
|
||||
tableViewRef.value.getDataList();
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
tableViewRef.value.handleAdd();
|
||||
};
|
||||
const handleView = () => {
|
||||
defaultTreeViewRef.value = !defaultTreeViewRef.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理展开/折叠树
|
||||
*/
|
||||
const handleExpand = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.handleExpand();
|
||||
} else {
|
||||
tableViewRef.value.handleExpand();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出Excel
|
||||
*/
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/dept/export', state.queryForm, 'dept.xlsx');
|
||||
};
|
||||
|
||||
const getDataList = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.getOrgData();
|
||||
} else {
|
||||
tableViewRef.value.getDataList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置查询条件
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
137
src/views/admin/system/dept/table-view.vue
Normal file
137
src/views/admin/system/dept/table-view.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
default-expand-all
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle?.headerCellStyle"
|
||||
>
|
||||
<el-table-column :label="$t('sysdept.name')" prop="name" width="400" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysdept.weight')" prop="weight" show-overflow-tooltip width="80"></el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysdept.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" show-overflow-tooltip width="250">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="folder-add" @click="deptDialogRef.openDialog('add', scope.row?.id)"
|
||||
v-auth="'sys_dept_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button
|
||||
>
|
||||
<el-button text type="primary" icon="edit-pen" @click="deptDialogRef.openDialog('edit', scope.row?.id)"
|
||||
v-auth="'sys_dept_edit'">{{
|
||||
$t('common.editBtn')
|
||||
}}
|
||||
</el-button>
|
||||
<el-button text type="primary" icon="delete" @click="handleDelete(scope.row)" v-auth="'sys_dept_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<dept-form ref="deptDialogRef" @refresh="getDataList()"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemDept">
|
||||
import {BasicTableProps, useTable} from '/@/hooks/table';
|
||||
import {deptTree, delObj} from '/@/api/admin/dept';
|
||||
import {useMessage, useMessageBox} from '/@/hooks/message';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const DeptForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const {t} = useI18n();
|
||||
// 定义变量内容
|
||||
const tableRef = ref(); // 表格引用
|
||||
const deptDialogRef = ref(); // 部门对话框引用
|
||||
const excelUploadRef = ref(); // Excel上传引用
|
||||
const showSearch = ref(true); // 是否显示搜索栏
|
||||
const isExpand = ref(false); // 是否展开
|
||||
|
||||
/**
|
||||
* 查询部门树方法,返回 Promise 对象
|
||||
* @param params - 查询参数
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
const queryDeptTree = (params?: any) => {
|
||||
return deptTree(params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
*/
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryDeptTree, // 页面列表数据
|
||||
queryForm: {
|
||||
deptName: '', // 部门名称
|
||||
},
|
||||
isPage: false, // 是否分页
|
||||
descs: ['create_time'], // 排序字段
|
||||
});
|
||||
|
||||
/**
|
||||
* 使用 useTable 定义表格相关操作
|
||||
*/
|
||||
const {getDataList, tableStyle} = useTable(state);
|
||||
|
||||
/**
|
||||
* 展开/折叠部门树方法
|
||||
*/
|
||||
const handleExpand = async () => {
|
||||
isExpand.value = !isExpand.value;
|
||||
const dataList = await deptTree();
|
||||
toggleExpand(dataList.data, isExpand.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 递归方法,用于展开/折叠部门树
|
||||
* @param children - 子节点
|
||||
* @param unfold - 是否展开
|
||||
*/
|
||||
const toggleExpand = (children: any[], unfold = true) => {
|
||||
for (const key in children) {
|
||||
tableRef.value?.toggleRowExpansion(children[key], unfold);
|
||||
if (children[key].children) {
|
||||
toggleExpand(children[key].children!, unfold);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除当前行
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(row.id);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = ()=>{
|
||||
deptDialogRef.value.openDialog('add')
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露组件中的一些方法和变量
|
||||
*/
|
||||
defineExpose({
|
||||
handleAdd, // 新增时间
|
||||
state, // 响应式表格数据
|
||||
getDataList, // 获取列表数据方法
|
||||
handleExpand // 展开/折叠部门树方法
|
||||
});
|
||||
</script>
|
||||
198
src/views/admin/system/dept/tree-view.vue
Normal file
198
src/views/admin/system/dept/tree-view.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<vue3-tree-org
|
||||
ref="treeOrgRef"
|
||||
:props="props"
|
||||
:data="data"
|
||||
:label-style="style"
|
||||
:define-menus="defineMenus"
|
||||
:default-expand-level="expandLevel"
|
||||
center
|
||||
:horizontal="horizontal"
|
||||
:collapsable="collapsable"
|
||||
:only-one-node="onlyOneNode"
|
||||
:filter-node-method="filterNodeMethod"
|
||||
:clone-node-drag="cloneNodeDrag"
|
||||
:node-add="addNode"
|
||||
:node-delete="delNode"
|
||||
:node-edit="editNode"
|
||||
@on-node-click="onNodeClick"
|
||||
/>
|
||||
</div>
|
||||
<dept-form ref="deptDialogRef" @refresh="getOrgData()" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="treeView" setup>
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { delObj, deptTree } from '/@/api/admin/dept';
|
||||
import { getObj } from '/@/api/admin/tenant';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
const DeptForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义org组件key-value定义
|
||||
const props = reactive({ id: 'id', pid: 'parentId', label: 'name', expand: 'expand', children: 'children' });
|
||||
const data = reactive({});
|
||||
|
||||
// 定义org组件右键定义
|
||||
const defineMenus = reactive([
|
||||
{ name: t('sysdept.addNodeText'), command: 'add' },
|
||||
{ name: t('sysdept.editNodeText'), command: 'edit' },
|
||||
{ name: t('sysdept.delNodeText'), command: 'delete' },
|
||||
]);
|
||||
|
||||
const cloneNodeDrag = ref(true);
|
||||
const collapsable = ref(false);
|
||||
const expandLevel = ref(2); //默认展开层级
|
||||
const horizontal = ref(false);
|
||||
const onlyOneNode = ref(false);
|
||||
const treeOrgRef = ref();
|
||||
const deptDialogRef = ref();
|
||||
|
||||
// 添加主题配置
|
||||
const themeConfig = useThemeConfig();
|
||||
const { themeConfig: theme } = storeToRefs(themeConfig);
|
||||
|
||||
// 添加 style 定义
|
||||
const style = computed(() => ({
|
||||
background: theme.value.isDark ? 'var(--el-bg-color-overlay)' : 'var(--el-bg-color-page)',
|
||||
color: theme.value.isDark ? 'var(--el-text-color-primary)' : '#5e6d82',
|
||||
}));
|
||||
|
||||
/**
|
||||
* 过滤节点
|
||||
*/
|
||||
const filter = (deptName: string) => {
|
||||
treeOrgRef.value.filter(deptName);
|
||||
};
|
||||
|
||||
/**
|
||||
* 节点过滤方法
|
||||
* @param {string} value 过滤条件
|
||||
* @param {object} data 节点数据
|
||||
* @returns {boolean} 返回过滤结果
|
||||
*/
|
||||
const filterNodeMethod = (value, data) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理展开/折叠树
|
||||
*/
|
||||
const handleExpand = async () => {
|
||||
collapsable.value = !collapsable.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查节点是否是根租户节点
|
||||
* @param {object} node 节点对象
|
||||
* @returns {boolean} 如果节点是根租户节点,返回true;否则返回false
|
||||
*/
|
||||
const checkNode = (node) => {
|
||||
if (node?.id === '0') {
|
||||
useMessage().error(t('sysdept.tenantNodeErrorText'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 当用户左键点击节点,模拟触发组件的右键事件
|
||||
* @param e
|
||||
*/
|
||||
const onNodeClick = (e: any) => {
|
||||
const { clientX, clientY } = e;
|
||||
const rightFun = new MouseEvent('contextmenu', {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
view: window,
|
||||
button: 2,
|
||||
buttons: 0,
|
||||
clientX,
|
||||
clientY,
|
||||
});
|
||||
e.target.dispatchEvent(rightFun);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const addNode = (node) => {
|
||||
deptDialogRef.value.openDialog('add', node?.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const editNode = (node) => {
|
||||
if (!checkNode(node)) {
|
||||
return;
|
||||
}
|
||||
deptDialogRef.value.openDialog('edit', node?.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const delNode = async (node) => {
|
||||
if (!checkNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(node.id);
|
||||
await getOrgData();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询部门数据
|
||||
*/
|
||||
const getOrgData = async () => {
|
||||
// 查询当前租户信息
|
||||
const tenant = await getObj(Session.getTenant());
|
||||
deptTree().then((res) => {
|
||||
Object.assign(data, { id: '0', name: tenant.data.name });
|
||||
data.children = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getOrgData();
|
||||
});
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
getOrgData,
|
||||
handleExpand,
|
||||
filter,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.zm-tree-org) {
|
||||
height: 100%;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
background: var(--el-bg-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
280
src/views/admin/system/menu/form.vue
Normal file
280
src/views/admin/system/menu/form.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')"
|
||||
v-model="visible"
|
||||
width="600"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
>
|
||||
<el-form ref="menuDialogFormRef" :model="state.ruleForm" :rules="dataRules" label-width="100px" v-loading="loading">
|
||||
<el-form-item :label="$t('sysmenu.menuType')" prop="menuType">
|
||||
<el-radio-group v-model="state.ruleForm.menuType">
|
||||
<el-radio border label="0">菜单</el-radio>
|
||||
<el-radio border label="1">按钮</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysmenu.parentId')" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="state.ruleForm.parentId"
|
||||
:data="state.parentData"
|
||||
:render-after-expand="false"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
class="w100"
|
||||
clearable
|
||||
check-strictly
|
||||
:placeholder="$t('sysmenu.inputParentIdTip')"
|
||||
>
|
||||
</el-tree-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name">
|
||||
<template #label>
|
||||
{{ state.ruleForm.menuType === '0' ? t('sysmenu.name') : t('sysmenu.buttonName') }}
|
||||
</template>
|
||||
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputNameTip')"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysmenu.path')" prop="path" v-if="state.ruleForm.menuType === '0'">
|
||||
<el-input v-model="state.ruleForm.path" :placeholder="$t('sysmenu.inputPathTip')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysmenu.permission')" prop="permission" v-if="state.ruleForm.menuType === '1'">
|
||||
<template #label>
|
||||
{{ t('sysmenu.permission') }}
|
||||
<tip content="对应后台接口@PreAuthorize注解入参字符串"></tip>
|
||||
</template>
|
||||
<el-input v-model="state.ruleForm.permission" maxlength="30" :placeholder="$t('sysmenu.inputPermissionTip')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysmenu.sortOrder')" prop="sortOrder">
|
||||
<el-input-number v-model="state.ruleForm.sortOrder" :min="0" controls-position="right"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysmenu.icon')" prop="icon" v-if="state.ruleForm.menuType === '0'">
|
||||
<IconSelector :placeholder="$t('sysmenu.inputIconTip')" v-model="state.ruleForm.icon"/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="keepAlive" v-if="state.ruleForm.menuType === '0'">
|
||||
<template #label> {{ $t('sysmenu.keepAlive') }}
|
||||
<tip content="组件保留状态,避免重新渲染"/>
|
||||
</template>
|
||||
<el-radio-group v-model="state.ruleForm.keepAlive">
|
||||
<el-radio border label="0">否</el-radio>
|
||||
<el-radio border label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="visible" v-if="state.ruleForm.menuType === '0'">
|
||||
<template #label> {{ $t('sysmenu.visible') }}
|
||||
<tip content="左侧菜单树是否显示"/>
|
||||
</template>
|
||||
<el-radio-group v-model="state.ruleForm.visible">
|
||||
<el-radio border label="0">否</el-radio>
|
||||
<el-radio border label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="mt-4">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="param" v-if="state.ruleForm.menuType === '0'">
|
||||
<template #label> {{ $t('sysmenu.param') }}
|
||||
<tip content="多个路径指向同一个组件"/>
|
||||
</template>
|
||||
<el-radio-group v-model="state.ruleForm.param">
|
||||
<el-radio border label="0">否</el-radio>
|
||||
<el-radio border label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="embedded"
|
||||
v-if="state.ruleForm.menuType === '0' && state.ruleForm.path?.startsWith('http')">
|
||||
<template #label> {{ $t('sysmenu.embedded') }}
|
||||
<tip content="iframe嵌套还是打开独立的Tab"/>
|
||||
</template>
|
||||
<el-radio-group v-model="state.ruleForm.embedded">
|
||||
<el-radio border label="0">否</el-radio>
|
||||
<el-radio border label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item class="mt-4" :label="$t('sysmenu.component')" prop="component" v-if="state.ruleForm.menuType === '0'
|
||||
&& state.ruleForm.param === '1'">
|
||||
<el-input v-model="state.ruleForm.component" :placeholder="$t('sysmenu.inputComponentTip')"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemMenuDialog">
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {getObj, pageList, putObj, addObj, validateExist} from '/@/api/admin/menu';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import {rule, validateNull} from "/@/utils/validate";
|
||||
import Tip from "/@/components/Tip/index.vue";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const {t} = useI18n();
|
||||
// 引入组件
|
||||
const IconSelector = defineAsyncComponent(() => import('/@/components/IconSelector/index.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const menuDialogFormRef = ref();
|
||||
const originalName = ref(''); // To store the original menu name for comparison during edits
|
||||
// 定义需要的数据
|
||||
const state = reactive({
|
||||
ruleForm: {
|
||||
menuId: '',
|
||||
name: '',
|
||||
permission: '',
|
||||
parentId: '',
|
||||
icon: '',
|
||||
path: '',
|
||||
param: '0',
|
||||
component: '',
|
||||
sortOrder: 0,
|
||||
menuType: '1',
|
||||
keepAlive: '0',
|
||||
visible: '1',
|
||||
embedded: '0',
|
||||
},
|
||||
parentData: [] as any[], // 上级菜单数据
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
const dataRules = reactive({
|
||||
menuType: [{required: true, message: '菜单类型不能为空', trigger: 'blur'}],
|
||||
parentId: [{required: true, message: '上级菜单不能为空', trigger: 'blur'}],
|
||||
name: [{validator: rule.overLength, trigger: 'blur'}, {
|
||||
required: true,
|
||||
message: '菜单不能为空',
|
||||
trigger: 'blur'
|
||||
}, {
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
// 如果是按钮类型菜单,跳过名称唯一性校验
|
||||
if (state.ruleForm.menuType === '1') {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// 如果是编辑状态且菜单名称未改变,跳过校验
|
||||
if (state.ruleForm.menuId !== '' && value === originalName.value) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// 其他情况下,验证菜单名称唯一性
|
||||
validateExist(rule, value, callback, false);
|
||||
},
|
||||
trigger: 'blur',
|
||||
}],
|
||||
path: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '路径不能为空', trigger: 'blur'}],
|
||||
icon: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '图标不能为空', trigger: 'blur'}],
|
||||
permission: [{validator: rule.overLength, trigger: 'blur'}, {
|
||||
required: true,
|
||||
message: '权限代码不能为空',
|
||||
trigger: 'blur'
|
||||
}, {
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateExist(rule, value, callback, state.ruleForm.menuId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
}],
|
||||
sortOrder: [{required: true, message: '排序不能为空', trigger: 'blur'}],
|
||||
component: [{min: 5, max: 255, message: '组件名称长度必须介于 5 和 255 之间', trigger: 'blur'},
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (state.ruleForm.menuType === '0' && state.ruleForm.param === '1' && validateNull(state.ruleForm.component)) {
|
||||
callback(new Error('请输入组件名称'));
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
}],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (type: string, row?: any) => {
|
||||
state.ruleForm.menuId = '';
|
||||
visible.value = true;
|
||||
originalName.value = ''; // Reset the original name
|
||||
|
||||
nextTick(() => {
|
||||
menuDialogFormRef.value?.resetFields();
|
||||
state.ruleForm.parentId = row?.id || '-1';
|
||||
});
|
||||
|
||||
if (row?.id && type === 'edit') {
|
||||
state.ruleForm.menuId = row.id;
|
||||
// 获取当前节点菜单信息
|
||||
getMenuDetail(row.id);
|
||||
}
|
||||
// 渲染上级菜单列表树
|
||||
getAllMenuData();
|
||||
};
|
||||
|
||||
// 获取菜单节点的详细信息
|
||||
const getMenuDetail = (id: string) => {
|
||||
getObj({menuId: id}).then((res) => {
|
||||
if (res.data[0].component) {
|
||||
state.ruleForm.param = '1'
|
||||
}
|
||||
originalName.value = res.data[0].name; // Store the original name
|
||||
Object.assign(state.ruleForm, res.data[0]);
|
||||
});
|
||||
};
|
||||
|
||||
// 从后端获取菜单信息(含层级)
|
||||
const getAllMenuData = () => {
|
||||
state.parentData = [];
|
||||
pageList({
|
||||
type: '0',
|
||||
}).then((res) => {
|
||||
let menu = {
|
||||
id: '-1',
|
||||
name: '根菜单',
|
||||
children: [],
|
||||
};
|
||||
menu.children = res.data;
|
||||
state.parentData.push(menu);
|
||||
});
|
||||
};
|
||||
|
||||
// 保存数据
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const valid = await menuDialogFormRef.value.validate().catch(() => {
|
||||
});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
state.ruleForm.menuId ? await putObj(state.ruleForm) : await addObj(state.ruleForm);
|
||||
useMessage().success(t(state.ruleForm.menuId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量 只有暴漏出来的变量 父组件才能使用
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
37
src/views/admin/system/menu/i18n/en.ts
Normal file
37
src/views/admin/system/menu/i18n/en.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
sysmenu: {
|
||||
index: '#',
|
||||
name: 'menu name',
|
||||
buttonName: 'button name',
|
||||
sortOrder: 'sortOrder',
|
||||
path: 'path',
|
||||
menuType: 'menuType',
|
||||
keepAlive: 'keepAlive',
|
||||
permission: 'permission',
|
||||
inputNameTip: 'input name',
|
||||
parentId: 'parent menu',
|
||||
embedded: 'embedded',
|
||||
param: 'param',
|
||||
component: 'component',
|
||||
visible: 'visible',
|
||||
icon: 'icon',
|
||||
inputMenuIdTip: 'input menuId',
|
||||
inputPermissionTip: 'input permission',
|
||||
inputPathTip: 'input path',
|
||||
inputParentIdTip: 'input parentId',
|
||||
inputIconTip: 'input icon',
|
||||
inputVisibleTip: 'input visible',
|
||||
inputSortOrderTip: 'input sortOrder',
|
||||
inputKeepAliveTip: 'input keepAlive',
|
||||
inputMenuTypeTip: 'input menuType',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
inputEmbeddedTip: 'input embedded',
|
||||
inputComponentTip: 'input component',
|
||||
deleteDisabledTip: 'menu inclusion subordinates cannot be deleted',
|
||||
},
|
||||
};
|
||||
31
src/views/admin/system/menu/i18n/zh-cn.ts
Normal file
31
src/views/admin/system/menu/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
sysmenu: {
|
||||
index: '#',
|
||||
name: '菜单名称',
|
||||
buttonName: '按钮名称',
|
||||
sortOrder: '排序',
|
||||
path: '路由',
|
||||
menuType: '类型',
|
||||
keepAlive: '缓冲',
|
||||
permission: '权限标识',
|
||||
inputNameTip: '请输入菜单名称',
|
||||
parentId: '上级菜单',
|
||||
embedded: '内嵌',
|
||||
param: '带参',
|
||||
component: '组件',
|
||||
visible: '显示',
|
||||
icon: '图标',
|
||||
inputMenuIdTip: '',
|
||||
inputPermissionTip: '请输入权限标识',
|
||||
inputPathTip: '请输入路由路径',
|
||||
inputParentIdTip: '请选择上级菜单',
|
||||
inputIconTip: '请选择图标',
|
||||
inputVisibleTip: '请选择是否显示',
|
||||
inputSortOrderTip: '请输入排序',
|
||||
inputKeepAliveTip: '请选择是否缓冲',
|
||||
inputMenuTypeTip: '请选择菜单类型',
|
||||
inputEmbeddedTip: '请选择是否内嵌',
|
||||
inputComponentTip: '请输入组件名称',
|
||||
deleteDisabledTip: '菜单包含下级不能删除',
|
||||
},
|
||||
};
|
||||
197
src/views/admin/system/menu/index.vue
Normal file
197
src/views/admin/system/menu/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('sysmenu.name')" prop="menuName">
|
||||
<el-input :placeholder="$t('sysmenu.inputNameTip')" clearable style="max-width: 180px" v-model="state.queryForm.menuName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="query" class="ml10" icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="onOpenAddMenu" class="ml10" icon="folder-add" type="primary" v-auth="'sys_menu_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="tableList"
|
||||
lazy
|
||||
:load="load"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
row-key="path"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle?.headerCellStyle"
|
||||
>
|
||||
<el-table-column :label="$t('sysmenu.name')" fixed prop="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.sortOrder')" prop="sortOrder" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.icon')" prop="icon" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<SvgIcon :name="scope.row.meta.icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.path')" prop="path" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.menuType')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.menuType === '0'">左菜单</el-tag>
|
||||
<el-tag v-if="scope.row.menuType === '2'">顶菜单</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.menuType === '1'">按钮</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.keepAlive')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.meta.isKeepAlive">开启</el-tag>
|
||||
<el-tag type="info" v-else>关闭</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.permission')" :show-overflow-tooltip="true" prop="permission"></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" show-overflow-tooltip width="250">
|
||||
<template #default="scope">
|
||||
<el-button icon="folder-add" @click="onOpenAddMenu('add', scope.row)" text type="primary" v-auth="'sys_menu_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="edit-pen" @click="onOpenEditMenu('edit', scope.row)" text type="primary" v-auth="'sys_menu_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-tooltip icon="delete" :content="$t('sysmenu.deleteDisabledTip')" :disabled="!deleteMenuDisabled(scope.row)" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
:disabled="deleteMenuDisabled(scope.row)"
|
||||
@click="handleDelete(scope.row)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_menu_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<MenuDialog @refresh="query()" ref="menuDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemMenu" setup>
|
||||
import { delObj, pageList } from '/@/api/admin/menu';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
// 引入组件
|
||||
const MenuDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const tableRef = ref();
|
||||
const menuDialogRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const queryRef = ref();
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: pageList, // H
|
||||
queryForm: {
|
||||
parentId: -1,
|
||||
menuName: '',
|
||||
},
|
||||
isPage: false,
|
||||
});
|
||||
|
||||
const { getDataList, tableStyle } = useTable(state);
|
||||
|
||||
// 根据类型判断是否有子节点
|
||||
const setHasChildren = (arr: any[]) => {
|
||||
arr.forEach((item) => {
|
||||
// 添加 hasChildren 属性
|
||||
item.hasChildren = item.menuType !== '1';
|
||||
});
|
||||
};
|
||||
const tableList = computed(() => {
|
||||
const list = state.dataList;
|
||||
if (Array.isArray(list)) {
|
||||
setHasChildren(list);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
// 打开新增菜单弹窗
|
||||
const onOpenAddMenu = (type?: string, row?: any) => {
|
||||
menuDialogRef.value.openDialog(type, row);
|
||||
};
|
||||
// 打开编辑菜单弹窗
|
||||
const onOpenEditMenu = (type: string, row: any) => {
|
||||
menuDialogRef.value.openDialog(type, row);
|
||||
};
|
||||
|
||||
//是否禁用删除
|
||||
const deleteMenuDisabled = (row: any) => {
|
||||
return (row.children || []).length > 0;
|
||||
};
|
||||
|
||||
// 搜索事件
|
||||
const query = () => {
|
||||
state.dataList = [];
|
||||
state.queryForm.parentId = undefined;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
state.dataList = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const load = (row: any, treeNode: unknown, resolve: (date: any[]) => void) => {
|
||||
const param = {
|
||||
parentId: row.id,
|
||||
};
|
||||
pageList(param)
|
||||
.then((res) => {
|
||||
const childrenList = res.data || [];
|
||||
if (Array.isArray(childrenList)) {
|
||||
setHasChildren(childrenList);
|
||||
}
|
||||
resolve(childrenList);
|
||||
})
|
||||
.catch(() => {
|
||||
// Handle API error by resolving with empty array
|
||||
resolve([]);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(row.id);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
135
src/views/admin/system/post/form.vue
Normal file
135
src/views/admin/system/post/form.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<el-dialog :title="form.postId ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" width="600" :close-on-click-modal="false" draggable>
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-form-item :label="t('post.postCode')" prop="postCode">
|
||||
<el-input v-model="form.postCode" :placeholder="t('post.inputpostCodeTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.postName')" prop="postName">
|
||||
<el-input v-model="form.postName" :placeholder="t('post.inputpostNameTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.postSort')" prop="postSort">
|
||||
<el-input-number v-model="form.postSort" :placeholder="t('post.inputpostSortTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.remark')" prop="remark">
|
||||
<el-input type="textarea" maxlength="100" row="3" v-model="form.remark" :placeholder="t('post.inputremarkTip')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemPostDialog">
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj, validatePostCode, validatePostName } from '/@/api/admin/post';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {rule} from "/@/utils/validate";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
postId: '',
|
||||
postCode: '',
|
||||
postName: '',
|
||||
postSort: 0,
|
||||
remark: '',
|
||||
delFlag: '',
|
||||
createTime: '',
|
||||
createBy: '',
|
||||
updateTime: '',
|
||||
updateBy: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
postCode: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '岗位编码不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePostCode(rule, value, callback, form.postId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
postName: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '岗位名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePostName(rule, value, callback, form.postId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
postSort: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位排序不能为空', trigger: 'blur' }],
|
||||
remark: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位描述不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.postId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取Post信息
|
||||
if (id) {
|
||||
form.postId = id;
|
||||
getPostData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
form.postId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.postId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表格数据
|
||||
const getPostData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
28
src/views/admin/system/post/i18n/en.ts
Normal file
28
src/views/admin/system/post/i18n/en.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
post: {
|
||||
index: '#',
|
||||
importPostTip: ' import Post',
|
||||
postId: 'postId',
|
||||
postCode: 'postCode',
|
||||
postName: 'postName',
|
||||
postSort: 'postSort',
|
||||
remark: 'remark',
|
||||
delFlag: 'delFlag',
|
||||
createTime: 'createTime',
|
||||
createBy: 'createBy',
|
||||
updateTime: 'updateTime',
|
||||
updateBy: 'updateBy',
|
||||
tenantId: 'tenantId',
|
||||
inputpostIdTip: 'input postId',
|
||||
inputpostCodeTip: 'input postCode',
|
||||
inputpostNameTip: 'input postName',
|
||||
inputpostSortTip: 'input postSort',
|
||||
inputremarkTip: 'input remark',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputtenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
28
src/views/admin/system/post/i18n/zh-cn.ts
Normal file
28
src/views/admin/system/post/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
post: {
|
||||
index: '#',
|
||||
importPostTip: '导入岗位管理',
|
||||
postId: '岗位ID',
|
||||
postCode: '岗位编码',
|
||||
postName: '岗位名称',
|
||||
postSort: '岗位排序',
|
||||
remark: '岗位描述',
|
||||
delFlag: '是否删除 -1:已删除 0:正常',
|
||||
createTime: '创建时间',
|
||||
createBy: '创建人',
|
||||
updateTime: '更新时间',
|
||||
updateBy: '更新人',
|
||||
tenantId: '租户ID',
|
||||
inputpostIdTip: '请输入岗位ID',
|
||||
inputpostCodeTip: '请输入岗位编码',
|
||||
inputpostNameTip: '请输入岗位名称',
|
||||
inputpostSortTip: '请输入岗位排序',
|
||||
inputremarkTip: '请输入岗位描述',
|
||||
inputdelFlagTip: '请输入是否删除 -1:已删除 0:正常',
|
||||
inputcreateTimeTip: '请输入创建时间',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateTimeTip: '请输入更新时间',
|
||||
inputupdateByTip: '请输入更新人',
|
||||
inputtenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
||||
143
src/views/admin/system/post/index.vue
Normal file
143
src/views/admin/system/post/index.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('post.postName')" prop="postName">
|
||||
<el-input :placeholder="$t('post.inputpostNameTip')" style="max-width: 180px" v-model="state.queryForm.postName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'sys_post_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="excelUploadRef.show()" class="ml10" icon="upload-filled" type="primary" v-auth="'sys_post_add'">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary" v-auth="'sys_post_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'sys_post_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
row-key="postId"
|
||||
border
|
||||
:cell-style="tableStyle?.cellStyle"
|
||||
:header-cell-style="tableStyle?.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('post.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('post.postCode')" prop="postCode" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.postName')" prop="postName" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.postSort')" prop="postSort" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.remark')" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="200">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.postId)" text type="primary" v-auth="'sys_post_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.postId])" text type="primary" v-auth="'sys_post_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
<!-- 导入excel -->
|
||||
<upload-excel
|
||||
:title="$t('post.importPostTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/post.xlsx"
|
||||
url="/admin/post/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemPost" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/admin/post';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/post/export', Object.assign(state.queryForm,{ids:selectObjs}), 'post.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { postId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ postId }) => postId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
209
src/views/admin/system/role/form.vue
Normal file
209
src/views/admin/system/role/form.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.roleId ? $t('common.editBtn') : $t('common.addBtn')" width="600" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-form-item :label="$t('sysrole.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_a_role_name')" clearable v-model="form.roleName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleCode')" prop="roleCode">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_the_role_Code')" :disabled="form.roleId !== ''" clearable v-model="form.roleCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleDesc')" prop="roleDesc">
|
||||
<el-input
|
||||
maxlength="100"
|
||||
:rows="3"
|
||||
:placeholder="$t('sysrole.please_enter_the_role_description')"
|
||||
type="textarea"
|
||||
v-model="form.roleDesc"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.menu_authority')" prop="dsType">
|
||||
<el-select :placeholder="$t('sysrole.please_select')" clearable v-model="form.dsType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in dictType" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dsType === 1">
|
||||
<el-tree
|
||||
:check-strictly="true"
|
||||
:data="dataForm.deptData"
|
||||
:default-checked-keys="dataForm.checkedDsScope"
|
||||
:props="dataForm.deptProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
ref="deptTreeRef"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemRoleDialog" setup>
|
||||
import { rule } from '/@/utils/validate';
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateRoleCode, validateRoleName } from '/@/api/admin/role';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const deptTreeRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleDesc: '',
|
||||
dsType: 0,
|
||||
dsScope: '',
|
||||
});
|
||||
|
||||
// 页面对应元数据
|
||||
const dataForm = reactive({
|
||||
deptData: [],
|
||||
checkedDsScope: [],
|
||||
deptProps: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
roleName: [
|
||||
{ required: true, message: '角色名称不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateRoleName(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleCode: [
|
||||
{ required: true, message: '角色标识不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{ validator: rule.validatorCapital, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateRoleCode(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleDesc: [{ validator: rule.overLength, trigger: 'blur' }],
|
||||
dsType: [{ required: true, message: '请选择数据权限类型', trigger: 'blur' }],
|
||||
menu_authority: [{ required: true, message: '数据权限不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: 4,
|
||||
},
|
||||
]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.roleId = '';
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value.resetFields();
|
||||
});
|
||||
|
||||
// 获取角色信息
|
||||
if (id) {
|
||||
form.roleId = id;
|
||||
getRoleData(id);
|
||||
}
|
||||
|
||||
getDeptData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
if (form.dsType === 1) {
|
||||
form.dsScope = deptTreeRef.value.getCheckedKeys().join(',');
|
||||
} else {
|
||||
form.dsScope = '';
|
||||
}
|
||||
|
||||
try {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
form.roleId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.roleId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化角色数据
|
||||
const getRoleData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
if (res.data.dsScope) {
|
||||
dataForm.checkedDsScope = res.data.dsScope.split(',');
|
||||
} else {
|
||||
dataForm.checkedDsScope = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取菜单结构数据
|
||||
const getDeptData = () => {
|
||||
deptTree().then((res: any) => {
|
||||
dataForm.deptData = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
23
src/views/admin/system/role/i18n/en.ts
Normal file
23
src/views/admin/system/role/i18n/en.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
sysrole: {
|
||||
index: '#',
|
||||
roleName: 'roleName',
|
||||
inputRoleNameTip: 'input roleName',
|
||||
permissionTip: 'Grant',
|
||||
deleteDisabledTip: 'not allowed to delete',
|
||||
mustCheckOneTip: 'the assign permissions menu must be selected',
|
||||
roleCode: 'roleCode',
|
||||
roleDesc: 'role description',
|
||||
data_authority: 'data authority',
|
||||
createTime: 'createTime',
|
||||
please_enter_a_role_name: 'please enter a role name',
|
||||
please_enter_the_role_Code: 'please enter the role Code',
|
||||
please_enter_the_role_description: 'please enter the role description',
|
||||
menu_authority: 'menu authority',
|
||||
please_select: 'please select',
|
||||
importRoleTip: 'import role',
|
||||
assignPermission: 'Assign Permission',
|
||||
update: 'Update',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
};
|
||||
23
src/views/admin/system/role/i18n/zh-cn.ts
Normal file
23
src/views/admin/system/role/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
sysrole: {
|
||||
index: '#',
|
||||
roleName: '角色名称',
|
||||
inputRoleNameTip: '请输入角色名称',
|
||||
permissionTip: '授权',
|
||||
deleteDisabledTip: '角色不允许删除',
|
||||
mustCheckOneTip: '必须选择【分配权限】菜单',
|
||||
roleCode: '角色标识',
|
||||
roleDesc: '角色描述',
|
||||
data_authority: '数据权限',
|
||||
createTime: '创建时间',
|
||||
please_enter_a_role_name: '请输入角色名称',
|
||||
please_enter_the_role_Code: '请输入角色标识',
|
||||
please_enter_the_role_description: '请输入角色描述',
|
||||
menu_authority: '数据权限',
|
||||
please_select: '请选择',
|
||||
importRoleTip: '导入角色',
|
||||
assignPermission: '分配权限',
|
||||
update: '更新',
|
||||
cancel: '取 消',
|
||||
},
|
||||
};
|
||||
197
src/views/admin/system/role/index.vue
Normal file
197
src/views/admin/system/role/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item :label="$t('sysrole.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('sysrole.inputRoleNameTip')" v-model="state.queryForm.roleName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="roleDialogRef.openDialog()" v-auth="'sys_role_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()" v-auth="'sys_user_add'">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" v-auth="'sys_user_del'" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_role_export'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
row-key="roleId"
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="center" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80" />
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 角色编辑、新增 -->
|
||||
<role-dialog ref="roleDialogRef" @refresh="getDataList()" />
|
||||
<!-- 导入角色 -->
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysrole.importRoleTip')"
|
||||
url="/admin/role/import"
|
||||
temp-url="/admin/sys-file/local/file/role.xlsx"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
<!-- 授权 -->
|
||||
<permession ref="permessionRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemRole">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { pageList, delObj } from '/@/api/admin/role';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const RoleDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const Permession = defineAsyncComponent(() => import('./permession.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const roleDialogRef = ref();
|
||||
const permessionRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
roleName: '',
|
||||
},
|
||||
pageList: pageList, // H
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: '4',
|
||||
},
|
||||
]);
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.roleId !== '1';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { roleId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
142
src/views/admin/system/role/permession.vue
Normal file
142
src/views/admin/system/role/permession.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="system-role-dialog-container">
|
||||
<el-dialog width="30%" v-model="state.dialog.isShowDialog" :close-on-click-modal="false" draggable>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>{{ state.dialog.title }}</div>
|
||||
<div class="flex mr-16">
|
||||
<el-checkbox :label="$t('common.expand')" @change="handleExpand" />
|
||||
<el-checkbox :label="$t('common.selectAll')" @change="handleSelectAll" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar class="h-[400px] sm:h-[600px]">
|
||||
<el-tree
|
||||
v-loading="loading"
|
||||
ref="menuTree"
|
||||
:data="state.treeData"
|
||||
:default-checked-keys="state.checkedKeys"
|
||||
:check-strictly="!checkStrictly"
|
||||
:props="state.defaultProps"
|
||||
class="filter-tree"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
show-checkbox
|
||||
/>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="state.dialog.isShowDialog = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit">{{ state.dialog.submitTxt }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="role-permession">
|
||||
import { fetchRoleTree, permissionUpd } from '/@/api/admin/role';
|
||||
import { pageList } from '/@/api/admin/menu';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const menuTree = ref();
|
||||
const checkStrictly = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
checkedKeys: [] as any[],
|
||||
treeData: [] as any[],
|
||||
defaultProps: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
roleId: '',
|
||||
dialog: {
|
||||
isShowDialog: false,
|
||||
title: t('sysrole.assignPermission'),
|
||||
submitTxt: t('common.editBtn'),
|
||||
},
|
||||
});
|
||||
|
||||
const checkedKeys: Ref<any[]> = ref([]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any) => {
|
||||
state.checkedKeys = [];
|
||||
state.treeData = [];
|
||||
checkedKeys.value = [];
|
||||
state.roleId = row.roleId;
|
||||
loading.value = true;
|
||||
fetchRoleTree(row.roleId)
|
||||
.then((res) => {
|
||||
checkedKeys.value = res.data;
|
||||
return pageList();
|
||||
})
|
||||
.then((r) => {
|
||||
state.treeData = r.data;
|
||||
state.checkedKeys = other.resolveAllEunuchNodeId(state.treeData, checkedKeys.value, []);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
state.dialog.isShowDialog = true;
|
||||
};
|
||||
|
||||
const handleExpand = (check: CheckboxValueType) => {
|
||||
const treeList = state.treeData;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
//@ts-ignore
|
||||
menuTree.value.store.nodesMap[treeList[i].id].expanded = check;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = (check: CheckboxValueType) => {
|
||||
if (check) {
|
||||
menuTree.value?.setCheckedKeys(state.treeData.map((item) => item.id));
|
||||
} else {
|
||||
menuTree.value?.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交授权数据
|
||||
const onSubmit = () => {
|
||||
// 初始角色选择节点必须包含 【分配权限】 菜单
|
||||
if (state.roleId === '1') {
|
||||
if (
|
||||
!menuTree.value
|
||||
.getCheckedNodes()
|
||||
.map((item: { name: string }) => {
|
||||
return item.name;
|
||||
})
|
||||
.includes('分配权限')
|
||||
) {
|
||||
useMessage().error(t('sysrole.mustCheckOneTip'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const menuIds = menuTree.value.getCheckedKeys().join(',').concat(',').concat(menuTree.value.getHalfCheckedKeys().join(','));
|
||||
loading.value = true;
|
||||
permissionUpd(state.roleId, menuIds)
|
||||
.then(() => {
|
||||
state.dialog.isShowDialog = false;
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
253
src/views/admin/system/tenant/form.vue
Normal file
253
src/views/admin/system/tenant/form.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<el-drawer :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="true" draggable size="50%">
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.name')" prop="name">
|
||||
<el-input v-model="form.name" :placeholder="t('tenant.inputnameTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.code')" prop="code">
|
||||
<el-input v-model="form.code" :placeholder="t('tenant.inputcodeTip')" :disabled="form.id !== ''" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.startTime')" prop="startTime">
|
||||
<el-date-picker class="!w-full" v-model="form.startTime" type="date" :placeholder="t('tenant.inputstartTimeTip')" :value-format="dateTimeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.endTime')" prop="endTime">
|
||||
<el-date-picker class="!w-full" v-model="form.endTime" type="date" :placeholder="t('tenant.inputendTimeTip')" :value-format="dateTimeStr" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :span="12" :label="t('tenant.tenantDomain')" prop="tenantDomain">
|
||||
<el-input v-model="form.tenantDomain" :placeholder="t('tenant.inputtenantDomainTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.status')" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="item.value" v-for="(item, index) in status_type" border :key="index">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-divider content-position="left" v-if="form.id !== '1'">
|
||||
<div>
|
||||
<span class="mr-4">{{ $t('tenantmenu.name') }}</span>
|
||||
<el-checkbox :label="$t('common.expand')" @change="handleExpand" />
|
||||
<el-checkbox :label="$t('common.selectAll')" @change="handleSelectAll" />
|
||||
</div>
|
||||
</el-divider>
|
||||
<el-scrollbar class="h-[400px] sm:h-[600px] ml-12" v-if="form.id !== '1'">
|
||||
<el-tree
|
||||
show-checkbox
|
||||
ref="menuTreeRef"
|
||||
:disabled="true"
|
||||
:check-strictly="false"
|
||||
:data="menuData"
|
||||
:props="defaultProps"
|
||||
:default-checked-keys="checkedMenu"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemTenantDialog">
|
||||
import { validateTenantCode, validateTenantName } from '/@/api/admin/tenant';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj, treemenu,fetchList } from '/@/api/admin/tenant';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
import {rule} from "/@/utils/validate";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const menuTreeRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
tenantDomain: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
status: '0',
|
||||
delFlag: '',
|
||||
createBy: '',
|
||||
updateBy: '',
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
menuId: '',
|
||||
});
|
||||
|
||||
const menuData = ref<any[]>([]);
|
||||
|
||||
const defaultProps = reactive({
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
const checkedMenu = ref<any[]>([]);
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantName(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
code: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '编码不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantCode(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: 'status不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string): void => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
form.menuId = '';
|
||||
checkedMenu.value = [];
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantData(id);
|
||||
}
|
||||
|
||||
getMenuData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (menuTreeRef.value?.getCheckedKeys().length === 0) {
|
||||
useMessage().error('请选择租户套餐菜单');
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (menuTreeRef.value?.getCheckedKeys()) {
|
||||
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()]
|
||||
|
||||
if (!checkMenu.includes('1300')) {
|
||||
useMessage().error('必须分配角色管理功能');
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkMenu.includes('1302')) {
|
||||
useMessage().error('必须分配角色管理功能');
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
form.menuId = checkMenu.join(',');
|
||||
}
|
||||
|
||||
form.id ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
await fetchList()
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化表格数据。
|
||||
* @param {string} id - 部门 ID。
|
||||
*/
|
||||
const getTenantData = async (id) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取菜单数据
|
||||
*/
|
||||
const getMenuData = async () => {
|
||||
const res = await treemenu();
|
||||
menuData.value = res.data;
|
||||
checkedMenu.value = form.menuId ? other.resolveAllEunuchNodeId(menuData.value, form.menuId.split(','), []) : [];
|
||||
};
|
||||
|
||||
const handleExpand = (check: CheckboxValueType) => {
|
||||
const treeList = menuData.value;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
//@ts-ignore
|
||||
menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = check;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = (check: CheckboxValueType) => {
|
||||
if (check) {
|
||||
menuTreeRef.value?.setCheckedKeys(menuData.value.map((item) => item.id));
|
||||
} else {
|
||||
menuTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
40
src/views/admin/system/tenant/i18n/en.ts
Normal file
40
src/views/admin/system/tenant/i18n/en.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export default {
|
||||
tenant: {
|
||||
index: '#',
|
||||
importTenantTip: ' import Tenant',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
code: 'code',
|
||||
tenantDomain: 'tenantDomain',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
status: 'status',
|
||||
delFlag: 'delFlag',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
menuId: 'menuIds',
|
||||
individuationBtn: 'individuation',
|
||||
inputidTip: 'input id',
|
||||
inputnameTip: 'input name',
|
||||
inputcodeTip: 'input code',
|
||||
inputtenantDomainTip: 'input tenantDomain',
|
||||
inputstartTimeTip: 'input startTime',
|
||||
inputendTimeTip: 'input endTime',
|
||||
inputstatusTip: 'input status',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputmenuIdTip: 'input menuId',
|
||||
deleteDisabledTip: 'base tenants are not allowed to delete',
|
||||
},
|
||||
tenantmenu: {
|
||||
name: 'tenantmenu',
|
||||
index: '#',
|
||||
status: 'status',
|
||||
createTime: 'createTime',
|
||||
},
|
||||
};
|
||||
53
src/views/admin/system/tenant/i18n/zh-cn.ts
Normal file
53
src/views/admin/system/tenant/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
export default {
|
||||
tenant: {
|
||||
index: '#',
|
||||
importTenantTip: '导入租户',
|
||||
id: '租户id',
|
||||
name: '租户名称',
|
||||
code: '编码',
|
||||
tenantDomain: '域名',
|
||||
startTime: '开始时间',
|
||||
endTime: '结束时间',
|
||||
status: '状态',
|
||||
delFlag: 'delFlag',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建',
|
||||
updateTime: '更新时间',
|
||||
menuId: '租户套餐',
|
||||
individuationBtn: '个性化',
|
||||
inputidTip: '请输入租户id',
|
||||
inputnameTip: '请输入名称',
|
||||
inputcodeTip: '请输入编码',
|
||||
inputtenantDomainTip: '请输入域名',
|
||||
inputstartTimeTip: '请输入开始时间',
|
||||
inputendTimeTip: '请输入结束时间',
|
||||
inputstatusTip: '请输入status',
|
||||
inputdelFlagTip: '请输入delFlag',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateByTip: '请输入修改人',
|
||||
inputcreateTimeTip: '请输入创建',
|
||||
inputupdateTimeTip: '请输入更新时间',
|
||||
inputmenuIdTip: '请选择租户套餐',
|
||||
deleteDisabledTip: '基础租户不允许删除',
|
||||
},
|
||||
tenantmenu: {
|
||||
name: '套餐',
|
||||
index: '#',
|
||||
status: '状态',
|
||||
createTime: '创建',
|
||||
},
|
||||
|
||||
individuation: {
|
||||
websiteName: '网站名称',
|
||||
miniQr: '移动端二维码',
|
||||
logo: '网站图标',
|
||||
footerAuthor: '页脚信息',
|
||||
background: '登录页背景图',
|
||||
inputIndividuationNameTip: '请输入网站名称',
|
||||
inputMiniQrTip: '请输入网站图标',
|
||||
inputLogoTip: '请输入网站Logo',
|
||||
inputFooterAuthorTip: '请输入页脚信息',
|
||||
inputBackgroundTip: '请输入登录页背景图',
|
||||
}
|
||||
};
|
||||
240
src/views/admin/system/tenant/index.vue
Normal file
240
src/views/admin/system/tenant/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('tenant.name')" prop="name">
|
||||
<el-input :placeholder="$t('tenant.inputnameTip')" style="max-width: 180px" v-model="state.queryForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'sys_systenant_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_systenant_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-scrollbar>
|
||||
<div class="mx-auto mt-4">
|
||||
<div class="px-4">
|
||||
<div class="grid sm:grid-cols-2 sm:gap-x-6 lg:grid-cols-3">
|
||||
<div class="p-6 mb-6 bg-gray-100 rounded-lg dark:bg-gray-800" v-for="tenant in state.dataList" :key="tenant.id">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ tenant.name }}</h3>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<svg
|
||||
t="1710908184286"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5178"
|
||||
class="w-5 h-5 mr-2 text-base text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<path
|
||||
d="M512 1003.52a497.92 497.92 0 1 1 497.92-497.92A498.56 498.56 0 0 1 512 1003.52zM512 71.68a433.92 433.92 0 1 0 433.92 433.92A434.56 434.56 0 0 0 512 71.68z"
|
||||
fill="#323333"
|
||||
p-id="5179"
|
||||
></path>
|
||||
<path
|
||||
d="M152.96 369.92a33.92 33.92 0 0 1 35.2-36.48 39.04 39.04 0 0 1 29.44 16l148.48 198.4V369.92a35.2 35.2 0 1 1 69.76 0V640a30.72 30.72 0 0 1-34.56 33.28 36.48 36.48 0 0 1-29.44-12.8l-147.2-198.4V640a30.72 30.72 0 0 1-34.56 33.28 31.36 31.36 0 0 1-37.12-33.28zM463.36 504.32a162.56 162.56 0 1 1 323.84 0 158.08 158.08 0 0 1-161.92 168.96 159.36 159.36 0 0 1-161.92-168.96z m252.16 0c-3.84-69.12-33.92-104.96-90.24-108.8s-84.48 39.68-88.32 108.8 33.28 104.32 88.32 108.16 86.4-36.48 90.24-108.16zM856.96 603.52A37.12 37.12 0 0 1 896 640c0 21.12-13.44 32-36.48 33.28s-35.84-12.16-37.12-33.28a37.76 37.76 0 0 1 34.56-36.48z"
|
||||
fill="#323333"
|
||||
p-id="5180"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="mr-1 dark:text-gray-300">{{ tenant.code }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="my-3 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
状态: {{ status_type.find((item: { value: string }) => item.value === tenant.status)?.label }}
|
||||
</p>
|
||||
<p class="my-3 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
有效期: {{ parseDate(tenant.startTime) }} - {{ parseDate(tenant.endTime) }}
|
||||
</p>
|
||||
<div class="flex items-center justify-between mt-6 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
<div class="flex">
|
||||
<svg
|
||||
t="1710908265535"
|
||||
class="w-6 h-5 mr-1 text-yellow-500"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="6175"
|
||||
width="128"
|
||||
height="128"
|
||||
>
|
||||
<path
|
||||
d="M512.001274 15.045039a497.880869 497.880869 0 0 1 496.99699 497.086142c0 137.285182-55.886849 261.573274-145.67566 351.652466-89.81301 90.098296-214.335444 145.167493-351.32133 145.167494-137.262257 0-261.529972-55.069197-351.31751-145.173862-89.788811-90.079192-145.67566-214.367284-145.67566-351.646098a497.880869 497.880869 0 0 1 496.99317-497.086142zM489.215293 932.144136V771.518957a405.118201 405.118201 0 0 0-129.123952 26.595319 137.138718 137.138718 0 0 0-17.101903 6.511917c3.53042 7.062113 6.785742 14.098754 10.570881 20.887043 25.514032 45.602527 55.33538 80.88762 88.987717 101.223193a372.744559 372.744559 0 0 0 46.671078 5.40898z m320.120674-717.103117a128.736778 128.736778 0 0 0-10.591259-10.061441 441.222218 441.222218 0 0 1-58.315604 36.934404c27.141693 70.795612 43.939205 156.01602 46.672351 247.447775h144.843999a419.629601 419.629601 0 0 0-122.609487-274.320738z m-46.672352-40.41388a415.300634 415.300634 0 0 0-85.71329-49.415687 434.384259 434.384259 0 0 1 33.623044 51.562976c3.805517 7.062113 7.861934 15.201692 11.942549 23.339997 13.571483-8.138305 27.117495-16.556802 40.137509-25.493654z m-181.233302-77.3572c-15.172399-2.404557-30.921738-4.056417-46.64688-5.133883V253.306336a429.022402 429.022402 0 0 0 129.127773-26.588951c5.985921-2.177856 11.392353-4.356986 17.076431-6.787015-2.954752-7.337211-7.036641-14.373851-10.541588-20.887043-24.989309-46.15527-55.610477-80.888893-89.013189-101.774662z m-92.217567-5.133883c-15.723868 1.077466-31.473207 2.729325-46.671078 5.133883-33.652337 20.887043-63.473685 55.619393-88.987717 101.774662-3.781319 6.510644-7.036641 13.547285-10.816686 20.887042 5.684078 2.430029 11.392353 4.609159 17.351529 6.787016a428.859382 428.859382 0 0 0 129.123952 26.595318V92.130962z m-142.141419 33.075396a414.845959 414.845959 0 0 0-85.732395 49.415687c13.020014 8.940673 26.591498 17.355349 40.412607 25.493654a233.26369 233.26369 0 0 1 11.667451-23.339997c10.041064-19.007209 21.708515-35.811089 33.652337-51.562976z m-122.088585 79.763031c-3.255322 3.004423-7.060839 6.536116-10.316162 10.061441a419.628328 419.628328 0 0 0-122.614581 274.32456h144.874565c2.703853-91.430482 19.529385-176.648342 46.370508-247.447775a478.47757 478.47757 0 0 1-58.31433-36.934405zM92.051999 535.189712a420.779662 420.779662 0 0 0 122.614581 274.319464l10.316162 10.293237a425.318773 425.318773 0 0 1 58.318151-37.413278c-26.841123-71.096182-43.666655-155.742195-46.370508-247.19815H92.051999z m169.286933 314.488813a415.88267 415.88267 0 0 0 85.732395 49.383847 360.53328 360.53328 0 0 1-33.652337-51.562976c-4.075521-7.313012-8.137031-15.176219-11.667451-22.788528a416.635367 416.635367 0 0 0-40.412607 24.962563z m273.441954 82.465611a371.780443 371.780443 0 0 0 46.646879-5.40898c33.402711-20.335574 64.02388-55.620666 89.013189-101.223194 3.504948-6.787015 7.586836-13.823656 10.541589-20.887042a133.885943 133.885943 0 0 0-17.076431-6.511918 405.259571 405.259571 0 0 0-129.120132-26.594045v160.625179z m142.169439-33.081764a416.339892 416.339892 0 0 0 85.71329-49.383847 390.678122 390.678122 0 0 0-40.137509-24.962563c-4.075521 7.612308-8.137031 15.475516-11.942548 22.788528-10.592533 18.706639-21.684316 36.362558-33.623045 51.562976z m121.789288-79.259959l10.591259-10.293237a420.780936 420.780936 0 0 0 122.614582-274.319464H787.101455c-2.729325 91.455954-19.530658 176.101968-46.672351 247.198149a395.620965 395.620965 0 0 1 58.315604 37.414552z m-99.82478-558.105599a203.624467 203.624467 0 0 1-19.004662 7.33721 460.617875 460.617875 0 0 1-145.124191 29.826442v190.500018h206.195866c-2.452954-84.393841-17.375727-162.553409-42.064466-227.66367z m-209.69954 37.163652a456.062207 456.062207 0 0 1-144.873292-29.826442 187.929892 187.929892 0 0 1-19.530658-7.33721c-24.412368 65.110261-39.608965 143.269829-42.064466 227.66367H489.215293V298.860466z m0 426.554165v-190.224919H282.746877c2.454228 84.144215 17.652098 161.977742 42.064466 227.388572 6.510644-2.428756 13.020014-5.158081 19.530658-7.33721 45.019218-17.354076 93.864332-27.396413 144.873292-29.826443z m45.570687 0a465.365857 465.365857 0 0 1 144.874566 29.826443c6.50937 2.17913 12.743643 4.908455 19.254287 7.33721 24.688739-65.410831 39.608965-143.244357 42.064466-227.388572H534.78598v190.224919z"
|
||||
fill="#3B3F51"
|
||||
p-id="6176"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="dark:text-gray-300">{{ tenant.tenantDomain }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="HomeFilled"
|
||||
@click="individuationRef.openDialog(tenant.id)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_edit'"
|
||||
>{{ $t('tenant.individuationBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="edit-pen"
|
||||
@click="formDialogRef.openDialog(tenant.id)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="delete"
|
||||
:disabled="tenant.id === '1'"
|
||||
@click="handleDelete([tenant.id])"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<individuation ref="individuationRef" />
|
||||
|
||||
<!-- 导入excel -->
|
||||
<upload-excel
|
||||
:title="$t('tenant.importTenantTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/tenant.xlsx"
|
||||
url="/admin/tenant/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemTenant" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchPage, fetchList } from '/@/api/admin/tenant';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const Individuation = defineAsyncComponent(() => import('./individuation.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const individuationRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const tenantMenuRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchPage,
|
||||
pagination: {
|
||||
size: 6,
|
||||
pageSizes: [3, 6, 9, 12],
|
||||
},
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/tenant/export', Object.assign(state.queryForm, { ids: selectObjs }), 'tenant.xlsx');
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
handleRefreshCache();
|
||||
}
|
||||
};
|
||||
|
||||
//刷新缓存
|
||||
const handleRefreshCache = () => {
|
||||
fetchList().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
137
src/views/admin/system/tenant/individuation.vue
Normal file
137
src/views/admin/system/tenant/individuation.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-drawer :title="$t('tenant.individuationBtn')" v-model="visible" :close-on-click-modal="false">
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" v-loading="loading">
|
||||
<el-row>
|
||||
<el-col :span="24" class="mt-4">
|
||||
<el-form-item :label="t('individuation.websiteName')" prop="websiteName" label-width="120px" align="left">
|
||||
<el-input v-model="form.websiteName" :placeholder="t('individuation.inputIndividuationNameTip')"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mt-4">
|
||||
<el-form-item prop="footerAuthor" label-width="120px" align="left">
|
||||
<template #label>
|
||||
{{ t('individuation.footerAuthor') }}
|
||||
<tip content="浏览器底部版权信息、备案信息"/>
|
||||
</template>
|
||||
<el-input v-model="form.footer" :placeholder="t('individuation.inputFooterAuthorTip')"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mt-4">
|
||||
<el-form-item prop="icon" label-width="120px" align="left">
|
||||
<template #label>
|
||||
{{ t('individuation.miniQr') }}
|
||||
<tip content="登录页右下角显示的移动端二维码"/>
|
||||
</template>
|
||||
<upload-img v-model:image-url="form.miniQr"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mt-4">
|
||||
<el-form-item :label="t('individuation.background')" prop="background" label-width="120px" align="left">
|
||||
<upload-img v-model:image-url="form.background"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemTenantDialog">
|
||||
import {useDict} from '/@/hooks/dict';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import {getObj, putObj} from '/@/api/admin/tenant';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import UploadImg from "/@/components/Upload/Image.vue";
|
||||
import {useThemeConfig} from "/@/stores/themeConfig";
|
||||
import pinia from "/@/stores";
|
||||
import {storeToRefs} from "pinia";
|
||||
import Tip from "/@/components/Tip/index.vue";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const {t} = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 字典
|
||||
const {status_type} = useDict('status_type');
|
||||
|
||||
// 导入配置文件
|
||||
const stores = useThemeConfig(pinia);
|
||||
const {themeConfig} = storeToRefs(stores);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
websiteName: themeConfig.value.globalTitle,
|
||||
background: '',
|
||||
miniQr: '',
|
||||
footer: themeConfig.value.footerAuthor,
|
||||
});
|
||||
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string): void => {
|
||||
visible.value = true;
|
||||
form.id = ''
|
||||
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantData(id);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化表格数据。
|
||||
* @param {string} id - 部门 ID。
|
||||
*/
|
||||
const getTenantData = async (id: any) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data);
|
||||
};
|
||||
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {
|
||||
});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
await putObj(form);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
305
src/views/admin/system/user/form.vue
Normal file
305
src/views/admin/system/user/form.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div class="system-user-dialog-container">
|
||||
<el-dialog :close-on-click-modal="false" :title="dataForm.userId ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="dataForm" :rules="dataRules" label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.username')" prop="username">
|
||||
<el-input :disabled="dataForm.userId !== ''" :placeholder="$t('sysuser.inputUsernameTip')" v-model="dataForm.username"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.password')" prop="password">
|
||||
<el-input clearable :placeholder="$t('sysuser.inputPasswordTip')" type="password" v-model="dataForm.password"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.name')" prop="name">
|
||||
<el-input clearable :placeholder="$t('sysuser.inputNameTip')" v-model="dataForm.name"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.phone')" prop="phone">
|
||||
<el-input clearable :placeholder="$t('sysuser.inputPhoneTip')" v-model="dataForm.phone"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.role')" prop="role">
|
||||
<el-select clearable multiple :placeholder="$t('sysuser.selectRole')" v-model="dataForm.role">
|
||||
<el-option :key="item.roleId" :label="item.roleName" :value="item.roleId" v-for="item in roleData" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.post')" prop="post">
|
||||
<el-select clearable multiple :placeholder="$t('sysuser.selectPost')" v-model="dataForm.post">
|
||||
<el-option :key="item.postId" :label="item.postName" :value="item.postId" v-for="item in postData" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.dept')" prop="deptId">
|
||||
<el-tree-select
|
||||
:data="deptData"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
check-strictly
|
||||
class="w100"
|
||||
clearable
|
||||
:placeholder="$t('sysuser.selectDept')"
|
||||
v-model="dataForm.deptId"
|
||||
>
|
||||
</el-tree-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.email')" prop="email">
|
||||
<el-input clearable :placeholder="$t('sysuser.inputEmailTip')" v-model="dataForm.email"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.nickname')" prop="nickname">
|
||||
<el-input clearable :placeholder="$t('sysuser.inputNicknameTip')" v-model="dataForm.nickname"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.lockFlag')" prop="lockFlag">
|
||||
<el-radio-group v-model="dataForm.lockFlag">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in lock_flag">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
|
||||
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUserDialog" setup>
|
||||
import { addObj, getObj, putObj, validatePhone, validateUsername } from '/@/api/admin/user';
|
||||
import { list as roleList } from '/@/api/admin/role';
|
||||
import { list as postList } from '/@/api/admin/post';
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义刷新表格emit
|
||||
const emit = defineEmits(['refresh']);
|
||||
// @ts-ignore
|
||||
const { lock_flag } = useDict('lock_flag');
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const deptData = ref<any[]>([]);
|
||||
const roleData = ref<any[]>([]);
|
||||
const postData = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const props = defineProps({
|
||||
deptId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const dataForm = reactive({
|
||||
userId: '',
|
||||
username: '',
|
||||
password: '' as String | undefined,
|
||||
salt: '',
|
||||
wxOpenid: '',
|
||||
qqOpenid: '',
|
||||
lockFlag: '0',
|
||||
phone: '' as String | undefined,
|
||||
deptId: '',
|
||||
roleList: [],
|
||||
postList: [],
|
||||
nickname: '',
|
||||
name: '',
|
||||
email: '',
|
||||
post: [] as string[],
|
||||
role: [] as string[],
|
||||
});
|
||||
|
||||
const dataRules = ref({
|
||||
// 用户名校验,不能为空 、长度 5-20、不能和已有数据重复
|
||||
username: [
|
||||
{ required: true, message: t('sysuser.usernameRequired'), trigger: 'blur' },
|
||||
{ min: 5, max: 20, message: t('sysuser.usernameLength'), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateUsername(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('sysuser.passwordRequired'), trigger: 'blur' },
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('sysuser.passwordLength'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
// 姓名校验,不能为空、只能是中文
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: t('sysuser.nameRequired'), trigger: 'blur' },
|
||||
{ validator: rule.chinese, trigger: 'blur' },
|
||||
],
|
||||
deptId: [{ required: true, message: t('sysuser.deptRequired'), trigger: 'blur' }],
|
||||
role: [{ required: true, message: t('sysuser.roleRequired'), trigger: 'blur' }],
|
||||
post: [{ required: true, message: t('sysuser.postRequired'), trigger: 'blur' }],
|
||||
// 手机号校验,不能为空、新增的时不能重复校验
|
||||
phone: [
|
||||
{ required: true, message: t('sysuser.phoneRequired'), trigger: 'blur' },
|
||||
{ validator: rule.validatePhone, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePhone(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: t('sysuser.emailFormat'), trigger: ['blur', 'change'] },
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
],
|
||||
lockFlag: [{ required: true, message: t('sysuser.statusRequired'), trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.userId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 修改获取用户信息
|
||||
if (id) {
|
||||
dataForm.userId = id;
|
||||
await getUserData(id);
|
||||
dataForm.password = '******';
|
||||
}
|
||||
|
||||
// 加载使用的数据
|
||||
getDeptData();
|
||||
getPostData();
|
||||
getRoleData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
// 立即设置 loading,防止重复点击
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) {
|
||||
loading.value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const { userId, phone, password } = dataForm;
|
||||
|
||||
if (userId) {
|
||||
// 清除占位符,避免提交错误的数据
|
||||
if (phone?.includes('*')) dataForm.phone = undefined;
|
||||
if (password?.includes('******')) dataForm.password = undefined;
|
||||
|
||||
await putObj(dataForm);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} else {
|
||||
await addObj(dataForm);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
}
|
||||
} catch (error: any) {
|
||||
useMessage().error(error.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从服务器获取用户数据
|
||||
*
|
||||
* @async
|
||||
* @param {string} id - 用户 ID
|
||||
* @return {Promise} - 包含用户数据的 Promise 对象
|
||||
*/
|
||||
const getUserData = async (id: string) => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await getObj(id);
|
||||
Object.assign(dataForm, data);
|
||||
if (data.roleList) {
|
||||
dataForm.role = data.roleList.map((item: { roleId: string }) => item.roleId);
|
||||
}
|
||||
if (data.postList) {
|
||||
dataForm.post = data.postList.map((item: { postId: string }) => item.postId);
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化部门数据
|
||||
const getDeptData = () => {
|
||||
// 获取部门数据
|
||||
deptTree().then((res) => {
|
||||
deptData.value = res.data;
|
||||
// 默认选择在树中选中的部门
|
||||
if (!dataForm.userId) {
|
||||
dataForm.deptId = props.deptId;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 岗位数据
|
||||
const getPostData = () => {
|
||||
postList().then((res) => {
|
||||
postData.value = res.data;
|
||||
// 默认选择第一个
|
||||
if (!dataForm.userId) {
|
||||
dataForm.post = [res.data[0].postId];
|
||||
}
|
||||
});
|
||||
};
|
||||
// 角色数据
|
||||
const getRoleData = () => {
|
||||
roleList().then((res) => {
|
||||
roleData.value = res.data;
|
||||
// 默认选择第一个
|
||||
if (!dataForm.userId) {
|
||||
dataForm.role = [res.data[0].roleId];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
45
src/views/admin/system/user/i18n/en.ts
Normal file
45
src/views/admin/system/user/i18n/en.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
sysuser: {
|
||||
index: '#',
|
||||
username: 'username',
|
||||
name: 'name',
|
||||
phone: 'phone',
|
||||
post: 'post',
|
||||
role: 'role',
|
||||
lockFlag: 'lockFlag',
|
||||
createTime: 'createTime',
|
||||
password: 'password',
|
||||
dept: 'dept',
|
||||
email: 'email',
|
||||
nickname: 'nickname',
|
||||
inputUsernameTip: 'input username',
|
||||
inputPhoneTip: 'input phone',
|
||||
inputPasswordTip: 'input password',
|
||||
inputNameTip: 'input name',
|
||||
inputEmailTip: 'input email',
|
||||
inputNicknameTip: 'input nickname',
|
||||
importUserTip: 'user import',
|
||||
deleteDisabledTip: 'admin are not allowed to delete',
|
||||
noDataScopeTip: 'no data permissions',
|
||||
passwordBtn: 'password',
|
||||
passwordLength: 'Password length must be between 5 and 20 characters',
|
||||
nameRequired: 'Name cannot be empty',
|
||||
deptRequired: 'Department cannot be empty',
|
||||
roleRequired: 'Role cannot be empty',
|
||||
postRequired: 'Post cannot be empty',
|
||||
phoneRequired: 'Phone cannot be empty',
|
||||
emailFormat: 'Please enter a valid email address',
|
||||
statusRequired: 'Status cannot be empty',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
usernameLength: 'Username length must be between 5 and 20 characters',
|
||||
passwordRequired: 'Password cannot be empty',
|
||||
selectDept: 'Please select a department',
|
||||
selectRole: 'Please select roles',
|
||||
selectPost: 'Please select posts',
|
||||
},
|
||||
personal: {
|
||||
name: 'personal info',
|
||||
passwordRule: 'The two passwords are inconsistent',
|
||||
passwordScore: 'Password level is too low',
|
||||
},
|
||||
};
|
||||
45
src/views/admin/system/user/i18n/zh-cn.ts
Normal file
45
src/views/admin/system/user/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
sysuser: {
|
||||
index: '#',
|
||||
username: '用户名',
|
||||
name: '姓名',
|
||||
phone: '手机号',
|
||||
post: '岗位',
|
||||
role: '角色',
|
||||
lockFlag: '启用',
|
||||
createTime: '创建时间',
|
||||
password: '密码',
|
||||
dept: '部门',
|
||||
email: '邮箱',
|
||||
nickname: '昵称',
|
||||
inputUsernameTip: '请输入用户名',
|
||||
inputPhoneTip: '请输入手机号',
|
||||
inputNameTip: '请输入姓名',
|
||||
inputPasswordTip: '请输入密码',
|
||||
inputEmailTip: '请输入邮箱',
|
||||
inputNicknameTip: '请输入昵称',
|
||||
importUserTip: '用户导入',
|
||||
deleteDisabledTip: 'admin 不允许被删除',
|
||||
noDataScopeTip: '没有数据权限',
|
||||
passwordBtn: '密码',
|
||||
passwordLength: '用户密码长度必须介于 5 和 20 之间',
|
||||
nameRequired: '姓名不能为空',
|
||||
deptRequired: '部门不能为空',
|
||||
roleRequired: '角色不能为空',
|
||||
postRequired: '岗位不能为空',
|
||||
phoneRequired: '手机号不能为空',
|
||||
emailFormat: '请输入正确的邮箱地址',
|
||||
statusRequired: '状态不能为空',
|
||||
usernameRequired: '用户名不能为空',
|
||||
usernameLength: '用户名称长度必须介于 5 和 20 之间',
|
||||
passwordRequired: '密码不能为空',
|
||||
selectDept: '请选择所属部门',
|
||||
selectRole: '请选择角色',
|
||||
selectPost: '请选择岗位',
|
||||
},
|
||||
personal: {
|
||||
name: '个人信息',
|
||||
passwordRule: '两次输入密码不一致',
|
||||
passwordScore: '密码等级太低',
|
||||
},
|
||||
};
|
||||
263
src/views/admin/system/user/index.vue
Normal file
263
src/views/admin/system/user/index.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<splitpanes>
|
||||
<pane size="15">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-scrollbar>
|
||||
<query-tree :placeholder="$t('common.queryDeptTip')" :query="deptData.queryList" :show-expand="true" @node-click="handleNodeClick">
|
||||
<!-- 没有数据权限提示 -->
|
||||
<template #default="{ node, data }">
|
||||
<el-tooltip v-if="data.isLock" class="item" effect="dark" :content="$t('sysuser.noDataScopeTip')" placement="right-start">
|
||||
<span
|
||||
>{{ node.label }}
|
||||
<SvgIcon name="ele-Lock" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-if="!data.isLock">{{ node.label }}</span>
|
||||
</template>
|
||||
</query-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</pane>
|
||||
<pane class="ml8">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
|
||||
<el-form-item :label="$t('sysuser.username')" prop="username">
|
||||
<el-input v-model="state.queryForm.username" :placeholder="$t('sysuser.inputUsernameTip')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysuser.phone')" prop="phone">
|
||||
<el-input v-model="state.queryForm.phone" :placeholder="$t('sysuser.inputPhoneTip')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="Search" type="primary" @click="getDataList">{{ $t('common.queryBtn') }}</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button v-auth="'sys_user_add'" icon="folder-add" type="primary" @click="userDialogRef.openDialog()">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain v-auth="'sys_user_add'" class="ml10" icon="upload-filled" type="primary" @click="excelUploadRef.show()">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
v-auth="'sys_user_del'"
|
||||
:disabled="multiple"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_user_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
v-loading="state.loading"
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
row-key="userId"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column :selectable="handleSelectable" type="selection" width="40" />
|
||||
<el-table-column :label="$t('sysuser.index')" type="index" width="60" fixed="left" />
|
||||
<el-table-column :label="$t('sysuser.username')" prop="username" fixed="left" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.name')" prop="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysuser.role')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-for="(item, index) in scope.row.roleList" :key="index">{{ item.roleName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysuser.lockFlag')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.lockFlag" @change="changeSwitch(scope.row)" active-value="0" inactive-value="9"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<div style="display: flex">
|
||||
<!-- 重置密码 -->
|
||||
<popover-input v-model="inputPassword" @confirm="changePassword(scope.row)">
|
||||
<template #default>
|
||||
<el-button v-auth="'sys_user_edit'" icon="RefreshLeft" text type="primary" class="mr-4">
|
||||
{{ $t('sysuser.passwordBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</popover-input>
|
||||
<!-- 修改信息 -->
|
||||
<el-button v-auth="'sys_user_edit'" icon="edit-pen" text type="primary" @click="userDialogRef.openDialog(scope.row.userId)">
|
||||
{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<!-- 删除用户 -->
|
||||
<el-tooltip :content="$t('sysuser.deleteDisabledTip')" :disabled="scope.row.userId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
v-auth="'sys_user_del'"
|
||||
:disabled="scope.row.username === 'admin'"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleDelete([scope.row.userId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle"> </pagination>
|
||||
</div>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
|
||||
<user-form ref="userDialogRef" @refresh="getDataList(false)" :deptId="state.queryForm.deptId" />
|
||||
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysuser.importUserTip')"
|
||||
temp-url="/admin/sys-file/local/file/user.xlsx"
|
||||
url="/admin/user/import"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUser" setup>
|
||||
import { delObj, pageList, putObj } from '/@/api/admin/user';
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 动态引入组件
|
||||
const UserForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
|
||||
const PopoverInput = defineAsyncComponent(() => import('/@/components/PopoverInput/index.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const userDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const inputPassword = ref();
|
||||
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
// 定义表格查询、后台调用的API
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
deptId: '',
|
||||
username: '',
|
||||
phone: '',
|
||||
},
|
||||
pageList: pageList,
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 部门树使用的数据
|
||||
const deptData = reactive({
|
||||
queryList: (name: String) => {
|
||||
return deptTree({
|
||||
deptName: name,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value?.resetFields();
|
||||
state.queryForm.deptId = '';
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = (e: any) => {
|
||||
state.queryForm.deptId = e.id;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/user/export', Object.assign(state.queryForm, { ids: selectObjs }), 'users.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.username !== 'admin';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { userId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ userId }) => userId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
//表格内开关 (用户状态)
|
||||
const changeSwitch = async (row: any) => {
|
||||
// 不修改密码
|
||||
row.password = undefined;
|
||||
row.phone = undefined;
|
||||
await putObj(row);
|
||||
useMessage().success(t('common.optSuccessText'));
|
||||
getDataList();
|
||||
};
|
||||
|
||||
//修改用户密码
|
||||
const changePassword = async (row: any) => {
|
||||
if (!inputPassword.value || inputPassword.value.length < 6 || inputPassword.value.length > 20) {
|
||||
useMessage().error(t('sysuser.inputPasswordTip'));
|
||||
return;
|
||||
}
|
||||
|
||||
row.phone = undefined;
|
||||
row.password = inputPassword.value;
|
||||
await putObj(row);
|
||||
useMessage().success(t('common.optSuccessText'));
|
||||
getDataList();
|
||||
};
|
||||
</script>
|
||||
565
src/views/admin/system/user/personal.vue
Normal file
565
src/views/admin/system/user/personal.vue
Normal file
@@ -0,0 +1,565 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" :title="$t('personal.name')" size="40%">
|
||||
<el-tabs style="height: 200px" class="demo-tabs">
|
||||
<el-tab-pane label="基本信息" v-loading="loading">
|
||||
<template #label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
基本信息
|
||||
</template>
|
||||
<el-form :model="formData" :rules="ruleForm" label-width="100px" class="mt30" ref="formdataRef">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item prop="avatar">
|
||||
<ImageUpload v-model:imageUrl="formData.avatar" borderRadius="50%">
|
||||
<template #empty>
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<span>请上传头像</span>
|
||||
</template>
|
||||
</ImageUpload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="formData.username" clearable disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="手机" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入手机" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="formData.nickname" placeholder="请输入昵称" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入姓名" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSaveUser"> 更新个人信息 </el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="安全信息">
|
||||
<template #label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
|
||||
</svg>
|
||||
安全信息
|
||||
</template>
|
||||
<el-form :model="passwordFormData" :rules="passwordRuleForm" label-width="100px" class="mt30" ref="passwordFormdataRef">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="原密码" prop="password">
|
||||
<el-input v-model="passwordFormData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable type="password">
|
||||
<template #suffix>
|
||||
<i
|
||||
class="iconfont el-input__icon login-content-password"
|
||||
:class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
</i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="新密码" prop="newpassword1">
|
||||
<strength-meter
|
||||
v-model="passwordFormData.newpassword1"
|
||||
:minlength="6"
|
||||
:maxlength="16"
|
||||
placeholder="请输入新密码"
|
||||
@score="passwordScore"
|
||||
></strength-meter>
|
||||
<!-- <el-input v-model="passwordFormData.newpassword1" clearable type="password"></el-input>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="确认密码" prop="newpassword2">
|
||||
<strength-meter v-model="passwordFormData.newpassword2" :minlength="6" :maxlength="16" placeholder="请重复密码"></strength-meter>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleChangePassword"> 修改密码 </el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="第三方账号">
|
||||
<template #label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" />
|
||||
</svg>
|
||||
|
||||
社交登录
|
||||
</template>
|
||||
<el-table :data="socialList" class="mt10">
|
||||
<el-table-column type="index" label="序号" width="80"></el-table-column>
|
||||
<el-table-column prop="name" label="平台"></el-table-column>
|
||||
<el-table-column label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.openId"> 已绑定 </el-tag>
|
||||
<el-tag v-else> 未绑定 </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="action" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button @click="unbinding(scope.row.type)" text type="primary" v-if="scope.row.openId"> 解绑 </el-button>
|
||||
<el-button @click="handleClick(scope.row.type)" text type="primary" v-else> 绑定 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="personal">
|
||||
import {useUserInfo} from '/@/stores/userInfo';
|
||||
import {editInfo, getObj, password, unbindingUser} from '/@/api/admin/user';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import {rule, validateNull} from '/@/utils/validate';
|
||||
import other from '/@/utils/other';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {getLoginAppList} from "/@/api/admin/social";
|
||||
import { SocialLoginEnum } from '/@/api/login';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
|
||||
const StrengthMeter = defineAsyncComponent(() => import('/@/components/StrengthMeter/index.vue'));
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
// 定义变量内容
|
||||
const formData = ref({
|
||||
userId: '',
|
||||
username: '',
|
||||
name: '',
|
||||
email: '',
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
wxDingUserid: '',
|
||||
wxCpUserid: '',
|
||||
phone: ('' as string) || undefined,
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const passwordFormData = reactive({
|
||||
password: '',
|
||||
newpassword1: '',
|
||||
newpassword2: '',
|
||||
});
|
||||
|
||||
const formdataRef = ref();
|
||||
const passwordFormdataRef = ref();
|
||||
|
||||
const ruleForm = reactive({
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatePhone, trigger: 'blur' },
|
||||
],
|
||||
nickname: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '昵称不能为空', trigger: 'blur' }],
|
||||
email: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
|
||||
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '姓名不能为空', trigger: 'blur' }],
|
||||
});
|
||||
const validatorPassword2 = (rule: any, value: any, callback: any) => {
|
||||
if (value !== passwordFormData.newpassword1) {
|
||||
callback(new Error(t('personal.passwordRule')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validatorScore = (rule: any, value: any, callback: any) => {
|
||||
if (score.value <= 1) {
|
||||
callback(new Error(t('personal.passwordScore')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const passwordRuleForm = reactive({
|
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
newpassword1: [
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 6 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{ validator: validatorScore, trigger: 'blur' },
|
||||
],
|
||||
newpassword2: [
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 6 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{ validator: validatorPassword2, trigger: 'blur' },
|
||||
],
|
||||
});
|
||||
|
||||
const score = ref(0);
|
||||
|
||||
const passwordScore = (e: any) => {
|
||||
score.value = e;
|
||||
};
|
||||
|
||||
const handleChangePassword = () => {
|
||||
passwordFormdataRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
password(passwordFormData)
|
||||
.then(() => {
|
||||
useMessage().success('修改成功');
|
||||
// 需要重新登录
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 保存用户
|
||||
const handleSaveUser = () => {
|
||||
formdataRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (formData.value.phone && formData.value.phone.includes('*')) {
|
||||
formData.value.phone = undefined;
|
||||
}
|
||||
|
||||
editInfo(formData.value)
|
||||
.then(() => {
|
||||
useMessage().success('修改成功');
|
||||
// 更新上下文的 user信息
|
||||
useUserInfo().setUserInfos();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const socialList = ref([] as any);
|
||||
|
||||
const initSocialList = () => {
|
||||
socialList.value = [
|
||||
{
|
||||
name: '企业微信',
|
||||
type: SocialLoginEnum.WEIXIN_CP,
|
||||
openId: formData.value.wxCpUserid,
|
||||
},
|
||||
{
|
||||
name: '钉钉办公',
|
||||
type: SocialLoginEnum.DINGTALK,
|
||||
openId: formData.value.wxDingUserid,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const handleClick = async (thirdpart: SocialLoginEnum) => {
|
||||
// 获取租户配置的账号信息
|
||||
const { data } = await getLoginAppList();
|
||||
const result = data.find((item: any) => item.type === thirdpart);
|
||||
if (validateNull(result)) {
|
||||
useMessage().error(t('scan.appErrorTip'));
|
||||
return;
|
||||
}
|
||||
|
||||
let redirect_uri, url;
|
||||
redirect_uri = encodeURIComponent(window.location.origin + '/#/authredirect');
|
||||
|
||||
if (thirdpart === SocialLoginEnum.WEIXIN_CP) {
|
||||
url = `https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=${result.appId}&agentid=${result.ext}&redirect_uri=${redirect_uri}&state=${SocialLoginEnum.WEIXIN_CP}-BIND`;
|
||||
}
|
||||
|
||||
if (thirdpart === SocialLoginEnum.DINGTALK) {
|
||||
url = `https://login.dingtalk.com/oauth2/auth?redirect_uri=${redirect_uri}&response_type=code&client_id=${result.appId}&scope=openid&state=${SocialLoginEnum.DINGTALK}-BIND&prompt=consent`;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
other.openWindow(url, thirdpart, 540, 540);
|
||||
}
|
||||
};
|
||||
|
||||
const unbinding = (type: SocialLoginEnum) => {
|
||||
unbindingUser(type)
|
||||
.then(() => {
|
||||
useMessage().success('解绑成功');
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
})
|
||||
.finally(() => {
|
||||
initUserInfo(formData.value.userId);
|
||||
});
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
visible.value = true;
|
||||
const data = useUserInfo().userInfos;
|
||||
initUserInfo(data.user.userId);
|
||||
// Object.assign(formData, data.user);
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const initUserInfo = (userId: any) => {
|
||||
loading.value = true;
|
||||
getObj(userId)
|
||||
.then((res) => {
|
||||
formData.value = res.data;
|
||||
initSocialList();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/theme/mixins/index.scss';
|
||||
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.personal-user-left {
|
||||
width: 180px;
|
||||
height: 130px;
|
||||
border-radius: 3px;
|
||||
|
||||
:deep(.el-upload) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-user-left-upload {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-user-right {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
|
||||
.personal-item-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
.personal-info-more {
|
||||
float: right;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info-box {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-info-ul {
|
||||
list-style: none;
|
||||
|
||||
.personal-info-li {
|
||||
font-size: 13px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
color: var(--el-text-color-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& a:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-recommend-row {
|
||||
.personal-recommend-col {
|
||||
.personal-recommend {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
right: 0px !important;
|
||||
bottom: 0px !important;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
font-size: 70px;
|
||||
transform: rotate(-30deg);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
|
||||
.personal-recommend-auto {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5%;
|
||||
color: var(--next-color-white);
|
||||
|
||||
.personal-recommend-msg {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-edit {
|
||||
.personal-edit-title {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.personal-edit-safe-box {
|
||||
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
padding: 15px 0;
|
||||
|
||||
.personal-edit-safe-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.personal-edit-safe-item-left {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-edit-safe-item-left-label {
|
||||
color: var(--el-text-color-regular);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.personal-edit-safe-item-left-value {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 178px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user