feat: 接入 UserHome 前端业务代码(含真实 src/依赖,保留 projman 标准 CI)
deploy-userbase-front / build-and-deploy (push) Failing after 1m54s
build / build (push) Successful in 3m36s

This commit is contained in:
chenxing
2026-09-09 22:37:59 +08:00
parent a53ce07228
commit 4279f99be1
270 changed files with 12479 additions and 25715 deletions
+602
View File
@@ -0,0 +1,602 @@
<template>
<header :class="headerClass">
<img v-if="!!logoUrl" :src="baseUrl + '/image/default-logo.png'" v-real-img="logoUrl" class="logo-box" alt="" />
<ul class="operate-container fix">
<!-- <li>
<Button @click="settingsMenu">test</Button>
</li> -->
<li v-if="isWidgetIconShow" :class="{ active: isWidgetShow }">
<div class="icon-box" v-wave @click="toggleWidgetShow">
<SvgIcon name="header-4" color="#333333" />
</div>
</li>
<li v-if="isSearchIconShow">
<Transition name="fade1" :duration="{ enter: 1000, leave: 0 }">
<div v-show="!isSearching" @click="isSearching = true" class="icon-box" v-wave>
<SvgIcon name="header-1" color="#333333" />
</div>
</Transition>
<Transition name="search" :duration="{ enter: 300, leave: 300 }">
<div v-show="isSearching" class="search-box">
<input ref="refSearchInput" v-model="searchAppText" type="text" placeholder="请输入应用名称搜索" />
<SvgIcon @click="isSearching = false" class="icon" name="header-1" size="20" color="#c4c4c4" />
</div>
</Transition>
</li>
<li>
<Poptip class="fl" popper-class="message-wrap reset-panel" :width="360" trigger="click"
placement="bottom-end" v-model="isMsgShow" :transfer="true">
<div class="icon-box" v-wave>
<SvgIcon name="header-2" color="#333333" />
</div>
<template #content>
<h3 class="title">查看消息</h3>
<vuescroll>
<ul class="msg-ul">
<li class="no-msg-box" v-if="userMsgList.length == 0">暂无消息</li>
<template v-else v-for="(item, index) in userMsgList">
<li class="fix">
<h4 :class="{
'text-ellipsis': true,
read: item.read,
}">
<span class="app-name-box">{{ item.name }}</span>
<span class="content" :title="item.msg"
@click="linkMessage(item.id, item.appPath, item.read)">
{{ item.msg }}
</span>
</h4>
<a href="javascript:;" @click="linkMessage(item.id, item.appPath, item.read)"
title="查看详情">
<SvgIcon name="arrow-right" color="#bbbbbb" />
</a>
</li>
</template>
</ul>
</vuescroll>
<div class="footer fix">
<a href="javascript:;" class="fl link-all" @click="showMsgList">查看全部</a>
<a href="javascript:;" class="fr read-all" v-if="userMsgList.length > 0"
@click="readMsgAll(1)">
全部已读
</a>
<!-- <a class="tzql_r" @click="clearMsg" href="javascript:;" title="清空消息"
><img src="image/msg-clear.png"
/></a> -->
</div>
</template>
</Poptip>
</li>
<li>
<Poptip class="fl" popper-class="roleinfo-wrap reset-panel" :width="240" trigger="click"
placement="bottom-end" v-model="isRoleInfoShow" :transfer="true">
<div class="icon-box" v-wave>
<SvgIcon name="header-3" color="#333333" />
</div>
<template #content>
<h3 class="title">
<SvgIcon name="switch-role" color="#272829" />
<span class="ml-8">切换身份</span>
</h3>
<vuescroll>
<ul class="roles-ul">
<template v-for="(item, index) in userRole">
<li :class="{ acitve: getIsCheckRoles(item) }" @click="changeRole(item)">
<SvgIcon v-if="getIsCheckRoles(item)" color="#45B972" name="role-checked" />
<SvgIcon v-else name="role-check" color="#dcdee0" />
<div class="user-wrap">
<div class="user-box fix">
<span v-if="item.roleId != 3" class="fl f16 mr-10">
{{ store.userInfo.name }}
</span>
<span v-else class="fl f16 mr-10">
{{ item.childName }}
</span>
<span v-if="!!item.roleName" class="role-info">
{{ item.roleName }}
</span>
</div>
<div class="schname-box">
{{ item.schName }}
</div>
</div>
</li>
</template>
</ul>
</vuescroll>
</template>
</Poptip>
</li>
<li>
<Poptip class="fl" popper-class="reset-panel" :width="220" trigger="click" placement="bottom-end"
v-model="isUserInfoShow" :transfer="true">
<div class="icon-box" v-wave>
<SvgIcon name="header-5" color="#333333" />
</div>
<template #content>
<ul class="userinfo-ul">
<!-- <li class="header border" @click="onUserinfoClickRoles">
<span class="name">{{ store.userInfo?.name }}</span>
<span v-if="!!store.curRoleInfo?.roleName" class="role-info mr-10">
{{ store.curRoleInfo?.roleName }}
</span>
<Icon :size="18" color="#333" type="ios-arrow-forward" />
</li> -->
<li class="border" @click="showUserInfo">
<img :src="requireImg('userinfo/icon1.png')" alt="" />
<span>个人信息</span>
</li>
<li v-if="!!store.getIsAdmin" @click="linkSysSetting">
<img :src="requireImg('userinfo/icon5.png')" alt="" />
<span>系统设置</span>
</li>
<li v-if="!!store.getIsAdmin" @click="linkLogoSetting">
<img :src="requireImg('userinfo/icon3.png')" alt="" />
<span>设置LOGO</span>
</li>
<li @click="settingsMenu" v-if="!!store.getIsAdmin">
<img :src="requireImg('userinfo/icon6.png')" alt="" />
<span>边栏设置</span>
</li>
<li @click="settingsStats" v-if="!!store.getIsAdmin">
<img :src="requireImg('userinfo/icon7.png')" alt="" />
<span>统计设置</span>
</li>
<!--<li @click="settingsTeacherCopilot" v-if="!!store.getIsAdmin">
<img :src="requireImg('userinfo/icon8.png')" alt="" />
<span>教师助手设置</span>
</li>-->
<li @click="settingsWallpaper" class="border">
<img :src="requireImg('userinfo/icon2.png')" alt="" />
<span>设置壁纸</span>
</li>
<li @click="logout">
<img :src="requireImg('userinfo/icon4.png')" alt="" />
<span>退出登陆</span>
</li>
</ul>
</template>
</Poptip>
</li>
</ul>
</header>
<Modal v-model="isMsgListShow" width="900" :mask="true" title="消息" class-name="msg-modal" :transfer="false">
<div class="fix mb-10">
<Button type="primary" @click="resetMsgPage" size="small" class="fl ml-10 width-10" ghost>刷新</Button>
<Button type="primary" @click="readMsgAll(2)" size="small" class="fl ml-10 width-10" ghost>全部已读</Button>
</div>
<TablePage :table-height="360" ref="refTablePage" page-type="small" :dataApi="tableApi" :pageParams="pageParams"
:columns="columns" :auto-load="false" />
<template #footer>
<Button type="primary" ghost @click="isMsgListShow = false">关闭</Button>
</template>
</Modal>
<Modal v-model="isWallpaperShow" class-name="wallpaper-modal" width="960" :mask="false" title="设置壁纸"
:transfer="false">
<vuescroll>
<div class="wallpaper-wrap">
<ul class="wallpaper-ul">
<template v-for="(item, index) in wallpaperList">
<li :class="(index + 1) % 6 == 0 ? '' : 'mr-16'" @click="onClickWallpaper(item.url)">
<div class="check-box">
<SvgIcon size="16" v-if="getIsWallpaper(item.url)" color="#45B972"
name="role-checked" />
<SvgIcon size="16" v-else name="role-check" color="#dcdee0" />
</div>
<label v-if="item.type == 0">系统</label>
<label v-if="item.type == 1">校</label>
<ContextMenu v-if="item.isEdit" @select="onWallpaperSelect" :menu="[
{
label: '删除',
id: item.id,
},
]">
<img :src="item.url" alt="" />
</ContextMenu>
<template v-else>
<img :src="item.url" alt="" />
</template>
<div class="mask-box">{{ index + 1 }}</div>
</li>
</template>
<li>
<Upload class="add-wrap tc" :on-success="wallpaperSuccess" :show-upload-list="false"
:action="$conf.serviceHost + '/api/wallpaperUpload'"
accept="image/jpeg, image/jpg, image/png" :with-credentials="true"
:headers="uploadHeaders">
<div class="add-box">
<SvgIcon name="upload-add" color="#939393" style="width: 24px; height: 24px" />
<p>点击上传图片</p>
</div>
</Upload>
</li>
</ul>
</div>
</vuescroll>
<template #footer>
<Button type="primary" ghost @click="isWallpaperShow = false">关闭</Button>
</template>
</Modal>
<Modal v-model="isLogoShow" class-name="logo-modal" width="720" :mask="false" title="设置LOGO" :transfer="false">
<vuescroll>
<div class="wallpaper-wrap">
<ul class="wallpaper-ul">
<template v-for="(item, index) in logoList">
<li :class="(index + 1) % 4 == 0 ? '' : 'mr-16'">
<label v-if="item.type == 0">系统</label>
<label v-else>校</label>
<ContextMenu v-if="item.isEdit" @select="onLogoSelect" :menu="[
{
label: '删除',
id: item.id,
},
]">
<img :src="item.url" alt="" />
</ContextMenu>
<template v-else>
<img :src="item.url" alt="" />
</template>
</li>
</template>
<li>
<Upload class="add-wrap tc" :on-success="logoSuccess" :show-upload-list="false"
:action="$conf.serviceHost + '/api/logoUpload'"
accept="image/jpeg, image/jpg, image/png, image/gif, image/webp"
:with-credentials="true" :headers="uploadHeaders">
<div class="add-box">
<SvgIcon name="upload-add" color="#939393" style="width: 24px; height: 24px" />
<p>点击上传图片</p>
</div>
</Upload>
</li>
</ul>
</div>
</vuescroll>
<template #footer>
<Button type="primary" ghost @click="isLogoShow = false">关闭</Button>
</template>
</Modal>
<MenuBarModal ref="refMenuBarModal" />
<StatsBarModal ref="refStatsBarModal" />
<TeacherCopilotModal ref="refTeacherCopilotModal" />
</template>
<script lang="ts" setup>
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
import SvgIcon from "@/components/SvgIcon.vue";
import vuescroll from "vuescroll";
import { ref, reactive, computed, onMounted, onUnmounted, inject, watch } from "vue";
import { useDefaultStore } from "@/store";
import { useRoute, useRouter } from "vue-router";
import { requireImg } from "@/utils/hook/useImg";
import ContextMenu from "@/components/ContextMenu.vue";
import MenuBarModal from "@/components/MenuBarModal.vue";
import StatsBarModal from "@/components/StatsBarModal.vue";
import TeacherCopilotModal from "@/components/TeacherCopilotModal.vue";
import BaseApi from "@/api/BaseApi";
import TablePage from "@/components/TablePage.vue";
import { IframePageParmas, RoleInfo } from "@/types";
import { isEqual, orderBy } from "lodash";
import { PageParams } from "@/types/Basics";
import UserSettingApi from "@/api/UserSettingApi";
import mittBus from "@/utils/mitt";
import MenuBarApi from "@/api/MenuBarApi";
import Utils from "@/utils";
const route = useRoute();
const router = useRouter();
const store = useDefaultStore();
const refTablePage: any = ref(null);
const { $conf } = useCurrentInstance();
const baseUrl = computed(() => $conf.fileHost);
const refSearchInput = ref<any>();
const refMenuBarModal = ref<any>();
const refStatsBarModal = ref<any>();
const refTeacherCopilotModal = ref<any>();
const uploadHeaders = { yschapirequest: 1, yschreferer: Utils.strToHexCharCode(window.location.href) };
const logoUrl = computed(() => {
const schLogo = logoList.value.find(x => x.type == 1);
if (schLogo) return schLogo.url;
const sysLogo = logoList.value.find(x => x.type == 0);
return sysLogo ? sysLogo.url : "";
});
const isMsgShow = ref(false),
isMsgListShow = ref(false),
isUserInfoShow = ref(false),
isRoleInfoShow = ref(false),
isWallpaperShow = ref(false),
isLogoShow = ref(false),
isSearching = ref(false),
searchAppText = ref<string | null>(null),
wallpaperList = ref<any[]>([]),
logoList = ref<any[]>([]),
userMsgList = ref<any[]>([]);
const headerClass = computed(() => {
return { "default-header": true, "base-mask": route.name !== "index" };
});
const isWidgetIconShow = computed(() => {
return route.name === "index";
});
const isSearchIconShow = computed(() => {
return route.name === "apps";
});
const isWidgetShow = computed(() => {
return store.getWidgetDragObj.isWidgetShow;
});
const userRole = computed(() => store.userRoleList);
const toggleSearch: any = inject("toggleSearch");
const pageParams: PageParams = reactive({ page: 1, count: 10 });
const tableApi = (params: PageParams) => BaseApi.getMessagePageList(params);
const columns = ref<any[]>([
{
type: "index",
title: "序号",
width: 70,
align: "center",
},
{
title: "应用",
width: 150,
tooltip: true,
align: "center",
key: "name",
},
{
title: "消息内容",
tooltip: true,
key: "msg",
},
{
title: "状态",
width: 100,
render(h: any, params: any) {
return h("span", params.row.read ? "已读" : "未读");
},
},
{
title: "日期",
align: "center",
width: 200,
key: "date",
},
]);
onMounted(async () => {
setTimeout(() => {
reloadMsg();
// isWallpaperShow.value = true;
}, 200);
loadLogoList();
mittBus.on("searchReset", () => {
isSearching.value = false;
searchAppText.value = null;
});
});
onUnmounted(() => {
mittBus.off("searchReset");
});
watch(isSearching, val => {
if (val) {
setTimeout(() => refSearchInput.value?.focus(), 300);
} else {
// searchAppText.value = null;
}
});
watch(searchAppText, val => {
mittBus.emit("searchChange", val);
});
function linkMessage(msgId: number, path: string, isread: boolean) {
if (!isread) BaseApi.readMessage(msgId);
var index = userMsgList.value.findIndex(x => x.id == msgId);
if (index !== -1) userMsgList.value[index].read = true;
window.open(path);
}
function getIsCheckRoles(item: RoleInfo) {
return isEqual(item, store.curRoleInfo);
}
async function changeRole(role: RoleInfo) {
if (!!getIsCheckRoles(role)) return;
setTimeout(() => {
isRoleInfoShow.value = false;
}, 150);
store.setCurRoleInfo(role);
await BaseApi.savUserRole(role);
mittBus.emit("switchRole", role);
await getUserWallpaperList();
await loadLogoList();
}
function onUserinfoClickRoles() {
isUserInfoShow.value = false;
setTimeout(() => {
isRoleInfoShow.value = true;
}, 0);
}
async function readMsgAll(type: 1 | 2) {
await BaseApi.readMessageAll();
await reloadMsg();
if (type == 2) resetMsgPage();
}
async function reloadMsg() {
const result = await BaseApi.getMessageList();
userMsgList.value = result.list;
}
function resetMsgPage() {
refTablePage.value.reloadData();
}
function showMsgList() {
isMsgShow.value = false;
isMsgListShow.value = true;
refTablePage.value.reloadData();
}
async function wallpaperSuccess(response: any, file: any, fileListt: any) {
await getUserWallpaperList();
}
async function getUserWallpaperList() {
const list = await UserSettingApi.getUserWallpaperList(store.getCurSchId);
// list.forEach(element => {
// if (element.type == 1) element.url = $conf.serviceHost + element.url;
// });
wallpaperList.value = orderBy(list, ["type", "id"], ["asc", "asc"]);
}
async function settingsWallpaper() {
await getUserWallpaperList();
isUserInfoShow.value = false;
isWallpaperShow.value = true;
}
async function settingsMenu() {
isUserInfoShow.value = false;
refMenuBarModal.value.show();
}
async function settingsStats() {
isUserInfoShow.value = false;
refStatsBarModal.value.show();
}
async function settingsTeacherCopilot() {
isUserInfoShow.value = false;
refTeacherCopilotModal.value.show();
}
async function onClickWallpaper(url: string) {
await UserSettingApi.checkWallpaper(store.getCurSchId, url);
const userSetting = store.curUserSettingInfo;
if (!!userSetting) {
userSetting.wallpaperUrl = url;
store.setCurUserSettingsInfo(userSetting);
}
isWallpaperShow.value = false;
}
async function onWallpaperSelect(item: any) {
await UserSettingApi.deleteWallById(item.id);
await getUserWallpaperList();
}
async function loadLogoList() {
const list = await UserSettingApi.getLogoList();
logoList.value = list || [];
}
async function logoSuccess(response: any, file: any, fileListt: any) {
await loadLogoList();
}
async function onLogoSelect(item: any) {
await UserSettingApi.deleteLogoById(item.id);
await loadLogoList();
}
function getIsWallpaper(url: string) {
return url === store.getCurWallPaperUrl;
}
async function toggleWidgetShow() {
const id = store.curUserSettingInfo?.id;
if (!!store.curUserSettingInfo) store.curUserSettingInfo.isWidgetShow = !store.getWidgetDragObj.isWidgetShow;
await UserSettingApi.toggleWidgetShow(id);
}
function linkSysSetting() {
isUserInfoShow.value = false;
router.push("/syssetting");
}
async function linkLogoSetting() {
await loadLogoList();
isUserInfoShow.value = false;
isLogoShow.value = true;
}
function showSeachBox() { }
function showUserInfo() {
isUserInfoShow.value = false;
mittBus.emit("showUserInfoModal");
}
function logout() {
let url = window.location.href;
url = "/cas/logout?service=" + url;
url = $conf.ssoHost + url;
if (!!window.top) window.top.location.href = url;
else window.location.href = url;
}
</script>
<style lang="scss" scoped>
@import url("@/assets/styles/header.scss");
::v-deep .ivu-table td {
height: 32px;
}
</style>
<style lang="scss">
.reset-panel .ivu-poptip-body {
padding: 0;
}
.reset-panel .ivu-poptip-inner {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.06);
}
.wallpaper-modal {
z-index: 11 !important;
}
.wallpaper-modal .ivu-modal-body {
padding: 0;
}
.logo-modal {
z-index: 11 !important;
}
.logo-modal .ivu-modal-body {
padding: 0;
}
.ivu-table-cell .operating-link {
display: inline-block;
color: #2d8cf0;
cursor: pointer;
padding-right: 10px;
&.disabled {
color: #aaa !important;
cursor: not-allowed;
}
}
.ivu-table:before {
content: none !important;
}
</style>