公共服务平台-大屏代码。现在废弃,改成其他项目开发
This commit is contained in:
parent
f1c606fa4b
commit
8bd21926c8
|
|
@ -20,7 +20,7 @@ export function getMenu(menuId) {
|
|||
// 查询菜单下拉树结构
|
||||
export function treeselect() {
|
||||
return request({
|
||||
url: '/system/menu/treeselect',
|
||||
url: '/system/menu/treeselectNew',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ export function treeselect() {
|
|||
// 根据角色ID查询菜单下拉树结构
|
||||
export function roleMenuTreeselect(roleId) {
|
||||
return request({
|
||||
url: '/system/menu/roleMenuTreeselect/' + roleId,
|
||||
url: '/system/menu/roleMenuTreeselectNew/' + roleId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -57,4 +57,4 @@ export function delMenu(menuId) {
|
|||
url: '/system/menu/' + menuId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ export const constantRoutes = [
|
|||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/index'),
|
||||
component: () => import('@/views/psp/productCenter/index'),
|
||||
name: 'Index',
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||||
meta: { title: '公共服务平台', icon: 'dashboard', affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -88,27 +88,48 @@ export const constantRoutes = [
|
|||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 在路由配置中导航条的路由
|
||||
{
|
||||
path: '/',
|
||||
component: Layout, // 布局组件,包含左侧菜单和顶部导航
|
||||
hidden: true, // 隐藏左侧菜单项
|
||||
children: [
|
||||
{
|
||||
path: '/productCenter/index',
|
||||
component: () => import('@/views/psp/productCenter/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/common/index',
|
||||
component: () => import('@/views/psp/common/index.vue')
|
||||
},
|
||||
// ... 其他子路由
|
||||
]
|
||||
},
|
||||
|
||||
// 在路由配置中添加产品详情页面路由
|
||||
{
|
||||
path: '/psp/productCenter',
|
||||
component: Layout,
|
||||
redirect: '/psp/productCenter/index',
|
||||
name: 'ProductCenter',
|
||||
meta: { title: '产品中心', icon: 'product' },
|
||||
meta: { title: '产品中心', icon: 'product', },
|
||||
hidden: true, // 隐藏左侧菜单项
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'ProductCenterIndex',
|
||||
component: () => import('@/views/psp/productCenter/index'),
|
||||
meta: { title: '产品中心', icon: 'product' }
|
||||
},
|
||||
{
|
||||
path: 'detail/:id(\\d+)',
|
||||
name: 'ProductDetail',
|
||||
component: () => import('@/views/psp/productCenter/product-detail'),
|
||||
meta: { title: '产品详情', activeMenu: '/psp/productCenter/index' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'case/:id(\\d+)',
|
||||
name: 'ProductCase',
|
||||
component: () => import('@/views/psp/productCenter/product-case'),
|
||||
meta: { title: '产品案例', activeMenu: '/psp/productCenter/index' },
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,457 @@
|
|||
<template>
|
||||
<div class="platform-container">
|
||||
<!-- 使用导航栏组件 -->
|
||||
<nav-bar
|
||||
:active-nav="activeNav"
|
||||
:search-keyword.sync="searchKeyword"
|
||||
@search="handleSearch">
|
||||
</nav-bar>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧筛选栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="filter-section">
|
||||
<h3 class="filter-title">
|
||||
<i class="el-icon-sort"></i> 排序方式
|
||||
</h3>
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" :class="{ active: sortType === 'publish' }">
|
||||
<el-radio v-model="sortType" label="publish">最新发布</el-radio>
|
||||
</li>
|
||||
<li class="filter-option" :class="{ active: sortType === 'download' }">
|
||||
<el-radio v-model="sortType" label="download">下载次数</el-radio>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<h3 class="filter-title">
|
||||
<i class="el-icon-menu"></i> 组件类型
|
||||
</h3>
|
||||
<ul class="filter-options">
|
||||
<li class="filter-option" v-for="type in componentTypes" :key="type.value">
|
||||
<el-checkbox v-model="selectedTypes" :label="type.value">{{ type.label }}</el-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<main class="content-area">
|
||||
<div class="component-grid">
|
||||
<!-- 组件卡片 -->
|
||||
<div class="component-card" v-for="component in filteredComponents" :key="component.id">
|
||||
<div class="card-header">
|
||||
<img :src="apiBase + component.image" alt="Component Image" class="card-image">
|
||||
<!-- 名称和版本在同一行 -->
|
||||
<div class="card-title-wrapper">
|
||||
<h3 class="card-title">{{ component.name }}</h3>
|
||||
<div v-if="showVersion" class="card-version">{{ component.version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button class="card-action-btn" style="background: linear-gradient( 180deg, #00C7EF 0%, #005EEF 100%);color: white; " @click="downloadComponent(component)">
|
||||
下载
|
||||
</button>
|
||||
<button class="card-action-btn" @click="showComments(component)">
|
||||
评论
|
||||
</button>
|
||||
<!-- <button class="card-action-btn" @click="viewDocument(component)">
|
||||
文档
|
||||
</button>-->
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="card-action-btn" @click="toggleDropdown(component)">
|
||||
文档
|
||||
</button>
|
||||
<ul class="dropdown-menu" v-if="activeComponentId === component.id">
|
||||
<li @click="viewDocument(component)">预览</li>
|
||||
<li @click="downloadDocument(component)">下载</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/views/psp/navBar.vue'
|
||||
|
||||
export default {
|
||||
name: "index.vue",
|
||||
components: {
|
||||
NavBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apiBase: process.env.VUE_APP_BASE_API,
|
||||
activeNav: 'components',
|
||||
searchKeyword: '',
|
||||
sortType: 'publish',
|
||||
selectedTypes: [],
|
||||
showVersion: true, // 控制版本号是否显示
|
||||
componentTypes: [
|
||||
{ value: 'data-display', label: '数据展示' },
|
||||
{ value: 'form-control', label: '表单控件' },
|
||||
{ value: 'layout-container', label: '布局容器' },
|
||||
{ value: 'navigation', label: '导航组件' }
|
||||
],
|
||||
components: [
|
||||
{
|
||||
id: 1,
|
||||
name: '数据表格组件',
|
||||
version: 'V2.1.2',
|
||||
type: 'data-display',
|
||||
downloads: 1250,
|
||||
publishDate: '2023-06-15',
|
||||
image: '/profile/avatar/2025/09/03/001_20250903132300A001.JPG',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '报表可视化',
|
||||
version: 'V2.1.2',
|
||||
type: 'data-display',
|
||||
downloads: 980,
|
||||
publishDate: '2023-05-20',
|
||||
image: '/profile/avatar/2025/09/03/ldst.png',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '布局容器',
|
||||
version: 'V2.1.2',
|
||||
type: 'layout-container',
|
||||
downloads: 750,
|
||||
publishDate: '2023-07-10',
|
||||
image: '/profile/avatar/2025/09/03/001_20250903132300A001.JPG',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '数据接入组件-交互',
|
||||
version: 'V2.1.2',
|
||||
type: 'data-display',
|
||||
downloads: 620,
|
||||
publishDate: '2023-04-05',
|
||||
image: '/profile/avatar/2025/09/03/ldst.png',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '表单控件',
|
||||
version: 'V2.1.2',
|
||||
type: 'form-control',
|
||||
downloads: 1100,
|
||||
publishDate: '2023-08-12',
|
||||
image: '/profile/avatar/2025/09/03/ldst.png',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredComponents() {
|
||||
let filtered = this.components;
|
||||
|
||||
// 按类型筛选
|
||||
if (this.selectedTypes.length > 0) {
|
||||
filtered = filtered.filter(component =>
|
||||
this.selectedTypes.includes(component.type)
|
||||
);
|
||||
}
|
||||
|
||||
// 按排序方式排序
|
||||
if (this.sortType === 'download') {
|
||||
filtered = filtered.sort((a, b) => b.downloads - a.downloads);
|
||||
} else {
|
||||
filtered = filtered.sort((a, b) =>
|
||||
new Date(b.publishDate) - new Date(a.publishDate)
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleSearch(keyword) {
|
||||
this.$message({
|
||||
message: `搜索关键词: ${keyword}`,
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
downloadComponent(component) {
|
||||
this.$message({
|
||||
message: `开始下载 ${component.name}`,
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
showComments(component) {
|
||||
this.$message({
|
||||
message: `查看 ${component.name} 的评论`,
|
||||
type: 'info'
|
||||
});
|
||||
},
|
||||
viewDocument(component) {
|
||||
this.$message({
|
||||
message: `查看 ${component.name} 的文档`,
|
||||
type: 'info'
|
||||
});
|
||||
this.activeComponentId = null; // 关闭下拉菜单
|
||||
},
|
||||
toggleDropdown(component) {
|
||||
if (this.activeComponentId === component.id) {
|
||||
this.activeComponentId = null; // 如果已经打开,则关闭
|
||||
} else {
|
||||
this.activeComponentId = component.id; // 否则打开
|
||||
}
|
||||
},
|
||||
downloadDocument(component) {
|
||||
this.$message({
|
||||
message: `开始下载 ${component.name} 的文档`,
|
||||
type: 'success'
|
||||
});
|
||||
this.activeComponentId = null; // 关闭下拉菜单
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.platform-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 主体内容 */
|
||||
.main-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 左侧筛选栏 */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #F6F6F6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.filter-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-title i {
|
||||
margin-right: 8px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.filter-option:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.filter-option.active {
|
||||
color: #409EFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-option .el-checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px 8px 8px 8px;
|
||||
}
|
||||
|
||||
/* 名称和版本的容器 */
|
||||
.card-title-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 10px 0 5px;
|
||||
}
|
||||
|
||||
/* 右侧内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.component-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.component-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.component-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-version {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
background-color: #f0f2f5;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 45px; /* 增大按钮间距 */
|
||||
}
|
||||
|
||||
.card-action-btn {
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
color: #2B2B2B;
|
||||
border: 1px solid #58D9F4; /* 定义边框宽度和透明度 */
|
||||
border-radius: 4px ;
|
||||
padding: 4px 0;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.card-action-btn:hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.card-action-btn:active {
|
||||
background-color: #409eff;
|
||||
color: #ffffff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-action i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.card-action:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 100px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-menu li {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-menu li:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.component-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
<template>
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img :src="logoUrl" alt="Logo" class="logo"/>
|
||||
<span class="platform-title">{{ platformTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-nav">
|
||||
<div
|
||||
v-for="item in navItems"
|
||||
:key="item.key"
|
||||
class="nav-item"
|
||||
:class="{ active: activeNav === item.key }"
|
||||
@click="handleNavClick(item)"
|
||||
>
|
||||
<img :src="item.icon" :alt="item.label" class="nav-icon"/>
|
||||
<span class="nav-text">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showRightSection" class="header-right">
|
||||
<!-- <div v-if="showSearch" class="search-box">
|
||||
<input
|
||||
v-model="internalSearchKeyword"
|
||||
type="text"
|
||||
placeholder="输入关键词搜索"
|
||||
class="search-input"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<button class="search-btn" @click="handleSearch">
|
||||
<i class="el-icon-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="showUserAvatar" class="user-avatar">
|
||||
<img :src="$store.state.user.avatar" alt="用户头像" />
|
||||
</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NavBar',
|
||||
props: {
|
||||
logoUrl: {
|
||||
type: String,
|
||||
default: '/img/psp/productCenter/logo.png'
|
||||
},
|
||||
platformTitle: {
|
||||
type: String,
|
||||
default: '公共服务平台'
|
||||
},
|
||||
navItems: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ key: 'products', label: '产品中心', icon: '/img/psp/productCenter/products.png',route: '/productCenter/index' },
|
||||
{ key: 'components', label: '公共组件', icon: '/img/psp/productCenter/components.png',route: '/common/index' },
|
||||
{ key: 'materials', label: '宣传物料', icon: '/img/psp/productCenter/materials.png',route: '/materials/index'},
|
||||
{ key: 'docs', label: '文档中心', icon: '/img/psp/productCenter/docs.png',route: '/docs/index'}
|
||||
]
|
||||
},
|
||||
activeNav: {
|
||||
type: String,
|
||||
default: 'products'
|
||||
},
|
||||
showRightSection: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showUserAvatar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
userAvatar: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
searchKeyword: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
internalSearchKeyword: this.searchKeyword,
|
||||
userAvatarUrl: this.userAvatar,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchKeyword(newVal) {
|
||||
this.internalSearchKeyword = newVal;
|
||||
},
|
||||
userAvatar(newVal) {
|
||||
this.userAvatarUrl = newVal;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNavClick(item) {
|
||||
// 发射导航变化事件
|
||||
this.$emit('nav-change', item.key);
|
||||
// 执行路由跳转
|
||||
if (item.route) {
|
||||
this.$router.push(item.route);
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
// 修复搜索功能,直接使用internalSearchKeyword
|
||||
this.$emit('update:searchKeyword', this.internalSearchKeyword);
|
||||
this.$emit('search', this.internalSearchKeyword);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
background-image: url("/img/psp/productCenter/topbg.png");
|
||||
color: white;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 100vw;
|
||||
margin: 0 auto;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item.active {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 20px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="platform-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<div class="header">
|
||||
<!-- <div class="header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
|
|
@ -41,7 +41,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
|
||||
<!-- 使用导航栏组件 -->
|
||||
<NavBar
|
||||
:active-nav="activeNav"
|
||||
:search-keyword.sync="searchKeyword"
|
||||
@nav-change="handleNavChange"
|
||||
/>
|
||||
|
||||
<!-- 主体内容 -->
|
||||
<div class="main-content">
|
||||
|
|
@ -96,8 +103,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
// 引入导航栏组件
|
||||
import NavBar from '@/views/psp/navBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'PlatformIndex',
|
||||
components: {
|
||||
NavBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeNav: 'products',
|
||||
|
|
@ -199,10 +212,11 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
console.log('搜索关键词:', this.searchKeyword)
|
||||
// 搜索逻辑已在computed中实现
|
||||
handleNavChange(navKey) {
|
||||
this.activeNav = navKey
|
||||
// 可以根据导航项做页面跳转或其他操作
|
||||
},
|
||||
|
||||
handleDemo(service) {
|
||||
console.log('访问演示:', service.title)
|
||||
// 这里可以跳转到演示页面
|
||||
|
|
|
|||
|
|
@ -0,0 +1,873 @@
|
|||
<template>
|
||||
<div class="product-detail">
|
||||
<!-- 顶部导航栏 -->
|
||||
<!-- 使用导航栏组件 -->
|
||||
<NavBar
|
||||
:active-nav="activeNav"
|
||||
:search-keyword.sync="searchKeyword"
|
||||
@nav-change="handleNavChange"
|
||||
/>
|
||||
|
||||
<!-- Added breadcrumb navigation with back button -->
|
||||
<div class="breadcrumb">
|
||||
<div class="breadcrumb-nav">
|
||||
<span class="breadcrumb-link" >产品中心</span>
|
||||
<span class="breadcrumb-separator">></span>
|
||||
<span class="breadcrumb-current">产品详情</span>
|
||||
<span class="breadcrumb-separator">></span>
|
||||
<span class="breadcrumb-current">案例集</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class="arrow-left-icon">←</i>
|
||||
<button class="btn-edit" @click="goBack">返回</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="section product-intro">
|
||||
<div class="intro-content">
|
||||
<div class="intro-image">
|
||||
<img :src="apiBase + intro" alt="安徽送变电工程有限公司" />
|
||||
</div>
|
||||
<div class="intro-text">
|
||||
<div class="intro-header">
|
||||
<h2>安徽送变电工程有限公司</h2>
|
||||
</div>
|
||||
<p>
|
||||
基于移动互联网(微信、APP),超前的互联网思维(在线订餐、互动、点评),由内到外的方方面面的管理功能;硬件完整兼容、版本无缝衔接;适用于多种食堂经营模式(单位自营 、承包经营、自由消费、固定消费 、计次消费、自提派送、食堂就餐、多样餐补方式);整体提升食堂服务和管理的质量;打造名副其实与时俱进的互联网食堂。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入导航栏组件
|
||||
import NavBar from '@/views/psp/navBar.vue'
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
components: {
|
||||
NavBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeNav: 'products',
|
||||
searchKeyword: '',
|
||||
brochureIndex: 0,
|
||||
videoIndex: 0,
|
||||
caseIndex: 0,
|
||||
itemsPerView: 4,
|
||||
autoPlayInterval: null,
|
||||
brochureOffset: 0,
|
||||
videoOffset: 0,
|
||||
brochureAutoInterval: null,
|
||||
videoAutoInterval: null,
|
||||
brochurePaused: false,
|
||||
videoPaused: false,
|
||||
scrollSpeed: 0.5, // pixels per frame
|
||||
apiBase: process.env.VUE_APP_BASE_API,
|
||||
intro :"/profile/avatar/2025/09/03/ldst.png",
|
||||
navItems: [
|
||||
{ key: 'products',label: '产品中心', icon: '/img/psp/productCenter/products.png' },
|
||||
{ key: 'components', label: '公共组件', icon: '/img/psp/productCenter/components.png' },
|
||||
{ key: 'materials', label: '宣传物料', icon: '/img/psp/productCenter/materials.png'},
|
||||
{ key: 'docs', label: '文档中心', icon: '/img/psp/productCenter/docs.png'}
|
||||
],
|
||||
brochures: [
|
||||
{ title: '智慧食堂产品手册1', image: '/profile/avatar/2025/09/03/ldst.png' },
|
||||
{ title: '智慧食堂产品手册2', image: '/profile/avatar/2025/09/03/001_20250903132300A001.jpg' },
|
||||
{ title: '智慧食堂产品手册3', image: '/profile/avatar/2025/09/03/ldst.png' },
|
||||
{ title: '智慧食堂产品手册4', image: '/profile/avatar/2025/09/03/001_20250903132300A001.jpg' },
|
||||
{ title: '智慧食堂产品手册5', image: '/profile/avatar/2025/09/03/ldst.png' },
|
||||
{ title: '智慧食堂产品手册6', image: '/profile/avatar/2025/09/03/001_20250903132300A001.jpg' }
|
||||
],
|
||||
videos: [
|
||||
{ title: '智慧食堂产品视频1', thumbnail: '/profile/avatar/2025/09/03/vi1.mp4' },
|
||||
{ title: '智慧食堂产品视频2', thumbnail: '/profile/avatar/2025/09/03/vi2.mp4' },
|
||||
{ title: '智慧食堂产品视频3', thumbnail: '/profile/avatar/2025/09/03/vi1.mp4' },
|
||||
{ title: '智慧食堂产品视频4', thumbnail: '/profile/avatar/2025/09/03/vi2.mp4' },
|
||||
{ title: '智慧食堂产品视频5', thumbnail: '/profile/avatar/2025/09/03/vi1.mp4' },
|
||||
{ title: '智慧食堂产品视频6', thumbnail: '/profile/avatar/2025/09/03/vi2.mp4' }
|
||||
],
|
||||
cases: [
|
||||
{
|
||||
company: '安徽送变电工程有限公司',
|
||||
description: '基于移动互联网(微信、APP),整合所有数据资源(在线订餐、充值、点评),由内到外的为学校师生提供便民服务,深度定制餐厅、校本文档精准、适用于智慧食堂数据资源,通过设备、消费设备、自助充值、智慧收银、零售管理、多样化计方式),致力提升学校整体服务和管理的能力,打造名副其实与时俱进的互联网餐厅。',
|
||||
image: '/profile/avatar/2025/09/03/ldst.png?height=280&width=400'
|
||||
},
|
||||
{
|
||||
company: '北京科技大学智慧食堂',
|
||||
description: '通过智慧食堂系统的实施,学校食堂管理效率提升了40%,学生满意度达到95%以上,实现了真正的数字化转型,为师生提供更便捷的用餐体验。',
|
||||
image: '/profile/avatar/2025/09/03/001_20250903132300A001.jpg?height=280&width=400'
|
||||
},
|
||||
{
|
||||
company: '上海交通大学',
|
||||
description: '采用先进的人工智能技术,实现了食堂的智能化管理,包括智能点餐、营养分析、库存管理等功能,大大提升了运营效率。',
|
||||
image: '/profile/avatar/2025/09/03/ldst.png?height=280&width=400'
|
||||
}
|
||||
],
|
||||
animationFrame: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.productId = this.$route.params.id
|
||||
console.log('产品ID:', this.productId)
|
||||
},
|
||||
computed: {
|
||||
maxBrochureIndex() {
|
||||
return Math.max(0, this.brochures.length - this.itemsPerView)
|
||||
},
|
||||
maxVideoIndex() {
|
||||
return Math.max(0, this.videos.length - this.itemsPerView)
|
||||
},
|
||||
maxCaseIndex() {
|
||||
return Math.max(0, this.cases.length - 1)
|
||||
},
|
||||
displayBrochures() {
|
||||
return [...this.brochures, ...this.brochures, ...this.brochures]
|
||||
},
|
||||
displayVideos() {
|
||||
return [...this.videos, ...this.videos, ...this.videos]
|
||||
},
|
||||
brochureTrackStyle() {
|
||||
const itemWidth = 280 // approximate width including gap
|
||||
return {
|
||||
transform: `translateX(-${this.brochureOffset}px)`,
|
||||
transition: 'none'
|
||||
}
|
||||
},
|
||||
videoTrackStyle() {
|
||||
const itemWidth = 280 // approximate width including gap
|
||||
return {
|
||||
transform: `translateX(-${this.videoOffset}px)`,
|
||||
transition: 'none'
|
||||
}
|
||||
},
|
||||
caseTrackStyle() {
|
||||
return {
|
||||
transform: `translateX(-${this.caseIndex * 100}%)`
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.startAutoPlay()
|
||||
this.startSmoothScroll()
|
||||
this.handleResize()
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopAutoPlay()
|
||||
this.stopSmoothScroll()
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleNavChange(navKey) {
|
||||
this.activeNav = navKey
|
||||
// 可以根据导航项做页面跳转或其他操作
|
||||
},
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
|
||||
prevBrochure() {
|
||||
const containerWidth = this.$refs.brochureCarousel.offsetWidth
|
||||
this.brochureOffset -= containerWidth
|
||||
if (this.brochureOffset < 0) {
|
||||
this.brochureOffset = this.brochures.length * 280
|
||||
}
|
||||
},
|
||||
nextBrochure() {
|
||||
const containerWidth = this.$refs.brochureCarousel.offsetWidth
|
||||
this.brochureOffset += containerWidth
|
||||
const maxOffset = this.brochures.length * 280
|
||||
if (this.brochureOffset >= maxOffset * 2) {
|
||||
this.brochureOffset = maxOffset
|
||||
}
|
||||
},
|
||||
prevVideo() {
|
||||
const containerWidth = this.$refs.videoCarousel.offsetWidth
|
||||
this.videoOffset -= containerWidth
|
||||
if (this.videoOffset < 0) {
|
||||
this.videoOffset = this.videos.length * 280
|
||||
}
|
||||
},
|
||||
nextVideo() {
|
||||
const containerWidth = this.$refs.videoCarousel.offsetWidth
|
||||
this.videoOffset += containerWidth
|
||||
const maxOffset = this.videos.length * 280
|
||||
if (this.videoOffset >= maxOffset * 2) {
|
||||
this.videoOffset = maxOffset
|
||||
}
|
||||
},
|
||||
prevCase() {
|
||||
if (this.caseIndex > 0) {
|
||||
this.caseIndex--
|
||||
}
|
||||
},
|
||||
nextCase() {
|
||||
if (this.caseIndex < this.maxCaseIndex) {
|
||||
this.caseIndex++
|
||||
}
|
||||
},
|
||||
startAutoPlay() {
|
||||
this.autoPlayInterval = setInterval(() => {
|
||||
// Auto-advance cases every 5 seconds
|
||||
if (this.caseIndex < this.maxCaseIndex) {
|
||||
this.caseIndex++
|
||||
} else {
|
||||
this.caseIndex = 0
|
||||
}
|
||||
}, 5000)
|
||||
},
|
||||
stopAutoPlay() {
|
||||
if (this.autoPlayInterval) {
|
||||
clearInterval(this.autoPlayInterval)
|
||||
this.autoPlayInterval = null
|
||||
}
|
||||
},
|
||||
startSmoothScroll() {
|
||||
const animate = () => {
|
||||
if (!this.brochurePaused) {
|
||||
this.brochureOffset += 0.5
|
||||
const itemWidth = 280
|
||||
const maxOffset = this.brochures.length * itemWidth
|
||||
|
||||
if (this.brochureOffset >= maxOffset * 2) {
|
||||
this.brochureOffset = maxOffset
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.videoPaused) {
|
||||
this.videoOffset += 0.5
|
||||
const itemWidth = 280
|
||||
const maxOffset = this.videos.length * itemWidth
|
||||
|
||||
if (this.videoOffset >= maxOffset * 2) {
|
||||
this.videoOffset = maxOffset
|
||||
}
|
||||
}
|
||||
|
||||
this.animationFrame = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
const itemWidth = 280
|
||||
this.brochureOffset = this.brochures.length * itemWidth
|
||||
this.videoOffset = this.videos.length * itemWidth
|
||||
|
||||
this.animationFrame = requestAnimationFrame(animate)
|
||||
},
|
||||
stopSmoothScroll() {
|
||||
if (this.animationFrame) {
|
||||
cancelAnimationFrame(this.animationFrame)
|
||||
this.animationFrame = null
|
||||
}
|
||||
},
|
||||
pauseBrochureScroll() {
|
||||
this.brochurePaused = true
|
||||
},
|
||||
resumeBrochureScroll() {
|
||||
this.brochurePaused = false
|
||||
},
|
||||
pauseVideoScroll() {
|
||||
this.videoPaused = true
|
||||
},
|
||||
resumeVideoScroll() {
|
||||
this.videoPaused = false
|
||||
},
|
||||
startInfiniteScroll() {
|
||||
},
|
||||
stopInfiniteScroll() {
|
||||
},
|
||||
playVideo(video) {
|
||||
console.log('Playing video:', video.title)
|
||||
// 实现视频播放逻辑
|
||||
},
|
||||
handleVideoError(event) {
|
||||
console.log('[v0] Video loading error:', event.target.src)
|
||||
// 视频加载失败时,可以设置默认缩略图
|
||||
event.target.style.display = 'none'
|
||||
const img = document.createElement('img')
|
||||
img.src = '/placeholder.svg?height=180&width=240'
|
||||
img.alt = 'Video thumbnail'
|
||||
img.style.width = '100%'
|
||||
img.style.height = '100%'
|
||||
img.style.objectFit = 'cover'
|
||||
event.target.parentNode.appendChild(img)
|
||||
},
|
||||
|
||||
handleVideoLoaded(event) {
|
||||
console.log('[v0] Video metadata loaded:', event.target.src)
|
||||
// 视频元数据加载完成,第一帧应该可以显示了
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-detail {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.header {
|
||||
background-image: url("/img/psp/productCenter/topbg.png");
|
||||
color: white;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 100vw;
|
||||
margin: 0 auto;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item.active {
|
||||
color: white;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
outline: none;
|
||||
width: 200px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.search-btn img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Enhanced breadcrumb styling with back button */
|
||||
.breadcrumb {
|
||||
padding: 16px 24px;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
.breadcrumb-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.arrow-left-icon {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #4A90E2;
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 4px;
|
||||
color: #9ca3af;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.breadcrumb-current {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
color: #4A90E2;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
padding: 24px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.view-more {
|
||||
color: #4A90E2;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.view-more:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Product Introduction */
|
||||
.product-intro {
|
||||
border: none;
|
||||
box-shadow: 0 4px 20px rgba(74, 144, 226, 0.08);
|
||||
}
|
||||
|
||||
.intro-content {
|
||||
display: flex;
|
||||
gap: 48px;
|
||||
align-items: center;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.intro-image {
|
||||
flex: 0 0 400px;
|
||||
}
|
||||
|
||||
.intro-image img {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(74, 144, 226, 0.12);
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.intro-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.intro-text h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.intro-text p {
|
||||
color: #4b5563;
|
||||
line-height: 1.8;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* Enhanced button styling */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 28px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(74, 144, 226, 0.35);
|
||||
}
|
||||
|
||||
/* Enhanced carousel styling for infinite loop */
|
||||
.carousel-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.carousel-btn {
|
||||
background: white;
|
||||
border: 2px solid #e5e7eb;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.carousel-btn:hover {
|
||||
background: #4A90E2;
|
||||
border-color: #4A90E2;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.25);
|
||||
}
|
||||
|
||||
.carousel-btn:hover .arrow-left,
|
||||
.carousel-btn:hover .arrow-right {
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
/* Added arrow icons */
|
||||
.arrow-left,
|
||||
.arrow-right {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
border-width: 6px 8px 6px 0;
|
||||
border-color: transparent #6b7280 transparent transparent;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
border-width: 6px 0 6px 8px;
|
||||
border-color: transparent transparent transparent #6b7280;
|
||||
}
|
||||
|
||||
.carousel-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.carousel-track {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
/* Remove transition as we're using smooth animation */
|
||||
}
|
||||
|
||||
/* Enhanced carousel items */
|
||||
.carousel-item {
|
||||
flex: 0 0 260px; /* Fixed width instead of percentage */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.brochure-item .item-image,
|
||||
.video-item .item-image {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.brochure-item:hover .item-image,
|
||||
.video-item:hover .item-image {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.item-image img {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
margin: 16px 0 0 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Enhanced video styling */
|
||||
.video-thumbnail {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.video-item:hover .play-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
background: rgba(74, 144, 226, 0.9);
|
||||
color: white;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.play-button:hover {
|
||||
background: #357ABD;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
/* Enhanced case items */
|
||||
.cases-carousel .carousel-track {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.case-item {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex: 0 0 100%;
|
||||
padding: 32px;
|
||||
background: linear-gradient(135deg, #f8fbff 0%, #ffffff 100%);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.case-image {
|
||||
flex: 0 0 400px;
|
||||
}
|
||||
|
||||
.case-image img {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.case-content {
|
||||
flex: 1;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.case-content h4 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.case-content p {
|
||||
color: #4b5563;
|
||||
line-height: 1.8;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* Enhanced responsive design */
|
||||
@media (max-width: 1024px) {
|
||||
.carousel-item {
|
||||
flex: 0 0 calc(33.333% - 14px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.breadcrumb {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.breadcrumb-nav {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.intro-content {
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.intro-image {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.intro-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.carousel-item {
|
||||
flex: 0 0 calc(50% - 10px);
|
||||
}
|
||||
|
||||
.case-item {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.case-image {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.case-content {
|
||||
padding-left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.carousel-container {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.carousel-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.carousel-item {
|
||||
flex: 0 0 calc(100% - 0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,47 +1,12 @@
|
|||
<template>
|
||||
<div class="product-detail">
|
||||
<!-- 顶部导航栏 -->
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<img src="/img/psp/productCenter/logo.png" alt="Logo" class="logo"/>
|
||||
<span class="platform-title">公共服务平台</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-nav">
|
||||
<div
|
||||
v-for="item in navItems"
|
||||
:key="item.key"
|
||||
class="nav-item"
|
||||
:class="{ active: activeNav === item.key }"
|
||||
@click="activeNav = item.key"
|
||||
>
|
||||
<img :src="item.icon" :alt="item.label" class="nav-icon"/>
|
||||
<span class="nav-text">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<!-- <div class="search-box">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
placeholder="输入关键词搜索"
|
||||
class="search-input"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<button class="search-btn" @click="handleSearch">
|
||||
<i class="el-icon-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<img :src="$store.state.user.avatar" alt="用户头像"/>
|
||||
</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 使用导航栏组件 -->
|
||||
<NavBar
|
||||
:active-nav="activeNav"
|
||||
:search-keyword.sync="searchKeyword"
|
||||
@nav-change="handleNavChange"
|
||||
/>
|
||||
|
||||
<!-- Added breadcrumb navigation with back button -->
|
||||
<div class="breadcrumb">
|
||||
|
|
@ -173,7 +138,7 @@
|
|||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h3>产品案例</h3>
|
||||
<a href="#" class="view-more">查看更多</a>
|
||||
<a href="#" class="view-more" @click.prevent="viewCase">查看更多</a>
|
||||
</div>
|
||||
<div class="carousel-container cases-carousel">
|
||||
<button class="carousel-btn prev" @click="prevCase" :disabled="caseIndex === 0">
|
||||
|
|
@ -206,8 +171,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
// 引入导航栏组件
|
||||
import NavBar from '@/views/psp/navBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
components: {
|
||||
NavBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeNav: 'products',
|
||||
|
|
@ -320,6 +291,10 @@ export default {
|
|||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleNavChange(navKey) {
|
||||
this.activeNav = navKey
|
||||
// 可以根据导航项做页面跳转或其他操作
|
||||
},
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
|
|
@ -453,7 +428,17 @@ export default {
|
|||
handleVideoLoaded(event) {
|
||||
console.log('[v0] Video metadata loaded:', event.target.src)
|
||||
// 视频元数据加载完成,第一帧应该可以显示了
|
||||
},
|
||||
|
||||
viewCase() {
|
||||
// 跳转到产品详情页,传递当前产品ID
|
||||
// this.$router.push(`/product-detail/${this.productId}`);
|
||||
this.$router.push({
|
||||
name: 'ProductCase',
|
||||
params: { id: `${this.productId}`}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-search"
|
||||
@click="handleViewUsers(scope.row)"
|
||||
>查看人员</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
|
|
@ -306,6 +312,14 @@ export default {
|
|||
})
|
||||
})
|
||||
},
|
||||
/** 查看部门人员操作 */
|
||||
handleViewUsers(row) {
|
||||
// 跳转到用户管理页面,并传递部门ID参数
|
||||
this.$router.push({
|
||||
path: '/system/user',
|
||||
query: { deptId: row.deptId , deptName: row.deptName }
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm: function() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,14 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope" v-if="scope.row.roleId !== 1">
|
||||
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-search"
|
||||
@click="handleViewUsers(scope.row)"
|
||||
>查看人员</el-button>
|
||||
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
|
|
@ -138,9 +146,9 @@
|
|||
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="handleDataScope" icon="el-icon-circle-check"
|
||||
v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
|
||||
v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
|
||||
<el-dropdown-item command="handleAuthUser" icon="el-icon-user"
|
||||
v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
|
||||
v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
|
@ -156,7 +164,7 @@
|
|||
/>
|
||||
|
||||
<!-- 添加或修改角色配置对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
|
||||
|
|
@ -183,19 +191,51 @@
|
|||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限">
|
||||
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
|
||||
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
|
||||
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
|
||||
<el-tree
|
||||
class="tree-border"
|
||||
:data="menuOptions"
|
||||
show-checkbox
|
||||
ref="menu"
|
||||
node-key="id"
|
||||
:check-strictly="!form.menuCheckStrictly"
|
||||
empty-text="加载中,请稍候"
|
||||
:props="defaultProps"
|
||||
></el-tree>
|
||||
<!-- Modified table structure to show grouped menu permissions -->
|
||||
<el-table
|
||||
ref="menuTable"
|
||||
:data="groupedMenuData"
|
||||
:span-method="cellMergeMethod"
|
||||
class="menu-permission-table"
|
||||
border
|
||||
style="width: 100%; margin-top: 10px;"
|
||||
max-height="400"
|
||||
>
|
||||
<el-table-column label="系统功能编码" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.systemCode }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="一级目录" width="150" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.topLevelDirectory }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="菜单" width="200">
|
||||
<template slot-scope="scope">
|
||||
<div v-for="menu in scope.row.menus" :key="menu.menuId" style="margin-bottom: 5px;">
|
||||
<el-checkbox
|
||||
v-model="menu.checked"
|
||||
@change="handleMenuCheck(menu)"
|
||||
>
|
||||
{{ menu.menuName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="按钮授权" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div v-for="permission in scope.row.permissions" :key="permission.menuId" style="margin-bottom: 5px;">
|
||||
<el-checkbox
|
||||
v-model="permission.checked"
|
||||
@change="handlePermissionCheck(permission)"
|
||||
>
|
||||
{{ permission.menuName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||
|
|
@ -216,7 +256,7 @@
|
|||
<el-form-item label="权限字符">
|
||||
<el-input v-model="form.roleKey" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限范围">
|
||||
<!-- <el-form-item label="权限范围">
|
||||
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
|
||||
<el-option
|
||||
v-for="item in dataScopeOptions"
|
||||
|
|
@ -225,7 +265,7 @@
|
|||
:value="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form-item>-->
|
||||
<el-form-item label="数据权限" v-show="form.dataScope == 2">
|
||||
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
|
||||
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
|
||||
|
|
@ -311,6 +351,8 @@ export default {
|
|||
],
|
||||
// 菜单列表
|
||||
menuOptions: [],
|
||||
menuTableData: [],
|
||||
groupedMenuData: [],
|
||||
// 部门列表
|
||||
deptOptions: [],
|
||||
// 查询参数
|
||||
|
|
@ -338,7 +380,8 @@ export default {
|
|||
roleSort: [
|
||||
{ required: true, message: "角色顺序不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
},
|
||||
mergeMap: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
@ -359,22 +402,218 @@ export default {
|
|||
getMenuTreeselect() {
|
||||
menuTreeselect().then(response => {
|
||||
this.menuOptions = response.data
|
||||
this.convertMenuDataToGroupedTable(response.data)
|
||||
})
|
||||
},
|
||||
// 所有菜单节点数据
|
||||
getMenuAllCheckedKeys() {
|
||||
// 目前被选中的菜单节点
|
||||
let checkedKeys = this.$refs.menu.getCheckedKeys()
|
||||
// 半选中的菜单节点
|
||||
let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys()
|
||||
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
|
||||
return checkedKeys
|
||||
|
||||
convertMenuDataToGroupedTable(menuData) {
|
||||
const directories = menuData.filter(item => item.parentId === 0) // 目录
|
||||
const menus = menuData.filter(item => item.parentId !== 0 && item.menuType === 'C') // 菜单
|
||||
const buttons = menuData.filter(item => item.menuType === 'F') // 按钮
|
||||
|
||||
const grouped = []
|
||||
this.mergeMap = {}
|
||||
|
||||
// Process each directory
|
||||
directories.forEach(directory => {
|
||||
const directoryMenus = menus.filter(menu => menu.parentId === directory.menuId)
|
||||
|
||||
if (directoryMenus.length > 0) {
|
||||
const startIndex = grouped.length
|
||||
|
||||
directoryMenus.forEach((menu, index) => {
|
||||
const menuButtons = buttons.filter(button => button.parentId === menu.menuId)
|
||||
|
||||
grouped.push({
|
||||
systemCode: directory.menuId,
|
||||
topLevelDirectory: directory.menuName,
|
||||
directoryId: directory.menuId, // Added for merge tracking
|
||||
directoryChecked: false,
|
||||
menus: [{
|
||||
menuId: menu.menuId,
|
||||
menuName: menu.menuName,
|
||||
checked: false
|
||||
}],
|
||||
permissions: menuButtons.map(button => ({
|
||||
menuId: button.menuId,
|
||||
menuName: button.menuName,
|
||||
checked: false
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
this.mergeMap[directory.menuId] = {
|
||||
startIndex: startIndex,
|
||||
rowspan: directoryMenus.length
|
||||
}
|
||||
} else {
|
||||
// If directory has no menus, still show it
|
||||
const startIndex = grouped.length
|
||||
grouped.push({
|
||||
systemCode: directory.menuId,
|
||||
topLevelDirectory: directory.menuName,
|
||||
directoryId: directory.menuId,
|
||||
directoryChecked: false,
|
||||
menus: [],
|
||||
permissions: []
|
||||
})
|
||||
|
||||
this.mergeMap[directory.menuId] = {
|
||||
startIndex: startIndex,
|
||||
rowspan: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.groupedMenuData = grouped
|
||||
},
|
||||
// 所有部门节点数据
|
||||
|
||||
cellMergeMethod({ row, column, rowIndex, columnIndex }) {
|
||||
// Only merge the first two columns (系统功能编码 and 一级目录)
|
||||
if (columnIndex === 0 || columnIndex === 1) {
|
||||
const directoryId = row.directoryId
|
||||
const mergeInfo = this.mergeMap[directoryId]
|
||||
|
||||
if (mergeInfo && rowIndex === mergeInfo.startIndex) {
|
||||
// First row of the group - show the merged cell
|
||||
return {
|
||||
rowspan: mergeInfo.rowspan,
|
||||
colspan: 1
|
||||
}
|
||||
} else if (mergeInfo && rowIndex > mergeInfo.startIndex && rowIndex < mergeInfo.startIndex + mergeInfo.rowspan) {
|
||||
// Other rows in the group - hide the cell
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default - no merge
|
||||
return {
|
||||
rowspan: 1,
|
||||
colspan: 1
|
||||
}
|
||||
},
|
||||
|
||||
handleMenuCheck(menu) {
|
||||
if (menu.checked) {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
const menuItem = group.menus.find(m => m.menuId === menu.menuId)
|
||||
if (menuItem) {
|
||||
const directory = this.menuOptions.find(item =>
|
||||
item.parentId === 0 && item.menuId === group.directoryId
|
||||
)
|
||||
if (directory) {
|
||||
group.directoryChecked = true
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
group.permissions.forEach(permission => {
|
||||
const menuButtons = this.menuOptions.filter(item =>
|
||||
item.menuType === 'F' && item.parentId === menu.menuId
|
||||
)
|
||||
if (menuButtons.some(button => button.menuId === permission.menuId)) {
|
||||
permission.checked = false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const directoryGroup = this.groupedMenuData.find(group =>
|
||||
group.menus.some(m => m.menuId === menu.menuId)
|
||||
)
|
||||
if (directoryGroup) {
|
||||
const allMenusUnchecked = this.groupedMenuData
|
||||
.filter(g => g.directoryId === directoryGroup.directoryId)
|
||||
.every(g => g.menus.every(m => !m.checked))
|
||||
|
||||
if (allMenusUnchecked) {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
if (group.directoryId === directoryGroup.directoryId) {
|
||||
group.directoryChecked = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handlePermissionCheck(permission) {
|
||||
const parentMenu = this.menuOptions.find(item =>
|
||||
item.menuType === 'C' &&
|
||||
this.menuOptions.some(button =>
|
||||
button.menuType === 'F' &&
|
||||
button.parentId === item.menuId &&
|
||||
button.menuId === permission.menuId
|
||||
)
|
||||
)
|
||||
|
||||
if (parentMenu) {
|
||||
if (permission.checked) {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
const menuItem = group.menus.find(menu => menu.menuId === parentMenu.menuId)
|
||||
if (menuItem) {
|
||||
menuItem.checked = true
|
||||
group.directoryChecked = true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const allPermissionsUnchecked = this.groupedMenuData.every(group => {
|
||||
return group.permissions.every(perm => {
|
||||
const isRelatedButton = this.menuOptions.some(button =>
|
||||
button.menuType === 'F' &&
|
||||
button.parentId === parentMenu.menuId &&
|
||||
button.menuId === perm.menuId
|
||||
)
|
||||
return !isRelatedButton || !perm.checked
|
||||
})
|
||||
})
|
||||
|
||||
if (allPermissionsUnchecked) {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
const menuItem = group.menus.find(menu => menu.menuId === parentMenu.menuId)
|
||||
if (menuItem) {
|
||||
menuItem.checked = false
|
||||
|
||||
const allMenusUnchecked = this.groupedMenuData
|
||||
.filter(g => g.directoryId === group.directoryId)
|
||||
.every(g => g.menus.every(m => !m.checked))
|
||||
|
||||
if (allMenusUnchecked) {
|
||||
group.directoryChecked = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getMenuAllCheckedKeys() {
|
||||
const checkedIds = []
|
||||
this.groupedMenuData.forEach(group => {
|
||||
if (group.directoryChecked) {
|
||||
checkedIds.push(group.directoryId)
|
||||
}
|
||||
|
||||
group.menus.forEach(menu => {
|
||||
if (menu.checked) {
|
||||
checkedIds.push(menu.menuId)
|
||||
}
|
||||
})
|
||||
group.permissions.forEach(permission => {
|
||||
if (permission.checked) {
|
||||
checkedIds.push(permission.menuId)
|
||||
}
|
||||
})
|
||||
})
|
||||
return [...new Set(checkedIds)]
|
||||
},
|
||||
|
||||
getDeptAllCheckedKeys() {
|
||||
// 目前被选中的部门节点
|
||||
let checkedKeys = this.$refs.dept.getCheckedKeys()
|
||||
// 半选中的部门节点
|
||||
let halfCheckedKeys = this.$refs.dept.getHalfCheckedKeys()
|
||||
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
|
||||
return checkedKeys
|
||||
|
|
@ -383,6 +622,7 @@ export default {
|
|||
getRoleMenuTreeselect(roleId) {
|
||||
return roleMenuTreeselect(roleId).then(response => {
|
||||
this.menuOptions = response.menus
|
||||
this.convertMenuDataToGroupedTable(response.menus)
|
||||
return response
|
||||
})
|
||||
},
|
||||
|
|
@ -416,25 +656,32 @@ export default {
|
|||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
if (this.$refs.menu != undefined) {
|
||||
this.$refs.menu.setCheckedKeys([])
|
||||
}
|
||||
this.groupedMenuData.forEach(group => {
|
||||
group.directoryChecked = false
|
||||
group.menus.forEach(menu => {
|
||||
menu.checked = false
|
||||
})
|
||||
group.permissions.forEach(permission => {
|
||||
permission.checked = false
|
||||
})
|
||||
})
|
||||
|
||||
this.menuExpand = false,
|
||||
this.menuNodeAll = false,
|
||||
this.deptExpand = true,
|
||||
this.deptNodeAll = false,
|
||||
this.form = {
|
||||
roleId: undefined,
|
||||
roleName: undefined,
|
||||
roleKey: undefined,
|
||||
roleSort: 0,
|
||||
status: "0",
|
||||
menuIds: [],
|
||||
deptIds: [],
|
||||
menuCheckStrictly: true,
|
||||
deptCheckStrictly: true,
|
||||
remark: undefined
|
||||
}
|
||||
this.menuNodeAll = false,
|
||||
this.deptExpand = true,
|
||||
this.deptNodeAll = false,
|
||||
this.form = {
|
||||
roleId: undefined,
|
||||
roleName: undefined,
|
||||
roleKey: undefined,
|
||||
roleSort: 0,
|
||||
status: "0",
|
||||
menuIds: [],
|
||||
deptIds: [],
|
||||
menuCheckStrictly: true,
|
||||
deptCheckStrictly: true,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
|
|
@ -451,7 +698,7 @@ export default {
|
|||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.roleId)
|
||||
this.single = selection.length!=1
|
||||
this.single = selection.length != 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
// 更多操作触发
|
||||
|
|
@ -469,12 +716,7 @@ export default {
|
|||
},
|
||||
// 树权限(展开/折叠)
|
||||
handleCheckedTreeExpand(value, type) {
|
||||
if (type == 'menu') {
|
||||
let treeList = this.menuOptions
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value
|
||||
}
|
||||
} else if (type == 'dept') {
|
||||
if (type == 'dept') {
|
||||
let treeList = this.deptOptions
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
this.$refs.dept.store.nodesMap[treeList[i].id].expanded = value
|
||||
|
|
@ -483,17 +725,13 @@ export default {
|
|||
},
|
||||
// 树权限(全选/全不选)
|
||||
handleCheckedTreeNodeAll(value, type) {
|
||||
if (type == 'menu') {
|
||||
this.$refs.menu.setCheckedNodes(value ? this.menuOptions: [])
|
||||
} else if (type == 'dept') {
|
||||
if (type == 'dept') {
|
||||
this.$refs.dept.setCheckedNodes(value ? this.deptOptions: [])
|
||||
}
|
||||
},
|
||||
// 树权限(父子联动)
|
||||
handleCheckedTreeConnect(value, type) {
|
||||
if (type == 'menu') {
|
||||
this.form.menuCheckStrictly = value ? true: false
|
||||
} else if (type == 'dept') {
|
||||
if (type == 'dept') {
|
||||
this.form.deptCheckStrictly = value ? true: false
|
||||
}
|
||||
},
|
||||
|
|
@ -515,22 +753,56 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
roleMenu.then(res => {
|
||||
let checkedKeys = res.checkedKeys
|
||||
checkedKeys.forEach((v) => {
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.menu.setChecked(v, true ,false)
|
||||
})
|
||||
checkedKeys.forEach((menuId) => {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
const menuItem = group.menus.find(menu => menu.menuId === menuId)
|
||||
if (menuItem) {
|
||||
menuItem.checked = true
|
||||
}
|
||||
const permissionItem = group.permissions.find(permission => permission.menuId === menuId)
|
||||
if (permissionItem) {
|
||||
permissionItem.checked = true
|
||||
}
|
||||
})
|
||||
})
|
||||
this.applyParentChildLinkageOnLoad()
|
||||
})
|
||||
})
|
||||
})
|
||||
this.title = "修改角色"
|
||||
},
|
||||
/** 选择角色权限范围触发 */
|
||||
dataScopeSelectChange(value) {
|
||||
if(value !== '2') {
|
||||
this.$refs.dept.setCheckedKeys([])
|
||||
}
|
||||
|
||||
applyParentChildLinkageOnLoad() {
|
||||
this.groupedMenuData.forEach(group => {
|
||||
group.permissions.forEach(permission => {
|
||||
if (permission.checked) {
|
||||
const parentMenu = this.menuOptions.find(item =>
|
||||
item.menuType === 'C' &&
|
||||
this.menuOptions.some(button =>
|
||||
button.menuType === 'F' &&
|
||||
button.parentId === item.menuId &&
|
||||
button.menuId === permission.menuId
|
||||
)
|
||||
)
|
||||
|
||||
if (parentMenu) {
|
||||
this.groupedMenuData.forEach(innerGroup => {
|
||||
const menuItem = innerGroup.menus.find(menu => menu.menuId === parentMenu.menuId)
|
||||
if (menuItem) {
|
||||
menuItem.checked = true
|
||||
innerGroup.directoryChecked = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (group.menus.some(menu => menu.checked)) {
|
||||
group.directoryChecked = true
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 分配数据权限操作 */
|
||||
handleDataScope(row) {
|
||||
this.reset()
|
||||
|
|
@ -599,7 +871,32 @@ export default {
|
|||
this.download('system/role/export', {
|
||||
...this.queryParams
|
||||
}, `role_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
},
|
||||
|
||||
/** 查看角色人员操作 */
|
||||
handleViewUsers(row) {
|
||||
// 跳转到用户管理页面,并传递角色ID参数
|
||||
this.$router.push({
|
||||
path: '/system/user',
|
||||
query: {
|
||||
roleId: row.roleId,
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-permission-controls {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.menu-permission-table {
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.menu-permission-table .el-table__header {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,344 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="head-container">
|
||||
<el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" />
|
||||
</div>
|
||||
|
||||
<div class="head-container">
|
||||
<el-tree
|
||||
:data="deptOptions"
|
||||
:props="defaultProps"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
ref="tree"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<span class="custom-tree-node" slot-scope="{ node, data }">
|
||||
<span>{{ node.label }}</span>
|
||||
<span class="tree-node-operations">
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
@click.stop="() => handleAddDept(data)"
|
||||
v-hasPermi="['system:dept:add']"
|
||||
title="新增子部门"
|
||||
></el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-edit"
|
||||
@click.stop="() => handleEditDept(data)"
|
||||
v-hasPermi="['system:dept:edit']"
|
||||
title="修改部门"
|
||||
></el-button>
|
||||
<el-button
|
||||
v-if="data.parentId !== 0 && data.id !== 0"
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click.stop="() => handleDeleteDept(data)"
|
||||
v-hasPermi="['system:dept:remove']"
|
||||
title="删除部门"
|
||||
></el-button>
|
||||
</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</div>
|
||||
|
||||
<!-- 添加或修改部门对话框 -->
|
||||
<el-dialog :title="deptTitle" :visible.sync="deptOpen" width="600px" append-to-body>
|
||||
<el-form ref="deptForm" :model="deptForm" :rules="deptRules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="24" v-if="deptForm.parentId !== 0">
|
||||
<el-form-item label="上级部门" prop="parentId">
|
||||
<treeselect v-model="deptForm.parentId" :options="deptOptionsForDialog" :normalizer="normalizer" placeholder="选择上级部门" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<el-input v-model="deptForm.deptName" placeholder="请输入部门名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="显示排序" prop="orderNum">
|
||||
<el-input-number v-model="deptForm.orderNum" controls-position="right" :min="0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="leader">
|
||||
<el-input v-model="deptForm.leader" placeholder="请输入负责人" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="phone">
|
||||
<el-input v-model="deptForm.phone" placeholder="请输入联系电话" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="deptForm.email" placeholder="请输入邮箱" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部门状态">
|
||||
<el-radio-group v-model="deptForm.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitDeptForm">确 定</el-button>
|
||||
<el-button @click="cancelDept">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
|
||||
import Treeselect from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
|
||||
export default {
|
||||
name: "DeptTreeWithOperations",
|
||||
dicts: ['sys_normal_disable'],
|
||||
components: { Treeselect },
|
||||
props: {
|
||||
deptOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
defaultProps: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
children: "children",
|
||||
label: "label"
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deptName: undefined,
|
||||
deptOpen: false, // 是否显示部门弹出层
|
||||
deptTitle: "", // 部门弹出层标题
|
||||
deptForm: {}, // 部门表单数据
|
||||
deptOptionsForDialog: [], // 部门对话框中的部门树选项
|
||||
// 部门表单校验
|
||||
deptRules: {
|
||||
parentId: [
|
||||
{ required: true, message: "上级部门不能为空", trigger: "blur" }
|
||||
],
|
||||
deptName: [
|
||||
{ required: true, message: "部门名称不能为空", trigger: "blur" }
|
||||
],
|
||||
orderNum: [
|
||||
{ required: true, message: "显示排序不能为空", trigger: "blur" }
|
||||
],
|
||||
email: [
|
||||
{
|
||||
type: "email",
|
||||
message: "请输入正确的邮箱地址",
|
||||
trigger: ["blur", "change"]
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: "请输入正确的手机号码",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 根据名称筛选部门树
|
||||
deptName(val) {
|
||||
this.$refs.tree.filter(val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 转换部门数据结构 */
|
||||
normalizer(node) {
|
||||
if (node.children && !node.children.length) {
|
||||
delete node.children
|
||||
}
|
||||
return {
|
||||
id: node.deptId,
|
||||
label: node.deptName,
|
||||
children: node.children
|
||||
}
|
||||
},
|
||||
// 筛选节点
|
||||
filterNode(value, data) {
|
||||
if (!value) return true
|
||||
return data.label.indexOf(value) !== -1
|
||||
},
|
||||
// 节点单击事件
|
||||
handleNodeClick(data) {
|
||||
this.$emit('node-click', data)
|
||||
},
|
||||
/** 新增部门按钮操作 */
|
||||
handleAddDept(data) {
|
||||
this.resetDeptForm()
|
||||
this.deptForm.parentId = data.id
|
||||
this.deptOpen = true
|
||||
this.deptTitle = "添加部门"
|
||||
listDept().then(response => {
|
||||
this.deptOptionsForDialog = this.handleTree(response.data, "deptId")
|
||||
})
|
||||
},
|
||||
/** 修改部门按钮操作 */
|
||||
handleEditDept(data) {
|
||||
this.resetDeptForm()
|
||||
const deptId = data.id
|
||||
getDept(deptId).then(response => {
|
||||
this.deptForm = response.data
|
||||
this.deptOpen = true
|
||||
this.deptTitle = "修改部门"
|
||||
listDeptExcludeChild(deptId).then(response => {
|
||||
this.deptOptionsForDialog = this.handleTree(response.data, "deptId")
|
||||
if (this.deptOptionsForDialog.length == 0) {
|
||||
const noResultsOptions = { deptId: this.deptForm.parentId, deptName: this.deptForm.parentName, children: [] }
|
||||
this.deptOptionsForDialog.push(noResultsOptions)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/** 删除部门按钮操作 */
|
||||
handleDeleteDept(data) {
|
||||
const deptName = data.label
|
||||
const deptId = data.id
|
||||
this.$modal.confirm('是否确认删除名称为"' + deptName + '"的数据项?').then(function() {
|
||||
return delDept(deptId)
|
||||
}).then(() => {
|
||||
this.$emit('dept-deleted')
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 部门表单重置 */
|
||||
resetDeptForm() {
|
||||
this.deptForm = {
|
||||
deptId: undefined,
|
||||
parentId: undefined,
|
||||
deptName: undefined,
|
||||
orderNum: undefined,
|
||||
leader: undefined,
|
||||
phone: undefined,
|
||||
email: undefined,
|
||||
status: "0"
|
||||
}
|
||||
this.resetForm("deptForm")
|
||||
},
|
||||
// 取消部门表单
|
||||
cancelDept() {
|
||||
this.deptOpen = false
|
||||
this.resetDeptForm()
|
||||
},
|
||||
/** 提交部门表单 */
|
||||
submitDeptForm() {
|
||||
this.$refs["deptForm"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.deptForm.deptId != undefined) {
|
||||
updateDept(this.deptForm).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.deptOpen = false
|
||||
this.$emit('dept-updated')
|
||||
})
|
||||
} else {
|
||||
addDept(this.deptForm).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.deptOpen = false
|
||||
this.$emit('dept-added')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 工具方法:处理树形数据
|
||||
handleTree(data, id, parentId, children) {
|
||||
let config = {
|
||||
id: id || 'id',
|
||||
parentId: parentId || 'parentId',
|
||||
childrenList: children || 'children'
|
||||
}
|
||||
|
||||
var childrenListMap = {}
|
||||
var nodeIds = {}
|
||||
var tree = []
|
||||
|
||||
for (let d of data) {
|
||||
let parentId = d[config.parentId]
|
||||
if (childrenListMap[parentId] == null) {
|
||||
childrenListMap[parentId] = []
|
||||
}
|
||||
nodeIds[d[config.id]] = d
|
||||
childrenListMap[parentId].push(d)
|
||||
}
|
||||
|
||||
for (let d of data) {
|
||||
let parentId = d[config.parentId]
|
||||
if (nodeIds[parentId] == null) {
|
||||
tree.push(d)
|
||||
}
|
||||
}
|
||||
|
||||
for (let t of tree) {
|
||||
adaptToChildrenList(t)
|
||||
}
|
||||
|
||||
function adaptToChildrenList(o) {
|
||||
if (childrenListMap[o[config.id]] !== null) {
|
||||
o[config.childrenList] = childrenListMap[o[config.id]]
|
||||
}
|
||||
if (o[config.childrenList]) {
|
||||
for (let c of o[config.childrenList]) {
|
||||
adaptToChildrenList(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.tree-node-operations {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-tree-node:hover .tree-node-operations {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tree-node-operations .el-button {
|
||||
padding: 4px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,12 +5,16 @@
|
|||
<!--部门数据-->
|
||||
<pane size="16">
|
||||
<el-col>
|
||||
<div class="head-container">
|
||||
<el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px" />
|
||||
</div>
|
||||
<div class="head-container">
|
||||
<el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" />
|
||||
</div>
|
||||
<!-- 使用新的部门树组件 -->
|
||||
<dept-tree-with-operations
|
||||
:dept-options="deptOptions"
|
||||
:default-props="defaultProps"
|
||||
@node-click="handleNodeClick"
|
||||
@dept-added="refreshDeptTree"
|
||||
@dept-updated="refreshDeptTree"
|
||||
@dept-deleted="refreshDeptTree"
|
||||
ref="deptTree"
|
||||
/>
|
||||
</el-col>
|
||||
</pane>
|
||||
<!--用户数据-->
|
||||
|
|
@ -28,6 +32,20 @@
|
|||
<el-option v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 添加角色下拉选择框 -->
|
||||
<el-form-item label="角色" prop="roleId">
|
||||
<el-select v-model="queryParams.roleId" placeholder="请选择角色" clearable style="width: 240px">
|
||||
<el-option
|
||||
v-for="role in roleOptions"
|
||||
:key="role.roleId"
|
||||
:label="role.roleName"
|
||||
:value="role.roleId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker v-model="dateRange" style="width: 240px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
|
@ -202,16 +220,25 @@
|
|||
|
||||
<script>
|
||||
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect } from "@/api/system/user"
|
||||
import { listRole } from "@/api/system/role" // 导入角色API
|
||||
import { getToken } from "@/utils/auth"
|
||||
import Treeselect from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
import { Splitpanes, Pane } from "splitpanes"
|
||||
import "splitpanes/dist/splitpanes.css"
|
||||
|
||||
// 导入部门树组件
|
||||
import DeptTreeWithOperations from "./dept"
|
||||
|
||||
export default {
|
||||
name: "User",
|
||||
dicts: ['sys_normal_disable', 'sys_user_sex'],
|
||||
components: { Treeselect, Splitpanes, Pane },
|
||||
components: {
|
||||
Treeselect,
|
||||
Splitpanes,
|
||||
Pane,
|
||||
DeptTreeWithOperations
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
|
|
@ -274,8 +301,11 @@ export default {
|
|||
userName: undefined,
|
||||
phonenumber: undefined,
|
||||
status: undefined,
|
||||
deptId: undefined
|
||||
deptId: undefined,
|
||||
roleId: undefined
|
||||
},
|
||||
// 用于显示角色名称的变量
|
||||
roleName: "",
|
||||
// 列信息
|
||||
columns: {
|
||||
userId: { label: '用户编号', visible: true },
|
||||
|
|
@ -314,18 +344,25 @@ export default {
|
|||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 根据名称筛选部门树
|
||||
deptName(val) {
|
||||
this.$refs.tree.filter(val)
|
||||
}
|
||||
this.$refs.deptTree.filter(val)
|
||||
},
|
||||
// 监听路由变化
|
||||
'$route'(to, from) {
|
||||
this.handleRouteParams()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getDeptTree()
|
||||
this.getRoleList().then(() => {
|
||||
this.handleRouteParams()
|
||||
})
|
||||
this.getConfigKey("sys.user.initPassword").then(response => {
|
||||
this.initPassword = response.msg
|
||||
})
|
||||
|
|
@ -341,13 +378,52 @@ export default {
|
|||
}
|
||||
)
|
||||
},
|
||||
/** 获取角色列表 */
|
||||
getRoleList() {
|
||||
listRole().then(response => {
|
||||
this.roleOptions = response.rows
|
||||
})
|
||||
},
|
||||
|
||||
/** 查询部门下拉树结构 */
|
||||
getDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data
|
||||
this.enabledDeptOptions = this.filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
|
||||
// 部门树加载完成后,处理路由参数
|
||||
this.handleRouteParams()
|
||||
})
|
||||
},
|
||||
|
||||
// 处理路由参数
|
||||
handleRouteParams() {
|
||||
const deptId = this.$route.query.deptId
|
||||
if (deptId && this.deptOptions && this.deptOptions.length > 0) {
|
||||
// 设置部门ID查询参数
|
||||
this.queryParams.deptId = deptId
|
||||
|
||||
// 选中对应的部门节点
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.deptTree && this.$refs.deptTree.$refs.tree) {
|
||||
this.$refs.deptTree.$refs.tree.setCurrentKey(parseInt(deptId))
|
||||
// 触发节点点击事件,确保查询该部门的用户
|
||||
const node = this.$refs.deptTree.$refs.tree.getNode(parseInt(deptId))
|
||||
if (node) {
|
||||
this.handleNodeClick(node.data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理角色ID参数 - 确保角色选项已加载
|
||||
const roleId = this.$route.query.roleId
|
||||
if (roleId && this.roleOptions) {
|
||||
const roleIdNum = parseInt(roleId)
|
||||
this.queryParams.roleId = roleIdNum
|
||||
this.handleQuery()
|
||||
}
|
||||
},
|
||||
|
||||
// 过滤禁用的部门
|
||||
filterDisabledDept(deptList) {
|
||||
return deptList.filter(dept => {
|
||||
|
|
@ -370,6 +446,7 @@ export default {
|
|||
this.queryParams.deptId = data.id
|
||||
this.handleQuery()
|
||||
},
|
||||
|
||||
// 用户状态修改
|
||||
handleStatusChange(row) {
|
||||
let text = row.status === "0" ? "启用" : "停用"
|
||||
|
|
@ -414,7 +491,14 @@ export default {
|
|||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.queryParams.deptId = undefined
|
||||
this.$refs.tree.setCurrentKey(null)
|
||||
|
||||
this.queryParams.roleId = undefined
|
||||
this.roleName = ""
|
||||
|
||||
// 清除树节点的选中状态
|
||||
if (this.$refs.deptTree && this.$refs.deptTree.$refs.tree) {
|
||||
this.$refs.deptTree.$refs.tree.setCurrentKey(null)
|
||||
}
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
|
|
@ -476,10 +560,10 @@ export default {
|
|||
}
|
||||
},
|
||||
}).then(({ value }) => {
|
||||
resetUserPwd(row.userId, value).then(response => {
|
||||
this.$modal.msgSuccess("修改成功,新密码是:" + value)
|
||||
})
|
||||
}).catch(() => {})
|
||||
resetUserPwd(row.userId, value).then(response => {
|
||||
this.$modal.msgSuccess("修改成功,新密码是:" + value)
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 分配角色操作 */
|
||||
handleAuthRole: function(row) {
|
||||
|
|
@ -547,7 +631,12 @@ export default {
|
|||
// 提交上传文件
|
||||
submitFileForm() {
|
||||
this.$refs.upload.submit()
|
||||
}
|
||||
},
|
||||
// 刷新部门树
|
||||
refreshDeptTree() {
|
||||
this.getDeptTree()
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue