This commit is contained in:
吴红兵
2026-03-07 01:34:48 +08:00
parent adc511cfdc
commit 94c3473958
1211 changed files with 599405 additions and 322105 deletions

View File

@@ -33,7 +33,7 @@
import { useI18n } from 'vue-i18n';
import { getObj, deptTree, addObj, putObj } from '/@/api/admin/dept';
import { useMessage } from '/@/hooks/message';
import {rule} from "/@/utils/validate";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -52,7 +52,10 @@ const loading = ref(false);
const dataRules = ref({
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '部门名称不能为空', trigger: 'blur' },
],
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
});

View File

@@ -12,9 +12,9 @@ export default {
inputLeaderIdTip: 'input leader',
inputsortOrderTip: 'input sortOrder',
importTip: 'import dept',
addNodeText:'add dept',
editNodeText:'edit dept',
delNodeText:'delete 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',
},

View File

@@ -1,21 +1,21 @@
export default {
sysdept: {
name: '部门名称',
parentId: '上级部门',
createTime: '创建时间',
weight: '排序',
sortOrder: '排序',
leaderId: '部门负责人',
inputdeptNameTip: '请输入部门名称',
inputnameTip: '请输入部门名称',
inputLeaderIdTip: '请输入部门领导',
inputparentIdTip: '请选择上级部门',
inputsortOrderTip: '请输入排序',
importTip: '导入部门',
addNodeText: '添加部门',
editNodeText: '编辑部门',
delNodeText: '删除部门',
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
view: '树/表视图'
},
sysdept: {
name: '部门名称',
parentId: '上级部门',
createTime: '创建时间',
weight: '排序',
sortOrder: '排序',
leaderId: '部门负责人',
inputdeptNameTip: '请输入部门名称',
inputnameTip: '请输入部门名称',
inputLeaderIdTip: '请输入部门领导',
inputparentIdTip: '请选择上级部门',
inputsortOrderTip: '请输入排序',
importTip: '导入部门',
addNodeText: '添加部门',
editNodeText: '编辑部门',
delNodeText: '删除部门',
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
view: '树/表视图',
},
};

View File

@@ -130,6 +130,6 @@ const resetQuery = () => {
</script>
<style scoped>
:deep(.el-table__body tr td) {
text-align: left !important;
text-align: left !important;
}
</style>
</style>

View File

@@ -1,50 +1,45 @@
<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()"/>
<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';
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 { t } = useI18n();
// 定义变量内容
const tableRef = ref(); // 表格引用
const deptDialogRef = ref(); // 部门对话框引用
@@ -58,33 +53,33 @@ const isExpand = ref(false); // 是否展开
* @returns Promise&lt;any&gt;
*/
const queryDeptTree = (params?: any) => {
return deptTree(params);
return deptTree(params);
};
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: queryDeptTree, // 页面列表数据
queryForm: {
deptName: '', // 部门名称
},
isPage: false, // 是否分页
descs: ['create_time'], // 排序字段
pageList: queryDeptTree, // 页面列表数据
queryForm: {
deptName: '', // 部门名称
},
isPage: false, // 是否分页
descs: ['create_time'], // 排序字段
});
/**
* 使用 useTable 定义表格相关操作
*/
const {getDataList, tableStyle} = useTable(state);
const { getDataList, tableStyle } = useTable(state);
/**
* 展开/折叠部门树方法
*/
const handleExpand = async () => {
isExpand.value = !isExpand.value;
const dataList = await deptTree();
toggleExpand(dataList.data, isExpand.value);
isExpand.value = !isExpand.value;
const dataList = await deptTree();
toggleExpand(dataList.data, isExpand.value);
};
/**
@@ -93,12 +88,12 @@ const handleExpand = async () => {
* @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);
}
}
for (const key in children) {
tableRef.value?.toggleRowExpansion(children[key], unfold);
if (children[key].children) {
toggleExpand(children[key].children!, unfold);
}
}
};
/**
@@ -106,32 +101,32 @@ const toggleExpand = (children: any[], unfold = true) => {
* @param row - 当前行数据
*/
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
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);
}
try {
await delObj(row.id);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
};
const handleAdd = ()=>{
deptDialogRef.value.openDialog('add')
}
const handleAdd = () => {
deptDialogRef.value.openDialog('add');
};
/**
* 暴露组件中的一些方法和变量
*/
defineExpose({
handleAdd, // 新增时间
state, // 响应式表格数据
getDataList, // 获取列表数据方法
handleExpand // 展开/折叠部门树方法
handleAdd, // 新增时间
state, // 响应式表格数据
getDataList, // 获取列表数据方法
handleExpand, // 展开/折叠部门树方法
});
</script>

View File

@@ -1,127 +1,134 @@
<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="100" :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-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="100" :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>
<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>
</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";
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 { t } = useI18n();
// 引入组件
const IconSelector = defineAsyncComponent(() => import('/@/components/IconSelector/index.vue'));
@@ -132,148 +139,160 @@ 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[], // 上级菜单数据
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'}],
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',
}],
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' },
],
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
state.ruleForm.menuId = '';
visible.value = true;
originalName.value = ''; // Reset the original name
nextTick(() => {
menuDialogFormRef.value?.resetFields();
state.ruleForm.parentId = row?.id || '-1';
});
nextTick(() => {
menuDialogFormRef.value?.resetFields();
state.ruleForm.parentId = row?.id || '-1';
});
if (row?.id && type === 'edit') {
state.ruleForm.menuId = row.id;
// 获取当前节点菜单信息
getMenuDetail(row.id);
}
// 渲染上级菜单列表树
getAllMenuData();
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]);
});
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);
});
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;
}
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
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;
}
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,
openDialog,
});
</script>

View File

@@ -1,37 +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',
},
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',
},
};

View File

@@ -1,31 +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: '菜单包含下级不能删除',
},
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: '菜单包含下级不能删除',
},
};

View File

@@ -198,6 +198,6 @@ const handleDelete = async (row: any) => {
<style scoped>
:deep(.el-table__body tr td) {
text-align: left !important;
text-align: left !important;
}
</style>

View File

@@ -27,7 +27,7 @@
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";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -56,7 +56,7 @@ const form = reactive({
// 定义校验规则
const dataRules = ref({
postCode: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位编码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -66,7 +66,7 @@ const dataRules = ref({
},
],
postName: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位名称不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -75,8 +75,14 @@ const dataRules = ref({
trigger: 'blur',
},
],
postSort: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位排序不能为空', trigger: 'blur' }],
remark: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位描述不能为空', trigger: 'blur' }],
postSort: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位排序不能为空', trigger: 'blur' },
],
remark: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位描述不能为空', trigger: 'blur' },
],
});
// 打开弹窗

View File

@@ -40,7 +40,7 @@
@selection-change="handleSelectionChange"
style="width: 100%"
v-loading="state.loading"
row-key="postId"
row-key="postId"
border
:cell-style="tableStyle?.cellStyle"
:header-cell-style="tableStyle?.headerCellStyle"
@@ -115,7 +115,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/post/export', Object.assign(state.queryForm,{ids:selectObjs}), 'post.xlsx');
downBlobFile('/admin/post/export', Object.assign(state.queryForm, { ids: selectObjs }), 'post.xlsx');
};
// 多选事件

View File

@@ -1,178 +1,169 @@
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="80%"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClose"
center
>
<el-form>
<el-form-item class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
<el-card class="role-group-card" shadow="hover">
<template #header>
<span class="group-name">{{ groupName }}</span>
</template>
<div class="role-group">
<el-radio-button
v-for="item in roles"
:key="item.roleCode"
:label="item.roleCode"
size="small"
>
{{ item.roleName }}
</el-radio-button>
</div>
</el-card>
</template>
</el-radio-group>
</el-form-item>
</el-form>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="80%"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClose"
center
>
<el-form>
<el-form-item class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
<el-card class="role-group-card" shadow="hover">
<template #header>
<span class="group-name">{{ groupName }}</span>
</template>
<div class="role-group">
<el-radio-button v-for="item in roles" :key="item.roleCode" :label="item.roleCode" size="small">
{{ item.roleName }}
</el-radio-button>
</div>
</el-card>
</template>
</el-radio-group>
</el-form-item>
</el-form>
<template v-if="!requireSelectToClose" #footer>
<el-button @click="handleFooterClose"> </el-button>
</template>
</el-dialog>
<template v-if="!requireSelectToClose" #footer>
<el-button @click="handleFooterClose"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, toRef } from 'vue'
import { listAllRole } from '/@/api/admin/role'
import { Local, Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { ref, computed, toRef } from 'vue';
import { listAllRole } from '/@/api/admin/role';
import { Local, Session } from '/@/utils/storage';
import { useMessage } from '/@/hooks/message';
/** 弹框标题,如「角色切换」「登录角色选择」 */
const props = withDefaults(
defineProps<{ title?: string; requireSelectToClose?: boolean }>(),
{ title: '角色切换', requireSelectToClose: false }
)
const dialogTitle = computed(() => props.title)
const props = withDefaults(defineProps<{ title?: string; requireSelectToClose?: boolean }>(), { title: '角色切换', requireSelectToClose: false });
const dialogTitle = computed(() => props.title);
const visible = ref(false)
const radio = ref('')
const visible = ref(false);
const radio = ref('');
/** 按分组名分组的角色列表:{ "未分组": [{ roleId, roleName, roleCode, ... }], ... } */
const allRoleGroups = ref<Record<string, any[]>>({})
const requireSelectToClose = toRef(props, 'requireSelectToClose')
const allRoleGroups = ref<Record<string, any[]>>({});
const requireSelectToClose = toRef(props, 'requireSelectToClose');
const open = () => {
if (visible.value) return
visible.value = true
listAllRole().then((res) => {
allRoleGroups.value = res.data && typeof res.data === 'object' && !Array.isArray(res.data)
? res.data
: { '未分组': Array.isArray(res.data) ? res.data : [] }
radio.value = Local.get('roleCode')
})
}
if (visible.value) return;
visible.value = true;
listAllRole().then((res) => {
allRoleGroups.value =
res.data && typeof res.data === 'object' && !Array.isArray(res.data) ? res.data : { 未分组: Array.isArray(res.data) ? res.data : [] };
radio.value = Local.get('roleCode');
});
};
/** 根据 roleCode 从分组数据中查找角色 */
const findRoleByCode = (code: string) => {
for (const roles of Object.values(allRoleGroups.value)) {
if (!Array.isArray(roles)) continue
const found = roles.find((r: any) => r.roleCode === code)
if (found) return found
}
return null
}
for (const roles of Object.values(allRoleGroups.value)) {
if (!Array.isArray(roles)) continue;
const found = roles.find((r: any) => r.roleCode === code);
if (found) return found;
}
return null;
};
const canClose = () => {
if (!radio.value) {
useMessage().warning('请选择一个角色')
return false
}
return true
}
if (!radio.value) {
useMessage().warning('请选择一个角色');
return false;
}
return true;
};
const handleBeforeClose = (done: () => void) => {
if (requireSelectToClose.value) {
useMessage().warning('请先选择登录角色')
return
}
if (!canClose()) return
done()
}
if (requireSelectToClose.value) {
useMessage().warning('请先选择登录角色');
return;
}
if (!canClose()) return;
done();
};
const handleFooterClose = () => {
if (!canClose()) return
visible.value = false
}
if (!canClose()) return;
visible.value = false;
};
const handleChangeRole = (label: string) => {
const obj = findRoleByCode(label)
if (!obj) return
Local.set('roleCode', obj.roleCode)
Local.set('roleName', obj.roleName)
Local.set('roleId', obj.roleId)
useMessage().success('操作成功')
// 清掉 tags 缓存,重载后只保留首页 tag
Session.remove('tagsViewList')
// 清除 pinia 持久化的 tagsView 路由,避免重载后先恢复旧角色菜单再被新路由覆盖前就初始化出“不存在的 tag”
try {
window.localStorage.removeItem('tagsViewRoutes')
} catch (_) {}
setTimeout(() => {
window.location.hash = '#/home'
window.location.reload()
}, 500)
}
const obj = findRoleByCode(label);
if (!obj) return;
Local.set('roleCode', obj.roleCode);
Local.set('roleName', obj.roleName);
Local.set('roleId', obj.roleId);
useMessage().success('操作成功');
// 清掉 tags 缓存,重载后只保留首页 tag
Session.remove('tagsViewList');
// 清除 pinia 持久化的 tagsView 路由,避免重载后先恢复旧角色菜单再被新路由覆盖前就初始化出“不存在的 tag”
try {
window.localStorage.removeItem('tagsViewRoutes');
} catch (_) {}
setTimeout(() => {
window.location.hash = '#/home';
window.location.reload();
}, 500);
};
defineExpose({
open
})
open,
});
</script>
<style scoped lang="scss">
.role-form-item {
:deep(.el-form-item__content) {
flex-wrap: wrap;
}
:deep(.el-form-item__content) {
flex-wrap: wrap;
}
}
.role-radio-group {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
:deep(.el-radio-button) {
margin: 0;
}
:deep(.el-radio-button__inner) {
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
line-height: 1.3;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
:deep(.el-radio-button) {
margin: 0;
}
:deep(.el-radio-button__inner) {
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
line-height: 1.3;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
}
.role-group-card {
width: 100%;
flex: 0 0 auto;
width: 100%;
flex: 0 0 auto;
:deep(.el-card__header) {
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-card__body) {
padding: 6px 12px 8px;
}
:deep(.el-card__header) {
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-card__body) {
padding: 6px 12px 8px;
}
}
.role-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
}
.group-name {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>
</style>

View File

@@ -47,7 +47,6 @@
style="width: 100%"
row-key="roleId"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@selection-change="handleSelectionChange"
border
:cell-style="tableStyle.cellStyle"
@@ -67,7 +66,7 @@
</template>
</el-table-column>
<el-table-column prop="roleSort" label="排序" width="80" align="center">
<template #default="scope">{{ scope.row._isGroup ? '—' : (scope.row.roleSort ?? 0) }}</template>
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleSort ?? 0 }}</template>
</el-table-column>
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip min-width="120">
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleCode }}</template>
@@ -133,9 +132,18 @@
title="批量指定关联用户"
width="720px"
destroy-on-close
@close="assignUserKeyword = ''; assignUserType = ''; assignUserList = []; assignUserTree = []; assignSelectedIds = []">
@close="
assignUserKeyword = '';
assignUserType = '';
assignUserList = [];
assignUserTree = [];
assignSelectedIds = [];
"
>
<template v-if="assignCurrentRole">
<div class="mb12"><el-text type="info">当前角色{{ assignCurrentRole.roleName }}{{ assignCurrentRole.roleCode }}</el-text></div>
<div class="mb12">
<el-text type="info">当前角色{{ assignCurrentRole.roleName }}{{ assignCurrentRole.roleCode }}</el-text>
</div>
<el-form :inline="true" class="mb12">
<el-form-item label="用户类型" required>
<el-radio-group v-model="assignUserType">
@@ -161,7 +169,8 @@
v-loading="assignUserLoading"
max-height="360"
border
@selection-change="handleAssignUserSelectionChange">
@selection-change="handleAssignUserSelectionChange"
>
<el-table-column type="selection" width="50" align="left" :selectable="(row: any) => !row._isDept" />
<el-table-column prop="label" label="部门 / 姓名" min-width="200">
<template #default="{ row }">
@@ -173,20 +182,24 @@
<template #default="{ row }">{{ row._isDept ? '—' : row.username }}</template>
</el-table-column>
<el-table-column prop="deptName" label="部门" width="140">
<template #default="{ row }">{{ row._isDept ? '—' : (row.deptName || '—') }}</template>
<template #default="{ row }">{{ row._isDept ? '—' : row.deptName || '—' }}</template>
</el-table-column>
</el-table>
</template>
<template #footer>
<el-button @click="showAssignUserDialog = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit">确定已选 {{ assignSelectedIds.length }} </el-button>
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit"
>确定已选 {{ assignSelectedIds.length }} </el-button
>
</template>
</el-dialog>
<!-- 查看角色关联用户弹窗 -->
<el-dialog v-model="showRoleUsersDialog" title="关联用户" width="560px" destroy-on-close>
<template v-if="currentRoleForUsers">
<div class="mb12"><el-text type="info">角色{{ currentRoleForUsers.roleName }}{{ currentRoleForUsers.roleCode }}</el-text></div>
<div class="mb12">
<el-text type="info">角色{{ currentRoleForUsers.roleName }}{{ currentRoleForUsers.roleCode }}</el-text>
</div>
<el-table :data="roleUsersList" v-loading="roleUsersLoading" max-height="400" border>
<el-table-column prop="deptName" label="部门" min-width="140" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" width="100" show-overflow-tooltip />
@@ -298,9 +311,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
const kw = String(params.roleName).trim().toLowerCase();
if (kw) {
data = data.filter(
(r: any) =>
(r.roleName && r.roleName.toLowerCase().includes(kw)) ||
(r.roleCode && r.roleCode.toLowerCase().includes(kw))
(r: any) => (r.roleName && r.roleName.toLowerCase().includes(kw)) || (r.roleCode && r.roleCode.toLowerCase().includes(kw))
);
}
}
@@ -353,10 +364,10 @@ const dictType = ref([
label: '本级',
value: '3',
},
{
label: '本人',
value: '4',
},
{
label: '本人',
value: '4',
},
]);
// table hook无分页不暴露 currentChangeHandle/sizeChangeHandle
@@ -370,7 +381,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
downBlobFile('/admin/role/export', Object.assign(state.queryForm, { ids: selectObjs }), 'role.xlsx');
};
// 是否可以多选(分组行不可选,管理员角色不可选)
@@ -470,9 +481,7 @@ async function loadAssignUserList() {
}
function handleAssignUserSelectionChange(rows: any[]) {
assignSelectedIds.value = (rows || [])
.filter((r: any) => !r._isDept && r.userId)
.map((r: any) => r.userId);
assignSelectedIds.value = (rows || []).filter((r: any) => !r._isDept && r.userId).map((r: any) => r.userId);
}
async function handleAssignUsersSubmit() {
@@ -520,9 +529,21 @@ const handleBatchGroup = async () => {
font-weight: 600;
color: var(--el-text-color-primary);
}
.mb12 { margin-bottom: 12px; }
.mb8 { margin-bottom: 8px; }
.mt8 { margin-top: 8px; }
.assign-user-tip { color: var(--el-text-color-secondary); font-size: 13px; }
.dept-row { font-weight: 600; color: var(--el-text-color-primary); }
.mb12 {
margin-bottom: 12px;
}
.mb8 {
margin-bottom: 8px;
}
.mt8 {
margin-top: 8px;
}
.assign-user-tip {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.dept-row {
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>

View File

@@ -15,12 +15,24 @@
<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-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-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">
@@ -71,11 +83,11 @@
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 { 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";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -120,7 +132,7 @@ const checkedMenu = ref<any[]>([]);
// 定义校验规则
const dataRules = ref({
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '名称不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -130,7 +142,7 @@ const dataRules = ref({
},
],
code: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '编码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -183,7 +195,7 @@ const onSubmit = async () => {
}
if (menuTreeRef.value?.getCheckedKeys()) {
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()]
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()];
if (!checkMenu.includes('1300')) {
useMessage().error('必须分配角色管理功能');
@@ -207,7 +219,7 @@ const onSubmit = async () => {
} catch (err: any) {
useMessage().error(err.msg);
} finally {
await fetchList()
await fetchList();
loading.value = false;
}
};

View File

@@ -1,53 +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: '创建',
},
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: '请输入登录页背景图',
}
individuation: {
websiteName: '网站名称',
miniQr: '移动端二维码',
logo: '网站图标',
footerAuthor: '页脚信息',
background: '登录页背景图',
inputIndividuationNameTip: '请输入网站名称',
inputMiniQrTip: '请输入网站图标',
inputLogoTip: '请输入网站Logo',
inputFooterAuthorTip: '请输入页脚信息',
inputBackgroundTip: '请输入登录页背景图',
},
};

View File

@@ -1,60 +1,60 @@
<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>
<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>
</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";
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 { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
@@ -62,42 +62,38 @@ const visible = ref(false);
const loading = ref(false);
// 字典
const {status_type} = useDict('status_type');
const { status_type } = useDict('status_type');
// 导入配置文件
const stores = useThemeConfig(pinia);
const {themeConfig} = storeToRefs(stores);
const { themeConfig } = storeToRefs(stores);
// 提交表单数据
const form = reactive({
id: '',
websiteName: themeConfig.value.globalTitle,
background: '',
miniQr: '',
footer: themeConfig.value.footerAuthor,
id: '',
websiteName: themeConfig.value.globalTitle,
background: '',
miniQr: '',
footer: themeConfig.value.footerAuthor,
});
// 定义校验规则
const dataRules = ref({
});
const dataRules = ref({});
// 打开弹窗
const openDialog = (id: string): void => {
visible.value = true;
form.id = ''
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
if (id) {
form.id = id;
getTenantData(id);
}
if (id) {
form.id = id;
getTenantData(id);
}
};
/**
@@ -105,33 +101,30 @@ const openDialog = (id: string): void => {
* @param {string} id - 部门 ID。
*/
const getTenantData = async (id: any) => {
const res = await getObj(id);
Object.assign(form, res.data);
const res = await getObj(id);
Object.assign(form, res.data);
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {
});
if (!valid) return false;
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;
}
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,
openDialog,
});
</script>

View File

@@ -3,41 +3,41 @@
<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"
autocomplete="off"
:readonly="formState.usernameReadonly"
@focus="formState.usernameReadonly = false"
></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"
autocomplete="new-password"
:readonly="formState.passwordReadonly"
@focus="formState.passwordReadonly = false"
></el-input>
</el-form-item>
</el-col>
<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"
autocomplete="off"
:readonly="formState.usernameReadonly"
@focus="formState.usernameReadonly = false"
></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"
autocomplete="new-password"
:readonly="formState.passwordReadonly"
@focus="formState.passwordReadonly = false"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.name')" prop="realName">
<el-input clearable :placeholder="$t('sysuser.inputNameTip')" v-model="dataForm.realName"></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.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" filterable>
@@ -45,13 +45,13 @@
</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.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
@@ -67,11 +67,11 @@
</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.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.lockFlag')" prop="lockFlag">
<el-radio-group v-model="dataForm.lockFlag">
@@ -135,7 +135,7 @@ const dataForm = reactive({
deptId: '',
roleList: [],
postList: [],
realName: '',
realName: '',
email: '',
post: [] as string[],
role: [] as string[],
@@ -227,7 +227,7 @@ const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {

View File

@@ -79,12 +79,12 @@
<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="realName" 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.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>

View File

@@ -2,12 +2,16 @@
<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>
<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">
@@ -55,26 +59,36 @@
</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>
<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-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">
@@ -103,13 +117,17 @@
</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 #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>
社交登录
</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>
@@ -132,14 +150,14 @@
</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 { 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 { Session } from '/@/utils/storage';
import { useI18n } from 'vue-i18n';
import { getLoginAppList } from '/@/api/admin/social';
import { SocialLoginEnum } from '/@/api/login';
const { t } = useI18n();
@@ -177,9 +195,18 @@ const ruleForm = reactive({
{ 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' }],
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) {