fix: bug#11262
This commit is contained in:
parent
f5a125df65
commit
df6bf4d9ee
|
@ -2,6 +2,8 @@ export const LoginPath = '/login'
|
||||||
export const InitHomePath = '/init-home'
|
export const InitHomePath = '/init-home'
|
||||||
export const AccountCenterBindPath = '/account/center/bind'
|
export const AccountCenterBindPath = '/account/center/bind'
|
||||||
export const InitLicense = '/init-license'
|
export const InitLicense = '/init-license'
|
||||||
|
export const NotificationSubscriptionCode = 'account/NotificationSubscription'
|
||||||
|
export const NotificationRecordCode = 'account/NotificationRecord'
|
||||||
|
|
||||||
export const AccountMenu = {
|
export const AccountMenu = {
|
||||||
path: '/account',
|
path: '/account',
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { queryOwnThree } from '@/api/system/menu'
|
import { queryOwnThree } from '@/api/system/menu'
|
||||||
import { filterAsyncRouter, findCodeRoute, MenuItem } from '@/utils/menu'
|
import { filterAsyncRouter, findCodeRoute, MenuItem } from '@/utils/menu'
|
||||||
import { isArray } from 'lodash-es'
|
import { cloneDeep, isArray } from 'lodash-es'
|
||||||
import { usePermissionStore } from './permission'
|
import { usePermissionStore } from './permission'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { onlyMessage } from '@/utils/comm'
|
import { onlyMessage } from '@/utils/comm'
|
||||||
import { AccountMenu } from '@/router/menu'
|
import { AccountMenu, NotificationRecordCode, NotificationSubscriptionCode } from '@/router/menu'
|
||||||
|
import { MESSAGE_SUBSCRIBE_MENU_CODE, USER_CENTER_MENU_CODE } from '@/utils/consts'
|
||||||
|
|
||||||
const defaultOwnParams = [
|
const defaultOwnParams = [
|
||||||
{
|
{
|
||||||
|
@ -96,6 +97,13 @@ export const useMenuStore = defineStore({
|
||||||
const permission = usePermissionStore()
|
const permission = usePermissionStore()
|
||||||
permission.permissions = {}
|
permission.permissions = {}
|
||||||
const { menusData, silderMenus } = filterAsyncRouter(resp.result)
|
const { menusData, silderMenus } = filterAsyncRouter(resp.result)
|
||||||
|
|
||||||
|
// 是否存在通知订阅
|
||||||
|
const hasMessageSub = resp.result.some((item: { code: string }) => item.code === MESSAGE_SUBSCRIBE_MENU_CODE)
|
||||||
|
console.log('hasMessageSub', hasMessageSub)
|
||||||
|
if (!hasMessageSub) {
|
||||||
|
AccountMenu.children = AccountMenu.children.filter((item: { code: string }) => ![NotificationSubscriptionCode, NotificationRecordCode].includes(item.code) )
|
||||||
|
}
|
||||||
this.menus = findCodeRoute([...resp.result, AccountMenu])
|
this.menus = findCodeRoute([...resp.result, AccountMenu])
|
||||||
Object.keys(this.menus).forEach((item) => {
|
Object.keys(this.menus).forEach((item) => {
|
||||||
const _item = this.menus[item]
|
const _item = this.menus[item]
|
||||||
|
@ -112,7 +120,7 @@ export const useMenuStore = defineStore({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
menusData.push(AccountMenu)
|
menusData.push(AccountMenu)
|
||||||
this.siderMenus = silderMenus
|
this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE, MESSAGE_SUBSCRIBE_MENU_CODE].includes(item.name))
|
||||||
res(menusData)
|
res(menusData)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { MESSAGE_SUBSCRIBE_MENU_DATA } from '@/views/init-home/data/baseMenu'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态颜色
|
* 状态颜色
|
||||||
*/
|
*/
|
||||||
|
@ -49,3 +51,6 @@ export const SystemConst = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const USER_CENTER_MENU_CODE = 'account-center'
|
export const USER_CENTER_MENU_CODE = 'account-center'
|
||||||
|
export const USER_CENTER_MENU_BUTTON_CODE = 'user-center-passwd-update'
|
||||||
|
export const MESSAGE_SUBSCRIBE_MENU_CODE = 'message-subscribe'
|
||||||
|
export const MESSAGE_SUBSCRIBE_MENU_BUTTON_CODE = 'message-subscribe-view'
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card" v-if='updatePassword'>
|
||||||
<h3>修改密码</h3>
|
<h3>修改密码</h3>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="content" style="align-items: flex-end">
|
<div class="content" style="align-items: flex-end">
|
||||||
|
@ -245,7 +245,10 @@ import moment from 'moment';
|
||||||
import { getMe_api, getView_api, setView_api } from '@/api/home';
|
import { getMe_api, getView_api, setView_api } from '@/api/home';
|
||||||
import { isNoCommunity } from '@/utils/utils';
|
import { isNoCommunity } from '@/utils/utils';
|
||||||
import { userInfoType } from './typing';
|
import { userInfoType } from './typing';
|
||||||
|
import { usePermissionStore } from 'store/permission'
|
||||||
|
|
||||||
|
const btnHasPermission = usePermissionStore().hasPermission;
|
||||||
|
const updatePassword = btnHasPermission('account-center:user-center-passwd-update')
|
||||||
const permission = 'system/User';
|
const permission = 'system/User';
|
||||||
const userInfo = ref<userInfoType>({} as any);
|
const userInfo = ref<userInfoType>({} as any);
|
||||||
// 第三方账号
|
// 第三方账号
|
||||||
|
@ -361,7 +364,7 @@ function getViews() {
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
if (resp?.status === 200) {
|
if (resp?.status === 200) {
|
||||||
if (resp.result) currentView.value = resp.result?.content;
|
if (resp.result) currentView.value = resp.result?.content;
|
||||||
else if (resp.result.username === 'admin') {
|
else if (resp.result?.username === 'admin') {
|
||||||
currentView.value = 'comprehensive';
|
currentView.value = 'comprehensive';
|
||||||
} else currentView.value = 'init';
|
} else currentView.value = 'init';
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import BaseMenu from '../data/baseMenu';
|
import BaseMenu, { MESSAGE_SUBSCRIBE_MENU_DATA, USER_CENTER_MENU_DATA } from '../data/baseMenu'
|
||||||
import { getSystemPermission, updateMenus } from '@/api/initHome';
|
import { getSystemPermission, updateMenus } from '@/api/initHome';
|
||||||
/**
|
/**
|
||||||
* 获取菜单数据
|
* 获取菜单数据
|
||||||
|
@ -70,7 +70,8 @@ const menuCount = (menus: any[]) => {
|
||||||
*/
|
*/
|
||||||
const initMenu = async () => {
|
const initMenu = async () => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const res = await updateMenus(menuDatas.current);
|
// 用户中心
|
||||||
|
const res = await updateMenus([...menuDatas.current!, USER_CENTER_MENU_DATA, MESSAGE_SUBSCRIBE_MENU_DATA]);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import USER_CENTER_MENU_DATA from './baseMenu'
|
|
||||||
/**
|
/**
|
||||||
* 内置角色数据
|
* 内置角色数据
|
||||||
*/
|
*/
|
||||||
|
@ -10,33 +9,6 @@ export enum ROLEKEYS {
|
||||||
|
|
||||||
export type roleKeysType = keyof typeof ROLEKEYS;
|
export type roleKeysType = keyof typeof ROLEKEYS;
|
||||||
|
|
||||||
export const ROLE_USER_CENTER = {
|
|
||||||
...USER_CENTER_MENU_DATA,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
id: 'passwd-update',
|
|
||||||
name: '密码修改',
|
|
||||||
enabled: true,
|
|
||||||
granted: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'info-update',
|
|
||||||
name: '基本信息修改',
|
|
||||||
enabled: true,
|
|
||||||
granted: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'message',
|
|
||||||
name: '消息订阅',
|
|
||||||
enabled: true,
|
|
||||||
granted: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
assetAccesses: [],
|
|
||||||
granted: true,
|
|
||||||
owner: 'iot'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RoleData = {
|
export const RoleData = {
|
||||||
[ROLEKEYS.device]: {
|
[ROLEKEYS.device]: {
|
||||||
name: '设备接入岗',
|
name: '设备接入岗',
|
||||||
|
@ -245,7 +217,6 @@ export default {
|
||||||
createTime: 1659344075524,
|
createTime: 1659344075524,
|
||||||
granted: true,
|
granted: true,
|
||||||
},
|
},
|
||||||
ROLE_USER_CENTER
|
|
||||||
],
|
],
|
||||||
[ROLEKEYS.link]: [
|
[ROLEKEYS.link]: [
|
||||||
{
|
{
|
||||||
|
@ -987,6 +958,5 @@ export default {
|
||||||
accessDescription: '此菜单不支持数据权限控制',
|
accessDescription: '此菜单不支持数据权限控制',
|
||||||
granted: true,
|
granted: true,
|
||||||
},
|
},
|
||||||
ROLE_USER_CENTER
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,34 +1,44 @@
|
||||||
import { USER_CENTER_MENU_CODE } from '@/utils/consts'
|
import {
|
||||||
|
MESSAGE_SUBSCRIBE_MENU_BUTTON_CODE,
|
||||||
|
MESSAGE_SUBSCRIBE_MENU_CODE,
|
||||||
|
USER_CENTER_MENU_BUTTON_CODE,
|
||||||
|
USER_CENTER_MENU_CODE
|
||||||
|
} from '@/utils/consts'
|
||||||
|
|
||||||
export const USER_CENTER_MENU_DATA = {
|
export const USER_CENTER_MENU_DATA = {
|
||||||
id: '19a1f2c763e1231f1e1',
|
id: '19a1f2c763e1231f1e1',
|
||||||
accessSupport: { value: 'unsupported', label: '不支持'},
|
accessSupport: { value: 'unsupported', label: '不支持'},
|
||||||
supportDataAccess: false,
|
supportDataAccess: false,
|
||||||
code: USER_CENTER_MENU_CODE,
|
code: USER_CENTER_MENU_CODE,
|
||||||
|
name: '个人中心',
|
||||||
|
url: '/user-center',
|
||||||
|
sortIndex: 9999,
|
||||||
|
granted: true,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
id: 'passwd-update',
|
id: USER_CENTER_MENU_BUTTON_CODE,
|
||||||
name: '密码修改',
|
name: '修改密码',
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
permission: 'user',
|
permission: 'user',
|
||||||
action: ['update-self-pwd']
|
action: ['update-self-pwd']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MESSAGE_SUBSCRIBE_MENU_DATA = {
|
||||||
|
id: '23a1f2c7123e56731f890',
|
||||||
|
accessSupport: { value: 'unsupported', label: '不支持'},
|
||||||
|
supportDataAccess: false,
|
||||||
|
code: MESSAGE_SUBSCRIBE_MENU_CODE,
|
||||||
|
name: '通知订阅',
|
||||||
|
url: '/message-subscribe',
|
||||||
|
buttons: [
|
||||||
{
|
{
|
||||||
id: 'info-update',
|
id: MESSAGE_SUBSCRIBE_MENU_BUTTON_CODE,
|
||||||
name: '基本信息修改',
|
name: '查看',
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
permission: 'user',
|
|
||||||
action: ['update-self-info']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'message',
|
|
||||||
name: '消息订阅',
|
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
permission: 'alarm-config',
|
permission: 'alarm-config',
|
||||||
|
@ -36,7 +46,8 @@ export const USER_CENTER_MENU_DATA = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
sortIndex: 9998
|
||||||
}
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
|
@ -4263,7 +4274,5 @@ export default [
|
||||||
supportDataAccess: false
|
supportDataAccess: false
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
}
|
||||||
// 用户中心
|
|
||||||
USER_CENTER_MENU_DATA
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -139,9 +139,9 @@ const judgeInitSet = async () => {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onBeforeMount(() => {
|
||||||
judgeInitSet();
|
// judgeInitSet();
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.page-container {
|
.page-container {
|
||||||
|
|
|
@ -73,7 +73,7 @@ import BaseMenu from '@/views/init-home/data/baseMenu';
|
||||||
import type { AntTreeNodeDropEvent } from 'ant-design-vue/es/tree';
|
import type { AntTreeNodeDropEvent } from 'ant-design-vue/es/tree';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { onlyMessage } from '@/utils/comm';
|
import { onlyMessage } from '@/utils/comm';
|
||||||
import { USER_CENTER_MENU_CODE } from '@/utils/consts'
|
import { MESSAGE_SUBSCRIBE_MENU_CODE, USER_CENTER_MENU_CODE } from '@/utils/consts'
|
||||||
|
|
||||||
const selectedKeys: any = ref([]);
|
const selectedKeys: any = ref([]);
|
||||||
const treeData = ref<any>([]);
|
const treeData = ref<any>([]);
|
||||||
|
@ -186,7 +186,7 @@ onMounted(() => {
|
||||||
);
|
);
|
||||||
getMenuTree_api(params).then((resp: any) => {
|
getMenuTree_api(params).then((resp: any) => {
|
||||||
if (resp.status == 200) {
|
if (resp.status == 200) {
|
||||||
systemMenu.value = resp.result?.filter((item: { code: string }) => item.code !== USER_CENTER_MENU_CODE);
|
systemMenu.value = resp.result?.filter((item: { code: string }) => ![USER_CENTER_MENU_CODE, MESSAGE_SUBSCRIBE_MENU_CODE].includes(item.code));
|
||||||
//初始化菜单
|
//初始化菜单
|
||||||
const baseMenuData = developArrToMap(baseMenu.value);
|
const baseMenuData = developArrToMap(baseMenu.value);
|
||||||
const systemMenuData = developArrToMap(systemMenu.value, true);
|
const systemMenuData = developArrToMap(systemMenu.value, true);
|
||||||
|
|
|
@ -84,7 +84,7 @@ import { getMenuTree_api, delMenuInfo_api } from '@/api/system/menu';
|
||||||
import { message } from 'jetlinks-ui-components';
|
import { message } from 'jetlinks-ui-components';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { USER_CENTER_MENU_CODE } from '@/utils/consts'
|
import { MESSAGE_SUBSCRIBE_MENU_CODE, USER_CENTER_MENU_CODE } from '@/utils/consts'
|
||||||
const admin = useUserInfo().userInfos?.type.id === 'admin';
|
const admin = useUserInfo().userInfos?.type.id === 'admin';
|
||||||
|
|
||||||
const permission = 'system/Menu';
|
const permission = 'system/Menu';
|
||||||
|
@ -206,7 +206,7 @@ const table = reactive({
|
||||||
return {
|
return {
|
||||||
code: resp.message,
|
code: resp.message,
|
||||||
result: {
|
result: {
|
||||||
data: resp.result?.filter((item: { code: string }) => item.code !== USER_CENTER_MENU_CODE),
|
data: resp.result?.filter((item: { code: string }) => ![USER_CENTER_MENU_CODE, MESSAGE_SUBSCRIBE_MENU_CODE].includes(item.code)),
|
||||||
pageIndex: resp.pageIndex,
|
pageIndex: resp.pageIndex,
|
||||||
pageSize: resp.pageSize,
|
pageSize: resp.pageSize,
|
||||||
total: resp.total,
|
total: resp.total,
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
import { FormInstance, message } from 'ant-design-vue';
|
import { FormInstance, message } from 'ant-design-vue';
|
||||||
import PermissTree from '../components/PermissTree.vue';
|
import PermissTree from '../components/PermissTree.vue';
|
||||||
import { useMenuStore } from '@/store/menu';
|
import { useMenuStore } from '@/store/menu';
|
||||||
|
import { USER_CENTER_MENU_DATA } from '@/views/init-home/data/baseMenu'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getRoleDetails_api,
|
getRoleDetails_api,
|
||||||
|
@ -71,7 +72,7 @@ const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
menus: [],
|
menus: [USER_CENTER_MENU_DATA],
|
||||||
getForm: () => {
|
getForm: () => {
|
||||||
getRoleDetails_api(roleId).then((resp) => {
|
getRoleDetails_api(roleId).then((resp) => {
|
||||||
if (resp.status) {
|
if (resp.status) {
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
<j-checkbox
|
<j-checkbox
|
||||||
v-model:checked="record.granted"
|
v-model:checked="record.granted"
|
||||||
:indeterminate="record.indeterminate"
|
:indeterminate="record.indeterminate"
|
||||||
|
:disabled='record.code === USER_CENTER_MENU_CODE'
|
||||||
@change="menuChange(record, true)"
|
@change="menuChange(record, true)"
|
||||||
>{{ record.name }}</j-checkbox
|
>{{ record.name }}</j-checkbox
|
||||||
>
|
>
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
v-for="button in record.buttons"
|
v-for="button in record.buttons"
|
||||||
v-model:checked="button.granted"
|
v-model:checked="button.granted"
|
||||||
@change="actionChange(record)"
|
@change="actionChange(record)"
|
||||||
|
:disabled='[USER_CENTER_MENU_BUTTON_CODE].includes(button.id)'
|
||||||
>{{ button.name }}</j-checkbox
|
>{{ button.name }}</j-checkbox
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,6 +103,13 @@
|
||||||
import { cloneDeep, uniqBy } from 'lodash-es';
|
import { cloneDeep, uniqBy } from 'lodash-es';
|
||||||
import { getPrimissTree_api } from '@/api/system/role';
|
import { getPrimissTree_api } from '@/api/system/role';
|
||||||
import { getCurrentInstance } from 'vue';
|
import { getCurrentInstance } from 'vue';
|
||||||
|
import {
|
||||||
|
USER_CENTER_MENU_BUTTON_CODE,
|
||||||
|
MESSAGE_SUBSCRIBE_MENU_BUTTON_CODE,
|
||||||
|
USER_CENTER_MENU_CODE,
|
||||||
|
MESSAGE_SUBSCRIBE_MENU_CODE
|
||||||
|
} from '@/utils/consts'
|
||||||
|
|
||||||
const emits = defineEmits(['update:selectItems']);
|
const emits = defineEmits(['update:selectItems']);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -222,9 +231,10 @@ const init = () => {
|
||||||
() => {
|
() => {
|
||||||
// 深克隆表格数据的扁平版 因为会做一些改动 该改动只用于反馈给父组件,本组件无需变化
|
// 深克隆表格数据的扁平版 因为会做一些改动 该改动只用于反馈给父组件,本组件无需变化
|
||||||
const selected = cloneDeep(flatTableData).filter(
|
const selected = cloneDeep(flatTableData).filter(
|
||||||
(item) =>
|
(item: any) =>
|
||||||
(item.granted && item.parentId) ||
|
(item.granted && item.parentId) ||
|
||||||
(item.indeterminate && item.buttons),
|
(item.indeterminate && item.buttons) ||
|
||||||
|
item.code === USER_CENTER_MENU_CODE || item.code === MESSAGE_SUBSCRIBE_MENU_CODE, // 放开个人中心以及消息订阅
|
||||||
);
|
);
|
||||||
|
|
||||||
selected.forEach((item) => {
|
selected.forEach((item) => {
|
||||||
|
@ -260,9 +270,17 @@ init();
|
||||||
function getAllPermiss() {
|
function getAllPermiss() {
|
||||||
const id = route.params.id as string;
|
const id = route.params.id as string;
|
||||||
getPrimissTree_api(id).then((resp) => {
|
getPrimissTree_api(id).then((resp) => {
|
||||||
tableData.value = resp.result;
|
const _result = resp.result
|
||||||
|
// 默认选中个人中心相关设置
|
||||||
|
tableData.value = _result.map((item: { code: string , buttons: any[], granted: boolean}) => {
|
||||||
|
if (item.code === USER_CENTER_MENU_CODE) {
|
||||||
|
item.granted = true
|
||||||
|
item.buttons = item.buttons.map( b => ({...b, granted: true, enabled: true}))
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
});
|
||||||
|
|
||||||
treeToSimple(resp.result); // 表格数据扁平化
|
treeToSimple(tableData.value); // 表格数据扁平化
|
||||||
|
|
||||||
const selectList = flatTableData.filter((item) => item.granted); // 第一列选中的项
|
const selectList = flatTableData.filter((item) => item.granted); // 第一列选中的项
|
||||||
emits('update:selectItems', selectList); // 选中的项传回父组件
|
emits('update:selectItems', selectList); // 选中的项传回父组件
|
||||||
|
|
Loading…
Reference in New Issue