first commit

This commit is contained in:
ye1023
2026-04-09 15:09:34 +08:00
commit d8e3a63df1
2246 changed files with 352109 additions and 0 deletions
@@ -0,0 +1,271 @@
<template>
<BasicModal v-bind="config" :maxHeight="500" :title="currTitle" v-model:visible="visible" wrapClassName="loginSelectModal">
<a-form ref="formRef" v-bind="layout" :colon="false" class="loginSelectForm">
<a-form-item v-if="isMultiTenant" :validate-status="validate_status">
<!--label内容-->
<template #label>
<a-tooltip placement="topLeft">
<template #title>
<span>您隶属于多租户请选择当前所属租户</span>
</template>
<a-avatar style="background-color: #87d068" :size="30"> 租户 </a-avatar>
</a-tooltip>
</template>
<!--部门下拉内容-->
<a-select v-model:value="tenantSelected" placeholder="请选择登录部门" :class="{ 'valid-error': validate_status == 'error' }">
<template #suffixIcon>
<Icon icon="ant-design:gold-outline" />
</template>
<template v-for="tenant in tenantList" :key="tenant.id">
<a-select-option :value="tenant.id">{{ tenant.name }}</a-select-option>
</template>
</a-select>
</a-form-item>
<a-form-item v-if="isMultiDepart" :validate-status="validate_status1">
<!--label内容-->
<template #label>
<a-tooltip placement="topLeft">
<template #title>
<span>您隶属于多部门请选择当前所在部门</span>
</template>
<a-avatar style="background-color: rgb(104, 208, 203)" :size="30"> 部门 </a-avatar>
</a-tooltip>
</template>
<!--部门下拉内容-->
<a-select v-model:value="departSelected" placeholder="请选择登录部门" :class="{ 'valid-error': validate_status1 == 'error' }">
<template #suffixIcon>
<Icon icon="ant-design:gold-outline" />
</template>
<template v-for="depart in departList" :key="depart.orgCode">
<a-select-option :value="depart.orgCode">{{ depart.departName }}</a-select-option>
</template>
</a-select>
</a-form-item>
</a-form>
<template #footer>
<a-button @click="close">关闭</a-button>
<a-button @click="handleSubmit" type="primary">确认</a-button>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, watch, unref } from 'vue';
import { Avatar } from 'ant-design-vue';
import { BasicModal } from '/@/components/Modal';
import { getUserDeparts, selectDepart } from '/@/views/system/depart/depart.api';
import { getUserTenants } from '/@/views/system/tenant/tenant.api';
import { useMessage } from '/@/hooks/web/useMessage';
import { useUserStore } from '/@/store/modules/user';
const userStore = useUserStore();
const { createMessage, notification } = useMessage();
const props = defineProps({
title: { type: String, default: '部门选择' },
closable: { type: Boolean, default: false },
username: { type: String, default: '' },
});
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 18 },
};
const config = {
maskClosable: false,
closable: false,
canFullscreen: false,
width: '500px',
minHeight: 20,
maxHeight: 20,
};
const currTitle = ref('');
const isMultiTenant = ref(false);
const currentTenantName = ref('');
const tenantSelected = ref();
const tenantList = ref([]);
const validate_status = ref('');
const isMultiDepart = ref(false);
const currentDepartName = ref('');
const departSelected = ref('');
const departList = ref([]);
const validate_status1 = ref('');
//弹窗显隐
const visible = ref(false);
/**
* 弹窗打开前处理
*/
async function show() {
//加载部门
await loadDepartList();
//加载租户
await loadTenantList();
//标题配置
if (unref(isMultiTenant) && unref(isMultiDepart)) {
currTitle.value = '切换租户和部门';
} else if (unref(isMultiTenant)) {
currTitle.value =
unref(currentTenantName) && unref(currentTenantName).length > 0 ? `租户切换(当前租户 :${unref(currentTenantName)}` : props.title;
} else if (unref(isMultiDepart)) {
currTitle.value =
unref(currentDepartName) && unref(currentDepartName).length > 0 ? `部门切换(当前部门 :${unref(currentDepartName)}` : props.title;
}
//model显隐
if (unref(isMultiTenant) || unref(isMultiDepart)) {
visible.value = true;
}
}
/**
*加载部门信息
*/
async function loadDepartList() {
const result = await getUserDeparts();
if (!result.list || result.list.length == 0) {
return;
}
let currentDepart = result.list.filter((item) => item.orgCode == result.orgCode);
departList.value = result.list;
departSelected.value = currentDepart && currentDepart.length > 0 ? result.orgCode : '';
currentDepartName.value = currentDepart && currentDepart.length > 0 ? currentDepart[0].departName : '';
isMultiDepart.value = true;
}
/**
*加载租户信息
*/
async function loadTenantList() {
const result = await getUserTenants();
if (!result.list || result.list.length == 0) {
return;
}
let tenantId = userStore.getTenant;
let currentTenant = result.list.filter((item) => item.id == tenantId);
currentTenantName.value = currentTenant && currentTenant.length > 0 ? currentTenant[0].name : '';
tenantList.value = result.list;
tenantSelected.value = tenantId;
isMultiTenant.value = true;
}
/**
* 提交数据
*/
async function handleSubmit() {
if (unref(isMultiTenant) && unref(tenantSelected)==null) {
validate_status.value = 'error';
return false;
}
if (unref(isMultiDepart) && !unref(departSelected)) {
validate_status1.value = 'error';
return false;
}
departResolve()
.then(() => {
if (unref(isMultiTenant)) {
userStore.setTenant(unref(tenantSelected));
}
createMessage.success('切换成功');
//切换租户后要刷新首页
window.location.reload();
})
.catch((e) => {
console.log('登录选择出现问题', e);
})
.finally(() => {
if (unref(isMultiTenant)) {
userStore.setTenant(unref(tenantSelected));
}
close();
});
}
/**
*切换选择部门
*/
function departResolve() {
return new Promise(async (resolve, reject) => {
if (!unref(isMultiDepart)) {
resolve();
} else {
const result = await selectDepart({
username: userStore.getUserInfo.username,
orgCode: unref(departSelected),
loginTenantId: unref(tenantSelected),
});
if (result.userInfo) {
const userInfo = result.userInfo;
userStore.setUserInfo(userInfo);
resolve();
} else {
requestFailed(result);
userStore.logout();
reject();
}
}
});
}
/**
* 请求失败处理
*/
function requestFailed(err) {
notification.error({
message: '登录失败',
description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试',
duration: 4,
});
}
/**
* 关闭model
*/
function close() {
departClear();
}
/**
*初始化数据
*/
function departClear() {
currTitle.value = '';
isMultiTenant.value = false;
currentTenantName.value = '';
tenantSelected.value = '';
tenantList.value = [];
validate_status.value = '';
isMultiDepart.value = false;
currentDepartName.value = '';
departSelected.value = '';
departList.value = [];
validate_status1.value = '';
visible.value = false;
}
/**
* 监听username
*/
watch(
() => props.username,
(value) => {
value && loadDepartList();
}
);
defineExpose({
show,
});
</script>
<style lang="less" scoped>
.loginSelectForm {
margin-bottom: -20px;
}
.loginSelectModal {
top: 20px;
}
.valid-error .ant-select-selection__placeholder {
color: #f5222d;
}
</style>
@@ -0,0 +1,34 @@
<template>
<MenuItem :key="itemKey">
<span class="flex items-center">
<Icon :icon="icon" class="mr-1" />
<span>{{ text }}</span>
</span>
</MenuItem>
</template>
<script lang="ts">
import { Menu } from 'ant-design-vue';
import { computed, defineComponent, getCurrentInstance } from 'vue';
import Icon from '/@/components/Icon/index';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'DropdownMenuItem',
components: { MenuItem: Menu.Item, Icon },
props: {
// 【issues/6855】
itemKey: propTypes.string,
text: propTypes.string,
icon: propTypes.string,
},
setup(props) {
const instance = getCurrentInstance();
// update-begin--author:liaozhiyang---date:20240717---for:【issues/6855】组件使用key作props报警告,改为itemKey
const itemKey = computed(() => props.itemKey || instance?.vnode?.props?.itemKey);
// update-end--author:liaozhiyang---date:20240717---for:【issues/6855】组件使用key作props报警告,改为itemKey
return { itemKey };
},
});
</script>
@@ -0,0 +1,100 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="600px">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { rules } from '/@/utils/helper/validator';
import { defHttp } from '/@/utils/http/axios';
import { BasicModal, useModalInner } from '/@/components/Modal';
import BasicForm from '/@/components/Form/src/BasicForm.vue';
import { useForm } from '/@/components/Form/src/hooks/useForm';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocaleStore } from '/@/store/modules/locale';
import { useI18n } from '/@/hooks/web/useI18n';
const localeStore = useLocaleStore();
const { t } = useI18n();
// 声明Emits
const emit = defineEmits(['register']);
const $message = useMessage();
const formRef = ref();
const username = ref('');
// update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
const title = ref(t('layout.changePassword.changePassword'));
//表单配置
const [registerForm, { resetFields, validate, clearValidate }] = useForm({
schemas: [
{
label: t('layout.changePassword.oldPassword'),
field: 'oldpassword',
component: 'InputPassword',
required: true,
},
{
label: t('layout.changePassword.newPassword'),
field: 'password',
component: 'StrengthMeter',
componentProps: {
placeholder: t('layout.changePassword.pleaseEnterNewPassword'),
},
rules: [
{
required: true,
message: t('layout.changePassword.pleaseEnterNewPassword'),
},
],
},
{
label: t('layout.changePassword.confirmNewPassword'),
field: 'confirmpassword',
component: 'InputPassword',
dynamicRules: ({ values }) => rules.confirmPassword(values, true),
},
],
showActionButtonGroup: false,
wrapperCol: null,
labelWidth: localeStore.getLocale == 'zh_CN' ? 100 : 160,
});
// update-end--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner();
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
let params = Object.assign({ username: unref(username) }, values);
defHttp.put({ url: '/sys/user/updatePassword', params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
$message.createMessage.success(res.message);
//关闭弹窗
closeModal();
} else {
$message.createMessage.warning(res.message);
}
});
} finally {
setModalProps({ confirmLoading: false });
}
}
async function show(name) {
if (!name) {
$message.createMessage.warning('当前系统无登录用户!');
return;
} else {
username.value = name;
await setModalProps({ visible: true });
await resetFields();
await clearValidate();
}
}
defineExpose({
title,
show,
});
</script>
@@ -0,0 +1,254 @@
<template>
<Dropdown placement="bottomLeft" :overlayClassName="`${prefixCls}-dropdown-overlay`">
<span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex">
<img :class="`${prefixCls}__header`" :src="getAvatarUrl" />
<span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name `" class="truncate">
{{ getUserInfo.realname }}
</span>
</span>
</span>
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem itemKey="doc" :text="t('layout.header.dropdownItemDoc')" icon="ion:document-text-outline" v-if="getShowDoc" />
<MenuDivider v-if="getShowDoc" />
<MenuItem itemKey="account" :text="t('layout.header.dropdownItemSwitchAccount')" icon="ant-design:setting-outlined" />
<MenuItem itemKey="password" :text="t('layout.header.dropdownItemSwitchPassword')" icon="ant-design:edit-outlined" />
<MenuItem itemKey="depart" :text="t('layout.header.dropdownItemSwitchDepart')" icon="ant-design:cluster-outlined" />
<MenuItem itemKey="cache" :text="t('layout.header.dropdownItemRefreshCache')" icon="ion:sync-outline" />
<!-- <MenuItem
v-if="getUseLockPage"
itemKey="lock"
:text="t('layout.header.tooltipLock')"
icon="ion:lock-closed-outline"
/>-->
<MenuItem itemKey="logout" :text="t('layout.header.dropdownItemLoginOut')" icon="ion:power-outline" />
</Menu>
</template>
</Dropdown>
<LockAction v-if="lockActionVisible" ref="lockActionRef" @register="register" />
<DepartSelect ref="loginSelectRef" />
<UpdatePassword v-if="passwordVisible" ref="updatePasswordRef" />
</template>
<script lang="ts">
// components
import { Dropdown, Menu } from 'ant-design-vue';
import { defineComponent, computed, ref } from 'vue';
import { SITE_URL } from '/@/settings/siteSetting';
import { useUserStore } from '/@/store/modules/user';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/src/hooks/web/useMessage';
import { useGo } from '/@/hooks/web/usePage';
import headerImg from '/@/assets/images/header.jpg';
import { propTypes } from '/@/utils/propTypes';
import { openWindow } from '/@/utils';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { refreshCache, queryAllDictItems } from '/@/views/system/dict/dict.api';
import { DB_DICT_DATA_KEY } from '/src/enums/cacheEnum';
import { removeAuthCache, setAuthCache } from '/src/utils/auth';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getRefPromise } from '/@/utils/index';
type MenuEvent = 'logout' | 'doc' | 'lock' | 'cache' | 'depart';
const { createMessage } = useMessage();
export default defineComponent({
name: 'UserDropdown',
components: {
Dropdown,
Menu,
MenuItem: createAsyncComponent(() => import('./DropMenuItem.vue')),
MenuDivider: Menu.Divider,
LockAction: createAsyncComponent(() => import('../lock/LockModal.vue')),
DepartSelect: createAsyncComponent(() => import('./DepartSelect.vue')),
UpdatePassword: createAsyncComponent(() => import('./UpdatePassword.vue')),
},
props: {
theme: propTypes.oneOf(['dark', 'light']),
},
setup() {
const { prefixCls } = useDesign('header-user-dropdown');
const { t } = useI18n();
const { getShowDoc, getUseLockPage } = useHeaderSetting();
const userStore = useUserStore();
const go = useGo();
const passwordVisible = ref(false);
const lockActionVisible = ref(false);
const lockActionRef = ref(null);
const getUserInfo = computed(() => {
const { realname = '', avatar, desc } = userStore.getUserInfo || {};
return { realname, avatar: avatar || headerImg, desc };
});
const getAvatarUrl = computed(() => {
let { avatar } = getUserInfo.value;
if (avatar == headerImg) {
return avatar;
} else {
return getFileAccessHttpUrl(avatar);
}
});
const [register, { openModal }] = useModal();
/**
* 多部门弹窗逻辑
*/
const loginSelectRef = ref();
// update-begin--author:liaozhiyang---date:20230901---for:【QQYUN-6333】空路由问题—首次访问资源太大
async function handleLock() {
await getRefPromise(lockActionRef);
openModal(true);
}
// update-end--author:liaozhiyang---date:20230901---for:【QQYUN-6333】空路由问题—首次访问资源太大
// login out
function handleLoginOut() {
userStore.confirmLoginOut();
}
// open doc
function openDoc() {
openWindow(SITE_URL);
}
// 清除缓存
async function clearCache() {
const result = await refreshCache();
if (result.success) {
const res = await queryAllDictItems();
removeAuthCache(DB_DICT_DATA_KEY);
setAuthCache(DB_DICT_DATA_KEY, res.result);
// update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
createMessage.success(t('layout.header.refreshCacheComplete'));
// update-end--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
// update-begin--author:wangshuai---date:20241112---for:【issues/7433】vue3 数据字典优化建议
userStore.setAllDictItems(res.result);
// update-end--author:wangshuai---date:20241112---for:【issues/7433】vue3 数据字典优化建议
} else {
// update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
createMessage.error(t('layout.header.refreshCacheFailure'));
// update-end--author:liaozhiyang---date:20240124---for:【QQYUN-7970】国际化
}
}
// 切换部门
function updateCurrentDepart() {
loginSelectRef.value.show();
}
// 修改密码
const updatePasswordRef = ref();
// update-begin--author:liaozhiyang---date:20230901---for:【QQYUN-6333】空路由问题—首次访问资源太大
async function updatePassword() {
passwordVisible.value = true;
await getRefPromise(updatePasswordRef);
updatePasswordRef.value.show(userStore.getUserInfo.username);
}
// update-end--author:liaozhiyang---date:20230901---for:【QQYUN-6333】空路由问题—首次访问资源太大
function handleMenuClick(e: { key: MenuEvent }) {
switch (e.key) {
case 'logout':
handleLoginOut();
break;
case 'doc':
openDoc();
break;
case 'lock':
handleLock();
break;
case 'cache':
clearCache();
break;
case 'depart':
updateCurrentDepart();
break;
case 'password':
updatePassword();
break;
case 'account':
//update-begin---author:wangshuai ---date:20221125 for:进入用户设置页面------------
go(`/system/usersetting`);
//update-end---author:wangshuai ---date:20221125 for:进入用户设置页面--------------
break;
}
}
return {
prefixCls,
t,
getUserInfo,
getAvatarUrl,
handleMenuClick,
getShowDoc,
register,
getUseLockPage,
loginSelectRef,
updatePasswordRef,
passwordVisible,
lockActionVisible,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-header-user-dropdown';
.@{prefix-cls} {
height: @header-height;
padding: 0 0 0 10px;
padding-right: 10px;
overflow: hidden;
font-size: 12px;
cursor: pointer;
align-items: center;
img {
width: 24px;
height: 24px;
margin-right: 12px;
}
&__header {
border-radius: 50%;
}
&__name {
font-size: 14px;
}
&--dark {
&:hover {
background-color: @header-dark-bg-hover-color;
}
}
&--light {
&:hover {
background-color: @header-light-bg-hover-color;
}
.@{prefix-cls}__name {
color: @text-color-base;
}
.@{prefix-cls}__desc {
color: @header-light-desc-color;
}
}
&-dropdown-overlay {
// update-begin--author:liaozhiyang---date:20231226---for:【QQYUN-7512】顶部账号划过首次弹出时位置会变更一下
width: 160px;
// update-end--author:liaozhiyang---date:20231226---for:【QQYUN-7512】顶部账号划过首次弹出时位置会变更一下
.ant-dropdown-menu-item {
min-width: 160px;
}
}
}
</style>