This commit is contained in:
zhouhefeng
2026-05-28 18:31:36 +08:00
parent daac940f3f
commit 7ee1c4fbb5
112 changed files with 9837 additions and 273 deletions
@@ -56,6 +56,7 @@
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
<script src="${base!}/assets/platform/js/util/coordinateUtil.js"></script>
<script src="${base!}/assets/platform/js/util/voiceMenuNavigator.js"></script>
<script src="${base!}/assets/platform/js/main.js"></script>
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
<script src="${base!}/assets/platform/js/tool/businessTool.js"></script>
@@ -167,10 +168,20 @@
func && typeof func === "function" && func()
}
function returnH5Home(activeTab = "home") {
if (window.store && typeof window.store.commit === "function") {
window.store.commit("setActiveTarBar", activeTab || "home")
}
window.location.replace("/platform/h5/home")
}
Vue.mixin({
methods: {
historyBack: function(func = ()=>{}) {
historyBack(func);
},
returnH5Home: function(activeTab = "home") {
returnH5Home(activeTab)
}
},
created() {
@@ -397,19 +408,49 @@
</script>
<script nonce="${cspNonce!}">
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + new Date().getTime()))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + new Date().getTime()))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + new Date().getTime()))
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + new Date().getTime()))
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + new Date().getTime()))
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + new Date().getTime()))
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + new Date().getTime()))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + new Date().getTime()))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + new Date().getTime()))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + new Date().getTime()))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + new Date().getTime()))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + new Date().getTime()))
const h5ComponentVersion = "20260524"
Vue.component("rich-text", httpVueLoader("/components/plugins/sysRichTextView/index.vue?v=" + h5ComponentVersion))
Vue.component("h5-file-upload", httpVueLoader("/components/plugins/sysUpload/h5Index.vue?v=" + h5ComponentVersion))
Vue.component("h5-signature", httpVueLoader("/components/plugins/sysSignature/h5Index.vue?v=" + h5ComponentVersion))
Vue.component("text-editor", httpVueLoader("/components/plugins/sysTextEditor/index.vue?v=" + h5ComponentVersion))
Vue.component("dict-tag", httpVueLoader("/components/plugins/sysDict/DictTag.vue?v=" + h5ComponentVersion))
Vue.component("svg-icon", httpVueLoader("/components/plugins/sysSvgIcon/index.vue?v=" + h5ComponentVersion))
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + h5ComponentVersion))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + h5ComponentVersion))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + h5ComponentVersion))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + h5ComponentVersion))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + h5ComponentVersion))
</script>
<style>
.h5-voice-menu {
position: fixed;
right: 16px;
bottom: calc(76px + env(safe-area-inset-bottom));
z-index: 21000;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
color: #fff;
background: #1989fa;
box-shadow: 0 6px 18px rgba(25, 137, 250, 0.35);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
outline: none;
}
.h5-voice-menu.is-listening {
background: #ff976a;
box-shadow: 0 6px 18px rgba(255, 151, 106, 0.38);
}
.h5-voice-menu .voice-menu-text {
display: none;
}
</style>
</head>
<body>
<div class="main-page page-showtabbar">
@@ -417,6 +458,10 @@
<div id="container">${layoutContent}</div>
</div>
</div>
<button type="button" class="h5-voice-menu" id="voice-menu-btn" data-platform="H5" title="语音打开菜单">
<i class="fa fa-microphone"></i>
<span class="voice-menu-text">语音</span>
</button>
<script nonce="${cspNonce!}">
toggleShowBar()
</script>
@@ -0,0 +1,47 @@
<!doctype html>
<html lang="${lang,escape}">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>数智工会领导驾驶舱</title>
<link rel="stylesheet" href="${base!}/assets/platform/css/root.css?v=20260524"/>
<script src="${base!}/assets/platform/plugins/vue/vue.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js?v=20260524"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js?v=20260524"></script>
<script nonce="${cspNonce!}">
Vue.config.devtools = false
const store = {
state: {
user: JSON.parse(window.sessionStorage.getItem("user") || "null") || {permissions: [], roles: []}
}
}
window.ELEMENT = window.ELEMENT || {
Message: {
error(message) {
console.error(message || "操作失败")
}
},
Loading: {
service() {
return {
close() {}
}
}
}
}
Vue.prototype.$commonUtil = commonUtil
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
</script>
</head>
<body>
${layoutContent!}
</body>
</html>
@@ -0,0 +1,164 @@
<!doctype html>
<html lang="${lang,escape}">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
<title>智慧工会</title>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/index.css?v=20260524"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=20260524"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css?v=20260524"/>
<script src="${base!}/assets/platform/plugins/vue/vue.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/vant/index.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js?v=20260524"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js?v=20260524"></script>
<script nonce="${cspNonce!}">
Vue.config.devtools = true
if (vant.Form && vant.Form.props && vant.Form.props.showErrorMessage) {
vant.Form.props.showErrorMessage.default = false
}
if (vant.ActionSheet && vant.ActionSheet.props && vant.ActionSheet.props.lazyRender) {
vant.ActionSheet.props.lazyRender.default = false
}
vant.Toast.allowMultiple()
if (vant.Lazyload) {
Vue.use(vant.Lazyload)
}
window.loadScriptOnce = function(src) {
window.__scriptLoadingMap = window.__scriptLoadingMap || {}
if (window.__scriptLoadingMap[src]) {
return window.__scriptLoadingMap[src]
}
window.__scriptLoadingMap[src] = new Promise(function(resolve, reject) {
const existing = document.querySelector('script[src="' + src + '"]')
if (existing) {
resolve()
return
}
const script = document.createElement("script")
script.src = src
script.onload = function() {
resolve()
}
script.onerror = function() {
reject(new Error("script load failed: " + src))
}
document.head.appendChild(script)
})
return window.__scriptLoadingMap[src]
}
window.loadStyleOnce = function(href) {
window.__styleLoadingMap = window.__styleLoadingMap || {}
if (window.__styleLoadingMap[href]) {
return window.__styleLoadingMap[href]
}
window.__styleLoadingMap[href] = new Promise(function(resolve, reject) {
const existing = document.querySelector('link[href="' + href + '"]')
if (existing) {
resolve()
return
}
const link = document.createElement("link")
link.rel = "stylesheet"
link.href = href
link.onload = function() {
resolve()
}
link.onerror = function() {
reject(new Error("style load failed: " + href))
}
document.head.appendChild(link)
})
return window.__styleLoadingMap[href]
}
const store = {
state: {
user: JSON.parse(window.sessionStorage.getItem("user") || "null"),
room: "${@auth.getPrincipalProperty('loginname')}:${@auth.getSessionId()}",
activeTarBar: null
},
commit(type, payload) {
if (type === "setUser") {
this.state.user = payload
} else if (type === "setActiveTarBar") {
this.state.activeTarBar = payload
}
},
dispatch(type) {
if (type === "logout") {
window.localStorage.removeItem("zhgh-h5-vuex")
window.location.href = "/platform/login/logout"
}
}
}
window.ELEMENT = window.ELEMENT || {
Message: {
error(message) {
vant.Toast.fail(message || "操作失败")
}
},
Loading: {
service() {
const loading = vant.Toast.loading({
duration: 0,
forbidClick: true,
message: "处理中,请稍候"
})
return {
close() {
loading.clear()
}
}
}
}
}
function historyBack(func) {
if (window.history.length > 1) {
window.history.back()
} else {
window.location.href = "/platform/h5/home"
}
func && typeof func === "function" && func()
}
function returnH5Home(activeTab = "home") {
store.commit("setActiveTarBar", activeTab || "home")
window.location.replace("/platform/h5/home")
}
Vue.mixin({
methods: {
historyBack: function(func = function(){}) {
historyBack(func)
},
returnH5Home: function(activeTab = "home") {
returnH5Home(activeTab)
}
}
})
Vue.prototype.$commonUtil = commonUtil
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService.bind(commonUtil)
</script>
</head>
<body>
<div class="main-page">
<div class="page-wrapper">
<div id="container">${layoutContent}</div>
</div>
</div>
</body>
</html>
@@ -709,7 +709,7 @@
</div>
<div class="v4-user-section">
<button type="button" class="v4-voice-menu" id="voice-menu-btn" title="语音打开菜单">
<button type="button" class="v4-voice-menu" id="voice-menu-btn" data-platform="PC" title="语音打开菜单">
<i class="fa fa-microphone"></i>
<span class="voice-menu-text">语音</span>
</button>
@@ -1513,7 +1513,7 @@ layout("/layouts/v4/baseLayout.html"){
<div class="oa-hero-section">
<!-- 背景图 -->
<div class="oa-background-image">
<img src="${config.AppHomeImg!}" alt=""/>
<img :src="homeBannerList[0] || ''" alt=""/>
</div>
<!-- 搜索和统计区域 -->
@@ -2020,6 +2020,7 @@ layout("/layouts/v4/baseLayout.html"){
recommendServices: [],
favoriteItems: [],
recommendApps: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item),
// 工会网站新闻
websiteNews: {},
@@ -73,7 +73,7 @@ layout("/layouts/v4/baseLayout.html"){
<!-- 首页banner -->
<div class="section-banner">
<div class="banner-img">
<img src="${config.AppHomeImg!}" alt=""/>
<img :src="homeBannerList[0] || ''" alt=""/>
<stats></stats>
</div>
</div>
@@ -87,6 +87,8 @@ layout("/layouts/v4/baseLayout.html"){
</div>
</div>
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
<jcdt :list="websiteNews"></jcdt>
</div>
@@ -94,6 +96,7 @@ layout("/layouts/v4/baseLayout.html"){
<script nonce="${cspNonce!}">
<!--#include("act.js"){}#-->
<!--#include("template.js"){}#-->
<!--#include("jcdt.js"){}#-->
<!--#include("entry.js"){}#-->
<!--#include("user.js"){}#-->
@@ -104,11 +107,13 @@ layout("/layouts/v4/baseLayout.html"){
el: '#v4-home-app',
data(){
return{
websiteNews: []
websiteNews: [],
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
}
},
components: {
'act': act,
'work-template': workTemplate,
'jcdt': jcdt,
'entry': entry,
'user': user,
@@ -0,0 +1,206 @@
const workTemplate = {
template: /*language=HTML*/ `
<div class="section-wrapper work-template-wrapper">
<div class="work-template-header">
<div class="work-template-title">
<i class="fa fa-clipboard"></i>
<span>工作模板</span>
<em>/Templates</em>
</div>
</div>
<div class="work-template-card">
<div v-if="templateList && templateList.length > 0" class="work-template-grid">
<div class="work-template-item"
v-for="template in templateList"
:key="template.id"
@click="go(template)">
<div class="work-template-icon">
<img :src="template.templateIcon || template.cover || 'https://www.ncu.edu.cn/__local/8/52/2C/644986B4C9A030F7B65300178A6_8679CB08_18467.jpg'"
alt="">
</div>
<div class="work-template-name">{{ template.templateName || template.name }}</div>
</div>
</div>
<div v-else class="no-activity-placeholder">
<div class="icon"><i class="fa fa-clipboard"></i></div>
<p>暂无工作模板</p>
<p class="subtext">敬请关注后续更新</p>
</div>
</div>
</div>
`,
data() {
return {
templateList: []
}
},
methods: {
listTemplate() {
this.$axios.post("/platform/home/listHomeTemplate").then((res) => {
if (res.code === 0) {
this.templateList = res.data
}
})
},
go(template) {
if (!template.url) {
this.$message.warning("管理员未配置模板链接")
return
}
const url = new URL(template.url, window.location.origin)
if (!url.searchParams.get("mode")) {
url.searchParams.set("mode", "edit")
}
if (!url.searchParams.get("id") && template.id) {
url.searchParams.set("id", template.id)
}
window.localStorage.setItem("zhgh_home_template_edit", JSON.stringify({
path: url.pathname,
id: url.searchParams.get("id"),
expiresAt: Date.now() + 60000
}))
window.open(url.href)
}
},
mounted() {
this.listTemplate()
},
style: /*language=CSS*/ `
.work-template-wrapper {
width: 80%;
margin: 20px auto 28px;
}
.work-template-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.work-template-title {
display: inline-flex;
align-items: baseline;
gap: 6px;
color: #19324d;
font-size: 18px;
font-weight: 700;
}
.work-template-title i {
color: #409eff;
font-size: 16px;
}
.work-template-title em {
color: #b5c0d6;
font-size: 14px;
font-style: normal;
font-weight: 500;
}
.work-template-card {
background: #ffffff;
border: 1px solid #edf1f7;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
min-height: 116px;
padding: 18px 20px;
}
.work-template-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
justify-items: center;
justify-content: start;
gap: 12px 18px;
overflow-x: hidden;
}
.work-template-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 8px;
background: #fff;
width: 100%;
max-width: 100%;
padding: 6px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.work-template-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.work-template-icon {
width: 46px;
height: 46px;
margin-bottom: 8px;
}
.work-template-icon img {
width: 100%;
height: 100%;
object-fit: contain;
}
.work-template-name {
min-height: 34px;
font-size: 12px;
color: #333;
line-height: 1.4;
word-break: break-word;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: #94a3b8;
text-align: center;
}
.work-template-wrapper .no-activity-placeholder .icon {
font-size: 44px;
margin-bottom: 12px;
opacity: 0.6;
}
.work-template-wrapper .no-activity-placeholder p {
margin: 4px 0 0;
font-size: 16px;
font-weight: 500;
}
.work-template-wrapper .no-activity-placeholder .subtext {
font-size: 13px;
opacity: 0.8;
}
@media (max-width: 768px) {
.work-template-grid {
grid-template-columns: repeat(3, minmax(72px, 1fr));
gap: 12px;
}
.work-template-item {
padding: 8px 4px;
}
}
@media (max-width: 480px) {
.work-template-grid {
grid-template-columns: repeat(2, minmax(72px, 1fr));
gap: 12px;
}
}
`
};