first commit

This commit is contained in:
2026-01-08 14:58:16 +08:00
commit 556e5d64aa
9575 changed files with 1621095 additions and 0 deletions
@@ -0,0 +1,595 @@
const apps = {
template: /*language=HTML*/ `
<div class="apps-container">
<van-nav-bar title="应用中心" placeholder fixed></van-nav-bar>
<!-- 搜索栏 -->
<van-sticky offset-top="46px">
<van-search
v-model="searchKeyword"
placeholder="搜索应用"
show-action
@search="onSearch"
@cancel="onCancel"
></van-search>
</van-sticky>
<div class="custom-tree-select">
<!-- 左侧导航 -->
<div class="tree-nav">
<div
v-for="(category, index) in allCategories"
:key="category.id"
:class="['nav-item', { active: activeTab === index }]"
@click="onClickNav(index)"
>
<div class="nav-icon">
<!--<i v-if="category.icon" :class="category.icon"></i>
<i v-else class="fa fa-book"></i>-->
<img v-if="category.picIcon" :src="category.picIcon" :alt="category.name || ''" style="width: 18px"/>
<i v-else :class="category.icon || 'fa fa-th-large'"></i>
</div>
<div class="nav-text">{{ category.name }}</div>
<div class="nav-indicator" v-if="activeTab === index"></div>
</div>
</div>
<!-- 右侧内容 -->
<div class="tree-content">
<!-- 应用列表 -->
<div :class="['app-grid', { 'app-grid-single': applications.length === 1 }]"
v-if="applications.length > 0">
<div v-for="app in applications"
:key="app.id"
class="app-grid-item"
:class="{ 'is-favorite': app.isFavorite }"
@click="openApp(app)"
@touchstart="onTouchStart(app)"
@touchend="onTouchEnd"
@touchmove="onTouchMove">
<!-- 图标 -->
<div class="app-grid-icon">
<van-icon v-if="app.picIcon" :name="app.picIcon" size="45"></van-icon>
<van-icon v-else-if="app.icon" :name="app.icon" size="28"></van-icon>
<van-icon v-else name="apps-o" size="28"></van-icon>
</div>
<!-- 名称 -->
<div class="app-grid-name">{{ app.name }}</div>
</div>
</div>
<!-- 空状态 -->
<van-empty v-else description="暂无应用"></van-empty>
</div>
</div>
<!-- 应用菜单弹框 -->
<van-popup v-model="showMenuPopup" position="bottom" :style="{ width: '100%', maxHeight: '60%' }" round>
<div class="menu-popup">
<div class="popup-header">
<div class="popup-title">{{ currentApp.name }}</div>
<van-icon name="cross" @click="showMenuPopup = false" class="close-icon"></van-icon>
</div>
<div class="menu-grid">
<div
v-for="menu in currentMenus"
:key="menu.id"
class="grid-item"
@click="navigateToMenu(menu)"
>
<img v-if="menu.picIcon" :src="menu.picIcon" :alt="menu.name || ''" style="width: 48px" class="grid-img"/>
<div v-else class="grid-icon">
<van-icon :name="menu.icon || 'apps-o'" size="24"></van-icon>
</div>
<div class="grid-name">{{ menu.name }}</div>
<div class="grid-desc" v-if="menu.aliasName">{{ menu.aliasName }}</div>
</div>
</div>
</div>
</van-popup>
</div>
`,
data() {
return {
// 应用列表
applications: [],
// 固定分类列表
categories: [
{id: "all", name: "全部应用", icon: "fa fa-th-large"},
{id: "favorites", name: "我的收藏", icon: "fa fa-star-o"},
{id: "recommended", name: "推荐应用", icon: "fa fa-thumbs-o-up"}
],
// 动态加载的分类
dynamicCategories: [],
// 当前选中的分类索引
activeTab: 0,
// 搜索关键词
searchKeyword: "",
// 列表相关
loading: true,
// 弹框相关
showMenuPopup: false,
currentApp: {},
currentMenus: [],
touchTimer: null,
isTouchMoved: false,
}
},
computed: {
// 所有分类(固定分类 + 动态分类)
allCategories() {
return [...this.categories, ...this.dynamicCategories]
},
// 当前选中的分类ID
currentCategoryId() {
return this.allCategories[this.activeTab]?.id || "all"
}
},
mounted() {
// 页面加载时获取分类和应用数据
this.loadCategories()
this.loadApps()
},
methods: {
// 加载分类数据
loadCategories() {
this.$axios.post("/platform/v4/apps/categories", {platform: "H5"}).then((res) => {
if (res.code === 0) {
this.dynamicCategories = res.data || []
} else {
console.error("获取应用分类失败:", res.msg)
}
})
},
// 加载应用数据
loadApps() {
this.loading = true
// 构建请求参数
const params = {
categoryId:
this.currentCategoryId === "all"
? ""
: this.currentCategoryId,
keyword: this.searchKeyword,
platform: "H5"
}
// 将参数转换为URL查询参数
const queryString = Object.keys(params)
.filter((key) => params[key] !== null && params[key] !== undefined && params[key] !== "")
.map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
.join("&")
// 如果是"我的收藏"分类
if (this.currentCategoryId === "favorites") {
this.loadFavorites()
return
}
// 如果是"推荐应用"分类
if (this.currentCategoryId === "recommended") {
this.$axios.post("/platform/home/listRecommendApp", { platform: "H5" }).then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取推荐应用失败:", result.msg)
}
this.loading = false
})
return
}
// 发送请求获取普通应用列表
this.$axios.post("/platform/v4/apps/list?" + queryString)
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取应用列表失败:", result.msg)
}
this.loading = false
})
},
// 加载收藏的应用
loadFavorites() {
this.$axios.post("/platform/v4/apps/favorite", {platform: 'H5'})
.then((result) => {
if (result.code === 0) {
this.applications = result.data || []
} else {
console.error("获取收藏应用失败:", result.msg)
}
this.loading = false
})
},
// 切换应用收藏状态
toggleFavorite(appId) {
const app = this.applications.find((a) => a.id === appId)
if (!app) return
const url = app.isFavorite ? "/platform/v4/apps/removeFavorite" : "/platform/v4/apps/addFavorite"
const params = {appId: appId}
$.post(url, params)
.then((result) => {
if (result.code === 0) {
app.isFavorite = !app.isFavorite
this.$toast(app.isFavorite ? ('收藏成功:' + app.name) : ('取消收藏:' + app.name))
} else {
console.error(app.isFavorite ? ('取消收藏:' + app.name) : ('收藏失败:' + app.name), result.msg)
}
})
.fail((error) => {
console.error(app.isFavorite ? ('取消收藏:' + app.name) : ('收藏异常:' + app.name), error)
})
},
// 长按开始
onTouchStart(app) {
this.isTouchMoved = false
this.touchTimer = setTimeout(() => {
this.toggleFavorite(app.id)
}, 800) // 长按 800ms 触发
},
// 触摸移动(判断是否滑动,避免误触长按)
onTouchMove() {
this.isTouchMoved = true
},
// 触摸结束(清除计时器)
onTouchEnd() {
if (this.touchTimer) {
clearTimeout(this.touchTimer)
this.touchTimer = null
}
},
// 搜索
onSearch() {
this.applications = []
this.loadApps()
},
// 取消搜索
onCancel() {
this.searchKeyword = ""
this.onSearch()
},
// 切换标签页 - TreeSelect导航点击事件
onClickNav(index) {
this.activeTab = index
this.applications = []
this.loadApps()
},
// 打开应用
openApp(app) {
this.$axios.post("/platform/sys/user/subAppMenus", {appId: app.id}).then((res) => {
if (res.code === 0) {
this.showAppMenus(app, res.data)
}
})
},
// 显示应用菜单弹框
showAppMenus(app, menus) {
if(app.href && menus.length === 0){
this.$pjaxReplace(app.href)
return;
}
if (!menus || menus.length === 0) {
this.$toast('该应用暂无可用功能')
return
}
this.currentApp = app
this.currentMenus = menus
this.showMenuPopup = true
},
// 导航到菜单
navigateToMenu(menu) {
// 关闭弹框
// this.showMenuPopup = false
if (menu.href) {
if(menu.href.startsWith('http://') || menu.href.startsWith('https://')){
window.open(menu.href, '_blank')
}else{
this.$pjaxReplace(menu.href)
}
} else {
this.$toast('该功能暂不可用')
}
}
},
// 初始加载消除长按收藏
beforeDestroy() {
if (this.touchTimer) {
clearTimeout(this.touchTimer)
}
},
style: /*language=CSS*/ `
.apps-container {
display: flex;
flex-direction: column;
height: 100%;
}
/* 自定义树形选择组件样式 */
.custom-tree-select {
display: flex;
height: calc(100vh - 150px);
background: #fff;
}
.tree-nav {
flex: 0 0 90px;
background: #f7f8fa;
border-right: 1px solid #ebedf0;
overflow-y: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.tree-nav::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
.nav-item {
position: relative;
padding: 12px 8px;
cursor: pointer;
transition: all 0.2s ease;
border-bottom: 1px solid #ebedf0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.nav-item:hover {
background: #f2f3f5;
}
.nav-item.active {
background: #fff;
color: var(--color-primary);
}
.nav-item.active::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: #1989fa;
}
.nav-icon {
font-size: 18px;
margin-bottom: 6px;
color: inherit;
}
.nav-text {
font-size: 12px;
font-weight: 500;
text-align: center;
line-height: 1.2;
word-break: break-all;
}
.nav-indicator {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 6px solid #1989fa;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
}
.tree-content {
flex: 1;
overflow-y: auto;
background: #fff;
}
.app-grid {
display: grid;
/* 改为自适应列数:每格最小 80px,自动换行 */
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
justify-items: center; /* 子项内容居中(图标和文字) */
justify-content: start; /* 整体网格靠左对齐,避免居中 */
gap: 12px;
padding: 12px;
background: #fff;
overflow-x: hidden;
}
.app-grid-single {
display: flex;
justify-content: start;
padding: 16px 0;
}
.app-grid-single .app-grid-item {
max-width: 80px;
margin: 0 12px;
}
.app-grid-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 4px;
border-radius: 8px;
background: #fff;
width: 100%;
max-width: 100%;
}
.app-grid-icon {
width: 45px;
height: 45px;
max-width: 45px;
max-height: 45px;
/*border-radius: 12px;
background: #f2f3f5;*/
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6px;
overflow: hidden;
/*border: 2px solid transparent;*/
transition: all 0.2s ease;
}
.app-grid-name {
width: 100%;
font-size: 14px;
color: #323233;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.app-grid-item.is-favorite .app-grid-icon,
.app-grid-icon.is-favorite {
border-color: #ff9800;
box-shadow: 0 0 0 2px rgba(255, 152, 0, 0.2);
}
/* 菜单弹框样式 */
/deep/ .menu-popup {
height: 100%;
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
}
/deep/ .popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #ebedf0;
background: #fff;
}
/deep/ .popup-title {
font-size: 16px;
font-weight: 500;
color: #323233;
}
/deep/ .close-icon {
font-size: 18px;
color: #969799;
cursor: pointer;
}
/deep/ .menu-grid {
flex: 1;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
/deep/ .grid-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 12px;
background: #fff;
cursor: pointer;
transition: all 0.2s ease;
min-width: 0;
}
/deep/ .grid-item:active {
transform: scale(0.95);
background: #f2f3f5;
}
/deep/ .grid-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
color: #fff;
}
/deep/ .grid-img {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
color: #fff;
}
/deep/ .grid-name {
font-size: 14px;
font-weight: 500;
color: #323233;
text-align: center;
margin-bottom: 4px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
min-width: 0;
}
/deep/ .grid-desc {
font-size: 12px;
color: #969799;
text-align: center;
line-height: 1.2;
}
/deep/ .menu-name {
font-size: 16px;
color: #323233;
margin-bottom: 4px;
}
/deep/ .menu-desc {
font-size: 12px;
color: #969799;
}
/deep/ .menu-arrow {
font-size: 16px;
color: #c8c9cc;
}
`
}
@@ -0,0 +1,203 @@
const home = {
template: /*language=HTML*/ `
<div>
<van-nav-bar title="首页" class="nav-bar" placeholder fixed></van-nav-bar>
<van-swipe :autoplay="100000" :height="200" class="home__swipe">
<van-swipe-item>
<img src="/assets/mobile/img/home/home1.png" alt="">
</van-swipe-item>
<van-swipe-item>
<img src="/assets/mobile/img/home/home2.png" alt="">
</van-swipe-item>
<van-swipe-item>
<img src="/assets/mobile/img/home/home3.png" alt="">
</van-swipe-item>
</van-swipe>
<!--快捷入口-->
<div class="quick-grid">
<div class="section-title">推荐服务</div>
<van-grid :column-num="4" icon-size="46" square clickable :border="false">
<van-grid-item v-for="item in quickEntries"
@click="menuClick(item)"
:key="item.id"
:text="item.name">
<img :src="item.picIcon"
slot="icon"
style="width: 40px; height: 40px"/>
</van-grid-item>
</van-grid>
</div>
<!--活动列表-->
<div class="act-list">
<div class="section-title">最新活动</div>
<template v-if="activityOptions && activityOptions.length > 0">
<div v-for="item in activityOptions" :key="item.id" class="act-item" @click="enterAct(item)">
<van-tag class="act-item-wrapper-tag" v-if="$moment().unix() > $moment(item.endDate).unix()">
已结束
</van-tag>
<van-tag class="act-item-wrapper-tag" type="success"
v-else-if="$moment().unix() > $moment(item.startDate).unix() && $moment().unix() < $moment(item.endDate).unix()">
进行中
</van-tag>
<van-tag class="act-item-wrapper-tag" type="success" v-else-if="$moment().unix() < $moment(item.startDate).unix()">
即将开始
</van-tag>
<div class="act-item-cover">
<van-image v-if="item.cover" width="120" height="84" :src="item.cover"></van-image>
</div>
<div class="act-item-wrapper">
<div class="content-title">
{{item.name}}
</div>
<div class="content-text">
<div>开始时间:{{$moment(item.startDate).format('MM-DD HH:mm')}}</div>
<div class="mt5">结束时间:{{$moment(item.endDate).format('MM-DD HH:mm')}}</div>
</div>
</div>
</div>
</template>
<van-empty v-if="!activityOptions || activityOptions.length === 0" description="暂无最新活动"></van-empty>
</div>
</div>
`,
data() {
return {
activityOptions: [],
quickEntries: []
}
},
methods: {
listRecommendApp() {
this.$axios.post("/platform/home/listRecommendService", { platform: "H5" }).then((res) => {
if (res.code === 0) {
this.quickEntries = res.data
}
})
},
listActivity() {
this.$axios.post("/platform/home/listHomeActivity").then((resp) => {
if (resp.code === 0) {
this.activityOptions = resp.data
}
})
},
menuClick(item) {
this.$pjaxReplace(item.href)
},
//点击进入活动
enterAct(item) {
if(this.$moment().unix() < this.$moment(item.startDate).unix()) {
this.$toast('活动即将开始')
return
}
this.$pjaxReplace(item.h5Url)
}
},
created() {
this.listRecommendApp()
this.listActivity()
},
style: /*language=CSS*/ `
.home__swipe .van-swipe-item img {
width: 100%;
height: 100%;
}
.quick-grid {
background-color: #fff;
margin: 10px;
border-radius: 10px;
padding: 10px;
}
.quick-grid .van-grid-item .van-grid-item__text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 85%;
text-align: center;
flex-shrink: 0;
}
.act-list {
background-color: #fff;
margin: 10px;
border-radius: 10px;
padding: 10px;
position: relative;
}
.act-list .act-item {
width: 100%;
padding: 6px;
background-color: #fff;
display: flex;
position: relative;
}
.act-list .act-item-cover {
margin: 0 14px 0 0;
width: 120px;
height: 84px;
flex-shrink: 0;
}
.act-list .act-item-cover .van-image{
/*width: 120px;*/
/*height: 84px;*/
width: 100%;
height: 100%;
}
.act-list .act-item-wrapper{
display: flex;
flex-wrap: wrap;
align-content: space-between;
width: 100%;
}
.act-list /deep/ .act-item-wrapper-tag{
position: absolute;
left: 10px;
top: 10px;
z-index: 1;
}
.act-item-wrapper .content-title{
font-size: 14px;
width: 100%;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.act-item-wrapper .content-text{
font-size: 12px;
color: grey;
padding-bottom: 6px;
}
.section-title{
padding: 0 0 5px 5px;
font-size: 15px;
}
/deep/ .home__swipe img {
width: 100%;
height: 100%;
}
/deep/ .act-item-cover img {
border-radius: 8px;
}
`
}
@@ -0,0 +1,82 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.profile-card{
background: #0a84ff;
color: #0b5a2c;
}
</style>
<div id="app" v-cloak>
<div>
<div>
<home v-if="homeTabbarActive === 'home'" @module-click="moduleClick"></home>
<todo v-if="homeTabbarActive === 'todo'"></todo>
<work v-if="homeTabbarActive === 'work'"></work>
<mine v-if="homeTabbarActive === 'mine'"></mine>
<apps v-if="homeTabbarActive === 'apps'"></apps>
<msg v-if="homeTabbarActive === 'msg'"></msg>
</div>
<div id="page-tarbar" class="page-tarbar">
<van-tabbar v-model="homeTabbarActive" @change="homeTabbarChange">
<van-tabbar-item name="home" icon="wap-home-o">首页</van-tabbar-item>
<van-tabbar-item name="apps" icon="apps-o">应用</van-tabbar-item>
<!-- <van-tabbar-item name="work" icon="gem">工作台</van-tabbar-item>-->
<van-tabbar-item name="todo" icon="records-o">待办</van-tabbar-item>
<van-tabbar-item name="msg" icon="envelop-o">消息</van-tabbar-item>
<van-tabbar-item name="mine" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include("home.js"){}#-->
<!--#include("work.js"){}#-->
<!--#include("mine.js"){}#-->
<!--#include("todo.js"){}#-->
<!--#include("apps.js"){}#-->
<!--#include("msg.js"){}#-->
new Vue({
el: "#app",
store,
components: {
home,
mine,
work,
todo,
apps,
msg
},
data() {
return {
homeTabbarActive: "home"
}
},
methods: {
homeTabbarChange(val) {
this.$store.commit("setActiveTarBar", val)
},
moduleClick(val) {
console.log(val)
this.homeTabbarActive = "work"
}
},
created() {
if (!this.$store.state.activeTarBar) {
this.$store.commit("setActiveTarBar", "home")
} else {
this.homeTabbarActive = this.$store.state.activeTarBar
}
},
style: /*language=CSS*/ `
`
})
</script>
<!--#
}
#-->
@@ -0,0 +1,376 @@
const mine = {
template: /*language=HTML*/ `
<div class="home-mine">
<van-nav-bar title="个人中心" class="nav-bar"></van-nav-bar>
<div class="profile-card">
<div class="profile-header">
<div class="avatar-section">
<div class="avatar-wrapper">
<van-image :src="avatar" round width="80" height="80" class="avatar">
<van-loading slot="loading" type="spinner" size="20"></van-loading>
</van-image>
<div class="status-badge">
<div class="status-dot"></div>
<span>{{ $store.state.user.userState }}</span>
</div>
</div>
</div>
<div class="basic-info">
<div class="name-row">
<h2 class="username">{{ $store.state.user.username }}</h2>
<div class="gender-tag">
<van-icon name="friends-o" class="gender-icon"></van-icon>
<span>{{ $store.state.user.sex }}</span>
</div>
</div>
<div class="employee-id">
工号:
<span class="id-value">{{ $store.state.user.loginname }}</span>
</div>
</div>
</div>
<div class="info-list">
<!-- <div class="info-item">
<van-icon name="user-o"></van-icon>
<span class="item-label">教职工类别</span>
<span class="info-value">{{ $store.state.user.personType }}</span>
</div>-->
<div class="info-item">
<van-icon name="user-o"></van-icon>
<span class="item-label">在职状态</span>
<span class="info-value">{{ $store.state.user.userState }}</span>
</div>
<div class="info-item">
<van-icon name="user-o"></van-icon>
<span class="item-label">编制类别</span>
<span class="info-value">{{ $store.state.user.preparedBy }}</span>
</div>
<div class="info-item">
<van-icon name="wap-home-o"></van-icon>
<span class="item-label">所属单位</span>
<span class="info-value">{{ $store.state.user.unit.name }}</span>
</div>
<div class="info-item">
<van-icon name="wap-home-o"></van-icon>
<span class="item-label">所属工会</span>
<span class="info-value">{{ $store.state.user.union.name }}</span>
</div>
</div>
</div>
<div class="action-cards">
<van-cell-group>
<van-cell is-link @click="$pjaxReplace('/platform/h5/indexMine/userInfo')" class="action-cell">
<span class="cell-title" slot="title">
<van-icon name="user-o" class="cell-icon"></van-icon>
个人信息
</span>
</van-cell>
<van-cell is-link @click="$pjaxReplace('/platform/signature/h5')" class="action-cell">
<span class="cell-title" slot="title">
<van-icon name="edit" class="cell-icon"></van-icon>
我的签名
</span>
</van-cell>
<!-- <van-cell :value="$store.state.user.totalIntegral" is-link @click="$pjaxReplace('/platform/integral/records/h5')" class="action-cell">-->
<!-- <span class="cell-title" slot="title">-->
<!-- <van-icon name="points" class="cell-icon"></van-icon>-->
<!-- 我的积分-->
<!-- </span>-->
<!-- </van-cell>-->
</van-cell-group>
</div>
<div class="logout-wrapper">
<van-button round block color="#ee0a24" @click="logout" class="logout-btn">退出登录</van-button>
</div>
</div>
`,
computed: {
avatar() {
const sex = this.$store.state.user.sex
if (sex === "男" || !sex) {
return "/assets/platform/img/home/avatar_boy.png"
} else {
return "/assets/platform/img/home/avatar_girl.png"
}
}
},
methods: {
logout() {
this.$dialog
.confirm({
title: "温馨提示",
message: "您确认要退出登录吗?"
})
.then(() => {
this.$store.dispatch("logout")
})
}
},
style() {
return /*language=CSS*/ `
.home-mine {
background: #f7f8fa;
}
.profile-card {
margin: 10px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
animation: slideIn 0.3s ease-out;
}
.profile-header {
display: flex;
gap: 16px;
align-items: flex-start;
margin-bottom: 20px;
animation: slideIn 0.3s ease-out;
}
.avatar-section {
flex-shrink: 0;
width: 80px;
}
.avatar-wrapper {
position: relative;
width: 80px;
height: 80px;
}
.avatar {
border: 2px solid #f5f5f5;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: transform 0.3s;
}
.status-badge {
position: absolute;
bottom: -2px;
right: -2px;
background: rgba(255, 255, 255, 0.9);
color: #34c759;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1;
animation: fadeInScale 0.3s ease-out 0.2s backwards;
}
.status-dot {
width: 6px;
height: 6px;
background: #34c759;
border-radius: 50%;
}
.basic-info {
flex: 1;
padding-top: 4px;
display: flex;
flex-direction: column;
gap: 8px;
}
.name-row {
display: flex;
align-items: center;
gap: 12px;
animation: slideLeft 0.3s ease-out;
}
.username {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #333;
}
.gender-tag {
display: inline-flex;
align-items: center;
gap: 4px;
background: #f5f5f5;
padding: 2px 10px;
border-radius: 20px;
font-size: 13px;
color: #666;
}
.employee-id {
color: #666;
font-size: 15px;
animation: slideLeft 0.3s ease-out 0.1s backwards;
}
.id-value {
color: #333;
font-weight: 500;
font-size: 16px;
}
.info-list {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #f5f5f5;
display: flex;
flex-direction: column;
gap: 0;
}
.info-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
animation: slideLeft 0.3s ease-out;
transition: background-color 0.3s;
padding: 8px;
border-radius: 8px;
}
.info-item:active {
background-color: #f5f5f5;
}
.item-label {
color: #999;
width: 70px;
}
.info-value {
color: #333;
flex: 1;
}
.info-item .van-icon {
font-size: 16px;
color: #666;
width: 20px;
}
.action-cards {
margin: 10px;
animation: slideUp 0.3s ease-out;
}
.action-cell {
padding: 16px !important;
}
.cell-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
color: #333;
}
.cell-icon {
font-size: 18px;
color: #666;
}
.logout-wrapper {
padding: 16px;
margin-top: 20px;
animation: fadeIn 0.3s ease-out;
}
.logout-btn {
height: 44px;
font-size: 16px;
transition: opacity 0.3s;
}
.logout-btn:active {
opacity: 0.8;
}
/* 动画定义 */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeft {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 为每个列表项添加延迟动画 */
.info-item:nth-child(1) {
animation-delay: 0.1s;
}
.info-item:nth-child(2) {
animation-delay: 0.2s;
}
.info-item:nth-child(3) {
animation-delay: 0.3s;
}
.info-item:nth-child(4) {
animation-delay: 0.4s;
}
`
}
}
@@ -0,0 +1,599 @@
const msg= {
template: /*language=HTML*/ `
<div class="message-center">
<van-nav-bar title="消息中心" placeholder fixed></van-nav-bar>
<!-- 筛选区域 -->
<van-sticky offset-top="46px">
<!-- <div class="filter-section">-->
<!-- <van-row gutter="10">-->
<!-- <van-col span="12">-->
<!-- <van-field-->
<!-- v-model="filters.type"-->
<!-- is-link-->
<!-- readonly-->
<!-- label="消息类型"-->
<!-- placeholder="选择消息类型"-->
<!-- @click="showTypePicker = true"-->
<!-- />-->
<!-- </van-col>-->
<!-- <van-col span="12" v-if="pageForm.isRead == 0">-->
<!-- <van-button -->
<!-- type="primary" -->
<!-- size="small" -->
<!-- :loading="loading"-->
<!-- @click="markAllAsRead"-->
<!-- style="margin-top: 6px; width: 100%;"-->
<!-- >-->
<!-- 全部已读-->
<!-- </van-button>-->
<!-- </van-col>-->
<!-- </van-row>-->
<!-- </div>-->
<!-- 标签页 -->
<van-tabs v-model="activeTab" @change="handleTabChange">
<van-tab title="全部消息" name="-1">
<template #title>
<van-icon name="chat-o" />
全部消息 ({{stats.total}})
</template>
</van-tab>
<van-tab title="未读消息" name="0">
<template #title>
<van-icon name="bell" />
未读消息 ({{stats.unread}})
</template>
</van-tab>
<van-tab title="已读消息" name="1">
<template #title>
<van-icon name="passed" />
已读消息 ({{stats.read}})
</template>
</van-tab>
</van-tabs>
</van-sticky>
<!-- 消息列表 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
style="padding: 10px"
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div
v-for="message in messages"
:key="message.id"
@click="viewMessage(message)"
class="custom-message-item"
:class="{'unread-message': !message.isRead}"
>
<div class="message-header">
<div class="message-title-wrapper">
<van-tag
:type="message.type === 1 ? 'danger' : 'primary'"
size="mini"
style="margin-right: 8px;"
>
{{getMessageTypeName(message.type)}}
</van-tag>
<!-- <span class="message-title">{{message.title}}</span>-->
<van-tag
v-if="!message.isRead"
type="warning"
size="mini"
style="margin-left: 8px;"
>
未读
</van-tag>
</div>
<div class="message-time">{{formatTime(message.createdAt)}}</div>
</div>
<div class="message-content">{{getContentPreview(message.content)}}</div>
<div class="message-arrow">
<van-icon name="arrow" />
</div>
</div>
</van-list>
</van-pull-refresh>
<!-- 消息类型选择器 -->
<van-popup v-model="showTypePicker" position="bottom">
<van-picker
:columns="typeColumns"
@confirm="onTypeConfirm"
@cancel="showTypePicker = false"
/>
</van-popup>
<!-- 消息详情弹窗 -->
<van-popup
v-model="detailVisible"
position="bottom"
:style="{ height: '80%' }"
closeable
close-icon-position="top-right"
>
<div class="message-detail" v-if="currentMessage.id">
<div class="detail-header">
<h3>{{currentMessage.title}}</h3>
<div class="detail-meta">
<van-tag :type="currentMessage?.globalMessage?.type === 1 ? 'danger' : 'primary'">
{{getMessageTypeName(currentMessage?.globalMessage?.type)}}
</van-tag>
<span class="detail-time">{{formatTime(currentMessage.createdAt)}}</span>
</div>
</div>
<div class="detail-content" v-html="currentMessage?.globalMessage?.content"></div>
</div>
</van-popup>
</div>
`,
data(){
return{
activeTab: '-1',
loading: false,
refreshing: false,
finished: false,
messages: [],
stats: {
total: 0,
unread: 0,
read: 0
},
filters: {
type: ''
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
isRead: -1
},
detailVisible: false,
currentMessage: {},
showTypePicker: false,
typeColumns: [
{ text: '全部类型', value: '' },
{ text: '系统公告', value: '1' },
{ text: '消息通知', value: '2' }
]
}
},
methods: {
// 加载统计数据
async loadStats() {
try {
const resp = await this.$axios.get('/platform/v4/msg/stats')
if (resp.code === 0) {
this.stats = resp.data
}
} catch (error) {
console.error('加载统计数据失败:', error)
}
},
// 加载消息列表
async loadMessages(isRefresh = false) {
if (isRefresh) {
this.pageForm.pageNumber = 1
this.messages = []
this.finished = false
}
this.loading = true
try {
const resp = await this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
if (resp.code === 0) {
const newMessages = resp.data.list || []
if (isRefresh) {
this.messages = newMessages
} else {
this.messages = [...this.messages, ...newMessages]
}
this.pageForm.totalCount = resp.data.totalCount
// 判断是否还有更多数据
if (this.messages.length >= this.pageForm.totalCount) {
this.finished = true
}
} else {
this.$toast(resp.msg)
}
} catch (error) {
this.$toast('加载消息失败')
} finally {
this.loading = false
this.refreshing = false
}
},
// 查看消息详情
async viewMessage(message) {
try {
const resp = await this.$axios.post(`/platform/v4/msg/detail/` + message.id)
if (resp.code === 0) {
this.currentMessage = resp.data
this.detailVisible = true
// 如果是未读消息,标记为已读
if (!message.isRead) {
await this.markAsRead(message.id)
message.isRead = true
this.loadStats()
}
}
} catch (error) {
this.$toast('加载消息详情失败')
}
},
// 标记单条消息为已读
async markAsRead(messageId) {
try {
await this.$axios.post(`/platform/v4/msg/read/` + messageId)
} catch (error) {
console.error('标记已读失败:', error)
}
},
// 全部标记为已读
async markAllAsRead() {
this.$dialog.confirm({
title: '提示',
message: '确定要将所有未读消息标记为已读吗?'
}).then(async () => {
this.loading = true
try {
const resp = await this.$axios.post('/platform/v4/msg/read/all')
if (resp.code === 0) {
this.$toast.success('操作成功')
this.loadMessages(true)
this.loadStats()
} else {
this.$toast(resp.msg)
}
} catch (error) {
this.$toast('操作失败')
} finally {
this.loading = false
}
}).catch(() => {
// 用户取消
})
},
// 切换标签页
handleTabChange(name) {
this.pageForm.isRead = parseInt(name)
this.loadMessages(true)
},
// 点击统计卡片切换标签
switchTab(tabValue) {
this.activeTab = tabValue.toString()
this.pageForm.isRead = tabValue
this.loadMessages(true)
},
// 下拉刷新
onRefresh() {
this.loadMessages(true)
this.loadStats()
},
// 上拉加载更多
onLoad() {
if (this.finished) {
return
}
this.pageForm.pageNumber++
this.loadMessages()
},
// 消息类型选择确认
onTypeConfirm(value) {
this.filters.type = value.value
this.showTypePicker = false
this.loadMessages(true)
},
// 获取消息类型名称
getMessageTypeName(type) {
return type === 1 ? '系统公告' : '消息通知'
},
// 获取内容预览
getContentPreview(content) {
if (!content) return ''
// 移除HTML标签
const text = content.replace(/<[^>]*>/g, '')
return text.length > 60 ? text.substring(0, 30) + '...' : text
},
// 格式化时间
formatTime(timestamp) {
return this.$moment(timestamp).format('MM-DD HH:mm')
}
},
created() {
this.loadStats()
this.loadMessages(true)
},
style: /*language=CSS*/ `
.message-center {
background-color: #f5f7fa;
min-height: 100vh;
padding-bottom: 20px;
}
/* 自定义消息列表项样式 */
/deep/ .custom-message-item {
background-color: #fff;
padding: 10px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
border-bottom: 1px solid #f0f0f0;
border-radius: 8px;
}
/deep/ .custom-message-item:hover {
background-color: #f8f9fa;
}
/deep/ .custom-message-item:active {
background-color: #f0f0f0;
}
/deep/ .message-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
/deep/ .message-time {
font-size: 12px;
color: #969799;
flex-shrink: 0;
margin-left: 12px;
}
/deep/ .message-content {
font-size: 14px;
color: #646566;
line-height: 1.6;
margin-bottom: 8px;
padding-right: 20px;
}
/deep/ .message-arrow {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
color: #c8c9cc;
font-size: 12px;
}
/* 筛选区域样式 */
/deep/ .filter-section {
padding: 12px 16px;
background-color: #fff;
}
/deep/ .message-title-wrapper {
display: flex;
align-items: center;
flex-wrap: wrap;
flex: 1;
}
/deep/ .message-title {
flex: 1;
/*font-weight: 500;*/
color: #323233;
}
/* 消息详情弹窗样式 */
/deep/ .message-detail {
padding: 20px;
height: 100%;
overflow-y: auto;
background-color: #fff;
}
/deep/ .detail-header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #eee;
}
/deep/ .detail-header h3 {
font-size: 18px;
font-weight: 500;
margin: 0 0 8px 0;
color: #323233;
}
/deep/ .detail-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 14px;
color: #666;
}
/deep/ .detail-time {
color: #969799;
}
/deep/ .detail-content {
line-height: 1.8;
font-size: 15px;
color: #323233;
}
/deep/ .detail-content img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 8px 0;
}
/deep/ .detail-content p {
margin: 12px 0;
}
/* 标签页样式优化 */
/deep/ .van-tabs__nav {
background-color: #fff;
}
/deep/ .van-tab {
font-size: 14px;
}
/deep/ .van-tab--active {
color: var(--color-primary, #1989fa);
font-weight: 500;
}
/deep/ .van-tabs__line {
background-color: var(--color-primary, #1989fa);
}
/* 列表样式优化 */
/deep/ .van-cell__title {
font-size: 15px;
}
/deep/ .van-cell__label {
font-size: 13px;
color: #969799;
margin-top: 4px;
}
/deep/ .van-cell__value {
font-size: 12px;
color: #969799;
}
/* 标签样式 */
/deep/ .van-tag--mini {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
}
/* 弹窗样式 */
/deep/ .van-popup--bottom {
border-radius: 16px 16px 0 0;
}
/* 下拉刷新和上拉加载样式 */
/deep/ .van-pull-refresh__track {
background-color: #f5f7fa;
}
/deep/ .van-list__finished-text {
color: #969799;
font-size: 12px;
padding: 16px 0;
}
/* 空状态样式 */
/deep/ .van-empty {
padding: 40px 0;
}
/deep/ .van-empty__description {
color: #969799;
font-size: 14px;
}
/* 简约动画效果 */
/* 基础过渡效果 */
/deep/ .oa-message-item,
/deep/ .oa-stat-item,
/deep/ .oa-refresh-btn,
/deep/ .oa-search-input {
transition: all 0.2s ease;
}
/* 页面进入动画 */
/deep/ .page-enter {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 简化的响应式动画 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* 响应式设计 */
@media (max-width: 480px) {
/deep/ .oa-header {
padding: 10px 12px;
}
/deep/ .oa-header-title {
font-size: 15px;
}
/deep/ .oa-stats-panel,
/deep/ .oa-toolbar,
/deep/ .oa-message-list {
margin: 6px 12px;
}
/deep/ .oa-stat-item {
padding: 12px 8px;
}
/deep/ .oa-stat-number {
font-size: 16px;
}
/deep/ .oa-stat-label {
font-size: 11px;
}
/deep/ .oa-message-item {
padding: 12px;
}
/deep/ .oa-message-title {
font-size: 14px;
}
/deep/ .oa-message-content {
font-size: 12px;
}
}
`
}
@@ -0,0 +1,295 @@
const todo = {
template: /*language=HTML*/ `
<div class="task-center">
<van-nav-bar title="待办中心" placeholder fixed></van-nav-bar>
<!-- 标签页 -->
<van-sticky offset-top="46px">
<van-tabs v-model="activeTab" @change="handleTabChange" animated swipeable>
<van-tab :title="'待办(' + todoCount + ')'" name="todo"></van-tab>
<van-tab :title="'已办(' + doneCount + ')'" name="done"></van-tab>
<van-tab :title="'发起(' + startedCount + ')'" name="started"></van-tab>
</van-tabs>
<!-- 搜索筛选区域 -->
<div class="filter-section">
<div>
<div class="search-form">
<van-search v-model="pageForm.searchKeyword" placeholder="请输入搜索关键词"
@search="doSearch"></van-search>
</div>
<!-- <van-field-->
<!-- readonly-->
<!-- clickable-->
<!-- :value="categoryLabel"-->
<!-- placeholder="选择流程分类"-->
<!-- class="category-select"-->
<!-- @click="showCategoryPicker = true"-->
<!-- ></van-field>-->
</div>
<van-popup v-model="showCategoryPicker" round position="bottom">
<van-picker
show-toolbar
:columns="categoryOptions"
value-key="name"
@confirm="onCategoryConfirm"
@cancel="showCategoryPicker = false"
></van-picker>
</van-popup>
</div>
</van-sticky>
<!-- 任务列表区域 -->
<table-list :api="'/flow/todoCenter/'+ activeTab" :page_form.sync="pageForm"
ref="tableListRef"
@ready="doSearch"
title="variable.instanceName">
<template v-slot="{index,row}">
<table-column label="任务节点">{{row.taskName}}</table-column>
<table-column label="流程分类">{{categoryOptions.find(item => item.id === row.category)?.name ||
'未知分类'}}
</table-column>
<table-column label="申请人">{{row.variable?.initiatorName || '未知'}}</table-column>
<table-column label="状态" v-if="activeTab === 'started'">{{processStatusMap[row.state]?.text ||
'未知状态'}}
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
</template>
</table-list>
</div>
`,
data() {
return {
// 统计数据
todoCount: 0,
doneCount: 0,
startedCount: 0,
// 查询表单
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: "",
category: ""
},
// 流程类型选项
categoryOptions: [],
showCategoryPicker: false,
categoryLabel: "",
// 任务列表
tasks: [],
loading: false,
finished: false,
refreshing: false,
// 标签页
activeTab: "todo",
processStatusMap: {
10: {text: "进行中", class: "doing"},
20: {text: "已完成", class: "finished"},
30: {text: "已撤回", class: "withdraw"},
40: {text: "强行终止", class: "interrupt"},
45: {text: "已拒绝", class: "reject"},
50: {text: "挂起", class: "pending"},
99: {text: "已废弃", class: "abandon"}
}
}
},
created() {
this.initData();
},
methods: {
// 初始化数据
async initData() {
await Promise.all([this.getStatistics(), this.listCategory()]);
},
// 获取统计数据
async getStatistics() {
try {
const {code, data, msg} = await this.$axios.post("/flow/todoCenter/statistics");
if (code === 0) {
const {todoCount, doneCount, startedCount} = data
this.todoCount = todoCount;
this.doneCount = doneCount;
this.startedCount = startedCount;
}
} catch (error) {
this.$toast("获取统计数据失败");
console.error("获取统计数据失败:", error);
}
},
// 获取流程类型
async listCategory() {
try {
// 这里使用模拟数据,实际项目中替换为API调用
const res = await axios.post("/flow/category/list");
if (res.data.code === 0) {
this.categoryOptions = res.data.data;
}
} catch (error) {
this.$toast("获取流程分类失败");
console.error("获取流程分类失败:", error);
}
},
// 处理标签页变化
handleTabChange(name) {
this.activeTab = name;
this.doSearch()
this.getStatistics();
},
// 搜索
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
// 重置搜索
reset() {
this.pageForm.searchKeyword = "";
this.pageForm.category = "";
this.categoryLabel = "";
this.doSearch();
},
// 分类选择确认
onCategoryConfirm(value) {
this.pageForm.category = value.id;
this.categoryLabel = value.name;
this.showCategoryPicker = false;
},
// 处理任务
onView(task) {
const {taskId, taskKey, instanceId, businessNo, formKey, h5FormKey} = task;
if (!h5FormKey) {
this.$toast("当前流程没有配置地址");
return;
}
this.$pjaxReplace(h5FormKey + "?taskId=" + taskId + "bizId=" + businessNo + "taskKey=" + taskKey+ "&tab=" + this.activeTab)
},
// 获取空状态文本
getEmptyText() {
switch (this.activeTab) {
case "todo":
return "您当前没有需要办理的任务,辛苦了";
case "done":
return "您当前没有已办理的任务记录";
case "started":
return "您当前没有发起的流程";
default:
return "暂无数据";
}
}
},
style: /*language=CSS*/ `
/* 标签页样式优化 */
/deep/ .van-tabs__nav {
background-color: #fff;
}
/deep/ .van-tab {
font-size: 14px;
}
/deep/ .van-tab--active {
color: var(--color-primary, #1989fa);
font-weight: 500;
}
/deep/ .van-tabs__line {
background-color: var(--color-primary, #1989fa);
}
/deep/ .filter-section {
/*margin-bottom: 16px;*/
}
.search-form {
display: flex;
background: #fff;
padding: 12px 16px;
border-radius: 8px;
align-items: center;
}
.search-form .van-search{
width: 100%;
}
.search-input {
flex: 1;
margin-right: 12px;
}
.task-list {
margin-top: 16px;
}
/deep/ .task-item {
margin-bottom: 12px;
overflow: hidden;
background-color: #ffffff;
}
/deep/ .task-content {
padding: 16px;
}
/deep/ .task-title {
font-weight: 500;
font-size: 16px;
margin-bottom: 8px;
color: #323233;
}
/deep/ .task-info {
display: flex;
font-size: 14px;
color: #969799;
margin-bottom: 4px;
}
/deep/ .task-info-label {
width: 70px;
flex-shrink: 0;
}
/deep/ .task-info-value {
flex: 1;
}
/deep/ .task-footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
/deep/ .empty-section {
padding: 40px 0;
text-align: center;
color: #969799;
}
.category-select {
margin: 0 12px 12px 0;
}
`
}
@@ -0,0 +1,119 @@
const work = {
template: /*language=HTML*/ `
<div class="workbenche">
<van-nav-bar title="工作台" placeholder fixed></van-nav-bar>
<div class="module-item" v-for="item in $store.state.user.h5ModuleMenus" :key="item.id">
<div class="module-header">
<div class="module-header__content">{{item.name}}</div>
</div>
<div class="module-content">
<van-grid :column-num="3">
<van-grid-item icon="photo-o" :text="menu.name" v-for="menu in item.menus" :key="menu.id"
@click="menuClick(menu)">
<template #icon>
<img :src="menu.icon" alt="" style="width: 40px;height: 40px">
</template>
<template #text>
<span>{{menu.name}}</span>
</template>
</van-grid-item>
</van-grid>
</div>
</div>
</div>
`,
store,
methods: {
menuClick(item) {
this.$pjaxReplace(item.href)
}
},
mounted() {
const styles = {
workbenche: window.goober.css`
padding: 16px;
background: #f5f5f5;
`,
moduleItem: goober.css`
margin-bottom: 16px;
background: white;
border-radius: 8px;
overflow: hidden;
`,
moduleHeader: goober.css`
padding: 12px 16px;
background: #fafafa;
border-bottom: 1px solid #eee;
`,
moduleHeaderContent: goober.css`
border-left: 4px solid var(--color-primary);
font-size: 15px;
color: #000;
font-weight: 600;
padding-left: 8px;
`,
moduleContent: goober.css`
padding: 8px;
.van-grid-item__content {
padding: 14px 8px;
}
`,
menuIcon: goober.css`
width: 40px;
height: 40px;
`
};
console.log(styles)
},
style: /*language=CSS*/ `
.left-span-label {
border-left: 4px solid var(--color-primary);
font-size: 15px;
color: #000;
font-weight: 600;
padding-left: 8px;
margin-top: 6px;
}
.van-grid-item__content {
padding: 14px 8px;
}
.grid_text {
white-space: nowrap;
overflow: hidden;
width: 100%;
text-align: center;
font-size: 12px;
font-family: "微软雅黑", Helvetica, STHeiTi, sans-serif;
color: grey;
margin-top: 6px;
}
.module-item {
background-color: #fff;
box-shadow: 1px 1px #ccc;
margin-bottom: 10px;
padding: 6px;
}
.module-header {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 10px;
font-weight: 400;
}
.workbenche .module-item .module-header .module-header__content {
display: flex;
flex-direction: column;
flex: 1;
font-size: 18px;
font-weight: 700;
color: var(--color-primary);
}
`
}
@@ -0,0 +1,149 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.bgcolor-white {
background: #ffffff;
}
.table-list .table-card {
margin: 10px;
background: #fff;
padding: 8px;
border-bottom: 1px #ebedf0 solid;
position: relative;
border-radius: 0 !important;
}
.table-list .table-card:nth-of-type(1) {
margin: 0 10px 10px 10px;
padding-top: 10px;
}
.flex-card {
display: flex;
align-items: center;
justify-content: space-between;
}
.left {
font-size: 14px;
width: 50%;
}
.right {
text-align: right;
}
.increase {
font-weight: bold;
color: red;
}
.decrease {
font-weight: bold;
}
.time {
color: grey;
font-size: 13px;
margin-top: 2px;
}
</style>
<div id="app" v-cloak>
<van-sticky>
<van-nav-bar title="我的积分" @click-left="historyBack" left-arrow left-text="返回" placeholder fixed></van-nav-bar>
<van-dropdown-menu>
<van-dropdown-item v-model="pageForm.changeType" :options="changeTypeOptions" @change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.state" :options="stateOptions" @change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.integralOrigin" :options="integralOriginOptions" @change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<div class="bgcolor-white">
<van-list v-if="tableData.length>0" v-model="tableLoading" :finished="tableFinished" finished-text="没有更多了" @load="onLoad">
<div class="table-list">
<div class="table-card flex-card" v-for="row in tableData" :key="row.id">
<div class="left">
<van-icon name="points" size="16" style="top: 1px"></van-icon>
{{ row.remark ? row.remark : row.integralOrigin }}
</div>
<div class="right">
<div :class="row.changeValue > 0 ? 'increase' : 'decrease'">{{ (row.changeValue > 0 ? '+' : '') + row.changeValue }}</div>
<div class="time">{{ row.changeTime }}</div>
</div>
</div>
</div>
</van-list>
<van-empty v-else image="/assets/platform/plugins/vant-green/images/nodata/nodata.png" description="暂无数据"></van-empty>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
components: {},
data() {
return {
totalIntegral: 0,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: ""
},
tableData: [],
tableLoading: false,
tableFinished: false,
changeTypeOptions: [],
stateOptions: [],
integralOriginOptions: []
}
},
methods: {
historyBack,
doSearch() {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.tableData = []
this.onLoad()
},
onLoad() {
this.tableLoading = true
this.$axios.post("/platform/integral/records/pageData", this.pageForm).then((resp) => {
this.tableData = this.tableData.concat(resp.data.list)
this.pageForm.totalCount = resp.data.totalCount
if (this.tableData.length >= this.pageForm.totalCount) {
this.tableFinished = true
} else {
this.pageForm.pageNumber++
}
this.tableLoading = false
})
}
},
created() {
this.totalIntegral = this.$store.state.user.totalIntegral
this.$businessTool.getDictOptions("INTEGRAL_CHANGE_TYPE").then((data) => {
data.forEach((item) => {
this.changeTypeOptions.push({ text: item.name, value: item.code })
})
this.changeTypeOptions.unshift({ text: "全部变动类型" })
})
this.$businessTool.getDictOptions("INTEGRAL_STATE").then((data) => {
data.forEach((item) => {
this.stateOptions.push({ text: item.name, value: item.code })
})
this.stateOptions.unshift({ text: "全部积分状态" })
})
this.$businessTool.getDictOptions("INTEGRAL_ORIGIN").then((data) => {
data.forEach((item) => {
this.integralOriginOptions.push({ text: item.name, value: item.code })
})
this.integralOriginOptions.unshift({ text: "全部积分来源" })
})
this.onLoad()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,99 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.bgcolor-white {
background: #ffffff;
}
.van-status__figure {
text-align: center;
line-height: 1.5;
}
.van-status__figure img {
width: 130px;
height: 130px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar title="我的签名" @click-left="historyBack" left-arrow left-text="返回" placeholder fixed></van-nav-bar>
<div class="bgcolor-white">
<div v-if="!showSignature" class="p10">
<van-image width="100%" height="200" :src="userSignatureUrl" v-if="userSignatureUrl" style="border: 1px dashed #000">
<template slot="error">获取签名失败</template>
</van-image>
<div v-else class="van-status__figure">
<img src="/assets/platform/plugins/vant-green/images/figure/fail.png" class="" />
<div class="text-center pt10 pb10">暂无签名</div>
</div>
<!-- <van-status orient="vertical" :figure="14" title="暂无签名" v-else></van-status>-->
<div class="margin-top10">
<van-button v-if="!userSignatureUrl" block type="primary" @click="openSignature">去签名</van-button>
<van-button v-else block type="primary" @click="openSignature">修改签名</van-button>
</div>
</div>
<signature v-if="showSignature" @save="saveSignature"></signature>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
components: {
signature: httpVueLoader("/components/plugins/sysSignature/index.vue?v=" + new Date().getTime())
},
data() {
return {
showSignature: false,
userSignatureUrl: null
}
},
methods: {
getUserSignature() {
this.$axios.post("/platform/signature/get").then((res) => {
if (res.code === 0) {
this.userSignatureUrl = res.data.signature
}
})
},
openSignature() {
this.showSignature = true
},
saveSignature(signature) {
console.log(signature)
const file = base64ToFile(signature, "signature.png")
const formData = new FormData()
formData.append("file", file)
axios
.post("/platform/signature/update", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then((res) => {
return res.data
})
.then((res) => {
if (res.code === 0) {
this.showSignature = false
this.$toast.success(res.msg)
this.getUserSignature()
} else {
this.$toast.fail(res.msg)
}
})
}
},
created() {
this.getUserSignature()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,35 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="个人信息" @click-left="historyBack" left-arrow left-text="返回" placeholder fixed></van-nav-bar>
<member-info :user-id="userId" ref="memberInfoRef"></member-info>
</div>
<script nonce="${cspNonce!}">
<!--#include("/platform/zhghh5/staffmanage/member/common/mobileMemberInfo.js"){}#-->
new Vue({
el: "#app",
store,
data() {
return {
userId: ""
}
},
components: {
"member-info": MOBILE_MEMBER_INFO
},
methods: {
viewInfo() {
this.userId = this.$store.state.user.id
}
},
created() {
this.viewInfo()
}
})
</script>
<!--#
}
#-->