feat: 接入 UserHome 前端业务代码(含真实 src/依赖,保留 projman 标准 CI)
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="app-list-wrapper base-mask">
|
||||
<vuescroll :ops="scrollOps">
|
||||
<template v-if="!isSearch" v-for="(item, index) in appList">
|
||||
<h3 class="app-type-box">{{ item.name }}</h3>
|
||||
<div
|
||||
:class="{
|
||||
'app-list-box': true,
|
||||
'mb-25': index === appList.length - 1,
|
||||
}"
|
||||
>
|
||||
<template v-for="citem in item.apps">
|
||||
<ContextMenu
|
||||
class="app-container app-container__mask"
|
||||
:title="citem.name"
|
||||
@on-change="onMenuChange"
|
||||
@select="onMenuSel"
|
||||
:menu="[
|
||||
{
|
||||
label: '添加到桌面',
|
||||
appId: citem.id,
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
label: '新窗口打开',
|
||||
appId: citem.id,
|
||||
url: citem.webAddr,
|
||||
id: '2',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a @click="linkPage(citem.webAddr, citem)">
|
||||
<img :src="$utils.appIconPath(citem.icon)" />
|
||||
<div class="app-name">{{ citem.name }}</div>
|
||||
</a>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!!isSearch">
|
||||
<div class="app-list-box" v-if="searchAppList.length > 0">
|
||||
<template v-for="item in searchAppList">
|
||||
<ContextMenu
|
||||
class="app-container app-container__mask"
|
||||
:title="item.name"
|
||||
@on-change="onMenuChange"
|
||||
@select="onMenuSel"
|
||||
:menu="[
|
||||
{
|
||||
label: '添加到桌面',
|
||||
appId: item.id,
|
||||
id: '1',
|
||||
},
|
||||
{
|
||||
label: '新窗口打开',
|
||||
appId: item.id,
|
||||
url: item.webAddr,
|
||||
id: '2',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a @click="linkPage(item.webAddr, item)">
|
||||
<img
|
||||
:src="requireImg('defappicon.png')"
|
||||
v-real-img="$utils.appIconPath(item.icon)"
|
||||
/>
|
||||
<div class="app-name">{{ item.name }}</div>
|
||||
</a>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
</div>
|
||||
<div class="no-search-box" v-else>
|
||||
<img :src="requireImg('no-search-bg.png')" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
</vuescroll>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import vuescroll from "vuescroll";
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from "vue";
|
||||
import BaseApi from "@/api/BaseApi";
|
||||
import { useDefaultStore } from "@/store";
|
||||
import { requireImg } from "@/utils/hook/useImg";
|
||||
import mittBus from "@/utils/mitt";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { BschLoadTypeEnum, DesktopAppTypeEnum, LinkOpenTypeEnum } from "@/types";
|
||||
import { flatten } from "lodash";
|
||||
import UserSettingApi from "@/api/UserSettingApi";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { bschLinkPage } from "@/utils/bschLinkPage";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
|
||||
// 仅在内容溢出时显示原生滚动条,避免空内容时也常驻滚动条
|
||||
const scrollOps = {
|
||||
vuescroll: { mode: "native", sizeStrategy: "percent" },
|
||||
scrollPanel: { scrollingX: false },
|
||||
bar: { showDelay: 500, onlyShowBarOnScroll: true, keepShow: false },
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const store = useDefaultStore();
|
||||
const { $conf, $utils } = useCurrentInstance();
|
||||
|
||||
const searchAppText = ref<string | null>(null),
|
||||
appList = ref<any[]>([]);
|
||||
|
||||
const searchAppList = computed(() => {
|
||||
const allAppList = flatten(appList.value.map(x => x.apps));
|
||||
return allAppList.filter(x => x.name.indexOf(searchAppText.value) !== -1);
|
||||
});
|
||||
|
||||
const isSearch = computed(() => !!searchAppText.value);
|
||||
const compMenuId = computed(() => {
|
||||
let menuList = store.curUserMenuList.find(x => x.url == route.path);
|
||||
return menuList ? menuList.id : -1;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
reloadAppData();
|
||||
mittBus.emit("searchReset");
|
||||
mittBus.on("searchChange", val => {
|
||||
searchAppText.value = val;
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
mittBus.emit("searchReset");
|
||||
mittBus.off("searchChange");
|
||||
});
|
||||
|
||||
watch(() => store.curRoleUid, reloadAppData);
|
||||
|
||||
async function reloadAppData() {
|
||||
const list = await BaseApi.getUserApp(store.getCurUserId, store.getCurSchId);
|
||||
appList.value = list.filter(x => x.apps.length > 0);
|
||||
}
|
||||
|
||||
function onMenuChange(curDom: any, state: boolean) {
|
||||
$(curDom).toggleClass("app-container__focus", state);
|
||||
}
|
||||
|
||||
function onMenuSel(item: any) {
|
||||
if (item.id == 1) {
|
||||
onLightAppSaveDesktop(item.appId);
|
||||
}
|
||||
if (item.id == 2) {
|
||||
bschLinkPage({ url: item.url, type: LinkOpenTypeEnum.Open });
|
||||
}
|
||||
if (item.id == 3) {
|
||||
router.push({
|
||||
name: "BschAppDetails",
|
||||
params: {
|
||||
appId: item.appId,
|
||||
menuId: compMenuId.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function onLightAppSaveDesktop(id: number) {
|
||||
try {
|
||||
await UserSettingApi.saveUserDesktopAppById(id, DesktopAppTypeEnum.Bsch);
|
||||
Message.success("添加成功");
|
||||
mittBus.emit("reloadUserSettings");
|
||||
} catch (error) {
|
||||
Message.error("内部服务错误");
|
||||
}
|
||||
}
|
||||
|
||||
function linkPage(url: string, item: any) {
|
||||
let options: Parameters<typeof bschLinkPage>[0];
|
||||
options = {
|
||||
url,
|
||||
type: LinkOpenTypeEnum.Open,
|
||||
title: item.name,
|
||||
};
|
||||
|
||||
if ($conf.ifameAppIdArr.includes(item.id)) {
|
||||
options.type = LinkOpenTypeEnum.IFrame;
|
||||
options.isHeader = true;
|
||||
}
|
||||
|
||||
bschLinkPage(options);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-list-wrapper {
|
||||
overflow-y: hidden;
|
||||
height: calc(100% - var(--headerH));
|
||||
padding: 12px 12px;
|
||||
h3.app-type-box {
|
||||
padding: 10px 0 10px 28px;
|
||||
margin: 10px 0 10px;
|
||||
color: #344449;
|
||||
font-variant-numeric: lining-nums tabular-nums;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: 1px;
|
||||
position: relative;
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 10px;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 5px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
background: #5979ea;
|
||||
}
|
||||
}
|
||||
// 首个分类标题去掉顶部 margin:该 10px margin 会塌陷进 vuescroll 的 __view,
|
||||
// 使 scrollHeight 比可视高度大 10px,vuescroll 误判"内容溢出"而强制显示原生滚动条(overflow 为 scroll 而非 auto)
|
||||
h3.app-type-box:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.app-list-box {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
.no-search-box {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: -50%;
|
||||
-webkit-transform: translateY(40%);
|
||||
transform: translateY(40%);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user