大屏产品中心-改成访问演示

This commit is contained in:
lSun 2026-01-05 10:59:45 +08:00
parent 5bb63eeab5
commit 4ad7d27634
13 changed files with 589 additions and 139 deletions

View File

@ -1,7 +1,15 @@
import router from '@/router'
import { MessageBox } from 'element-ui'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken,setPermissions,removePermissions, } from '@/utils/auth'
import {
getToken,
setToken,
removeToken,
setPermissions,
removePermissions,
setUserName,
removeUserName,
} from '@/utils/auth'
import { isHttp, isEmpty } from '@/utils/validate'
import defAva from '@/assets/images/profile.jpg'
import { encryptWithSM4 } from '@/utils/sm'
@ -56,6 +64,7 @@ const user = {
.then((res) => {
setToken(res.token)
setPermissions(res.authorities)
setUserName(userInfo.username.trim())
commit('SET_TOKEN', res.token)
resolve()
})
@ -144,6 +153,7 @@ const user = {
commit('SET_PERMISSIONS', [])
removeToken()
removePermissions()
removeUserName()
resolve()
})
.catch((error) => {
@ -158,6 +168,7 @@ const user = {
commit('SET_TOKEN', '')
removeToken()
removePermissions()
removeUserName()
resolve()
})
},

View File

@ -4,6 +4,8 @@ const TokenKey = 'Admin-Token'
const Permissions = '0'
const UserName = 'Admin-UserName'
export function getToken() {
return Cookies.get(TokenKey)
}
@ -28,3 +30,15 @@ export function setPermissions(permissions) {
export function removePermissions() {
return Cookies.remove(Permissions)
}
export function getUserName() {
return Cookies.get(UserName)
}
export function setUserName( name){
return Cookies.set(UserName, name)
}
export function removeUserName() {
return Cookies.remove(UserName)
}

View File

@ -76,9 +76,9 @@ service.interceptors.request.use(
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = '数据正在处理,请勿重复提交'
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
// const message = '数据正在处理,请勿重复提交'
// console.warn(`[${s_url}]: ` + message)
// return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}

View File

@ -85,9 +85,9 @@ service.interceptors.request.use(
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = '数据正在处理,请勿重复提交'
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
// const message = '数据正在处理,请勿重复提交'
// console.warn(`[${s_url}]: ` + message)
// return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}

View File

@ -11,7 +11,7 @@ export function isPathMatch(pattern, path) {
}
/**
* 判断value字符串是否为空
* 判断value字符串是否为空
* @param {string} value
* @returns {Boolean}
*/
@ -23,7 +23,7 @@ export function isEmpty(value) {
}
/**
* 判断url是否是http或https
* 判断url是否是http或https
* @param {string} url
* @returns {Boolean}
*/
@ -37,7 +37,7 @@ export function isHttp(url) {
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
return /^(http?:|mailto:|tel:)/.test(path)
}
/**
@ -54,7 +54,7 @@ export function validUsername(str) {
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
const reg = /^(http?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}

View File

@ -0,0 +1,400 @@
<template>
<div class="header_new">
<div class="header-content">
<div class="header-left">
<!-- <div class="logo-section" @click="handleLogoClick">-->
<div class="logo-section">
<img
src="../../../assets/logo/logo.png"
alt="Logo"
class="logo"
/>
<span class="platform-title">{{ platformTitle }}</span>
</div>
</div>
<div class="header-nav">
<div
class="nav-item"
:key="item.routePath"
v-for="item in navItems"
@click="handleNavClick(item)"
:class="{ active: activeNav === item.routePath }"
>
<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 && activeNav !== 'DocCenter'"
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>
<el-dropdown
trigger="hover"
class="avatar-container right-menu-item hover-effect"
>
<div class="avatar-wrapper">
<img :src="avatar" class="user-avatar" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="handleLogoClick">
后台管理
</el-dropdown-item>
<el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { getPermissions } from '../../../utils/auth'
export default {
name: 'NavBar',
props: {
logoUrl: {
type: String,
default: '/img/psp/productCenter/logo.png',
},
platformTitle: {
type: String,
default: '公共服务平台',
},
showRightSection: {
type: Boolean,
default: true,
},
showSearch: {
type: Boolean,
default: true,
},
showUserAvatar: {
type: Boolean,
default: true,
},
userAvatar: {
type: String,
default: '',
},
searchKeyword: {
type: String,
default: '',
},
},
computed: {
...mapGetters(['avatar']),
// activeNav
activeNav() {
//
const routeName = this.$route.name
const routePath = this.$route.path
//
if (routeName) {
const matchedItem = this.navItems.find(
(item) => item.routePath === routeName,
)
if (matchedItem) {
return matchedItem.routePath
}
}
//
if (routePath) {
const matchedItem = this.navItems.find((item) =>
routePath.includes(item.routePath.toLowerCase()),
)
if (matchedItem) {
return matchedItem.routePath
}
}
//
return 'ProductCenter'
},
setting: {
get() {
return this.$store.state.settings.showSettings
},
},
topNav: {
get() {
return this.$store.state.settings.topNav
},
},
},
data() {
return {
// activeNav: 'ProductCenter', //
userAvatarUrl: this.userAvatar,
internalSearchKeyword: this.searchKeyword,
permissions: getPermissions(),
navItems: [
{
label: '产品中心',
routePath: 'ProductCenter',
icon: require('../../../assets/images/productCenter/products.png'),
},
/*{
label: '公共组件',
routePath: 'CommonCom',
icon: require('../../../assets/images/productCenter/components.png'),
},*/
{
label: '宣传物料',
routePath: 'ProMaterials',
icon: require('../../../assets/images/productCenter/materials.png'),
},
/*{
label: '文档中心',
routePath: 'DocCenter',
icon: require('../../../assets/images/productCenter/docs.png'),
},*/
],
}
},
watch: {
$route() {
// activeNav
},
searchKeyword(newVal) {
this.internalSearchKeyword = newVal
},
userAvatar(newVal) {
this.userAvatarUrl = newVal
},
internalSearchKeyword(newVal) {
//
this.$emit('update:searchKeyword', newVal)
},
},
methods: {
handleNavClick(item) {
// this.activeNav = item.routePath
this.$router.push({
name: item.routePath,
})
},
handleSearch() {
console.log('执行搜索:', this.internalSearchKeyword)
//
this.$emit('search', this.internalSearchKeyword.trim())
},
//
handleLogoClick() {
if (this.permissions.includes(2)) {
this.$router.push('/')
} else {
//
this.$message.error('没有后台权限,请联系管理员')
}
},
logout() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.$store.dispatch('LogOut').then(() => {
location.href =
process.env.VUE_APP_ENV === 'production'
? '/pubSerPlatform/index'
: '/index'
})
})
.catch(() => {})
},
},
}
</script>
<style scoped lang="scss">
.header_new {
background-image: url('../../../assets/images/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;
//cursor: pointer;
cursor: context-menu;
}
.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;
}
}
.avatar-container {
margin-right: 0px;
padding-right: 0px;
.avatar-wrapper {
margin-top: 10px;
position: relative;
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 50%;
}
.user-nickname {
position: relative;
bottom: 10px;
font-size: 14px;
font-weight: bold;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
</style>

View File

@ -1,7 +1,15 @@
<template>
<div class="platform-container">
<!-- 使用导航栏组件 -->
<NavBar2
v-if="isDemoUser"
@search="handleSearch"
:searchKeyword.sync="searchKeyword"
:showSearch="showSearch"
/>
<NavBar
v-else
@search="handleSearch"
:searchKeyword.sync="searchKeyword"
:showSearch="showSearch"
@ -17,17 +25,32 @@
<script>
//
import NavBar from './components/navBar.vue'
//
import NavBar2 from './components/navBar2.vue'
import {getUserName} from '../../utils/auth'
export default {
name: 'PublicService',
components: {
NavBar,
NavBar2
},
data() {
return {
searchKeyword: '' //
searchKeyword: '', //
isDemoUser: false //
}
},
mounted() {
const userName = getUserName();
//
if (userName) {
this.isDemoUser = userName.includes('演示')
}
},
computed: {
//
showSearch() {

View File

@ -6,6 +6,7 @@ export default {
name: 'PublicService_1',
mounted() {
const permissions = getPermissions();
console.log("检查权限值",permissions);
//
if (permissions) {
//

View File

@ -12,7 +12,7 @@
{{ item.label }}
</div>
<div class="category-section">
<!-- <div class="category-section">
<div class="section-title">分类</div>
<div class="checkbox-item">
<el-checkbox
@ -33,7 +33,7 @@
产品视频
</el-checkbox>
</div>
</div>
</div>-->
</div>

View File

@ -147,10 +147,10 @@ export default {
} else if (!url.startsWith('http://') && !url.startsWith('https://')) {
// URL http:// https:// https://
if (url.startsWith('www.')) {
url = 'https://' + url
url = 'http://' + url
} else {
// https://
url = 'https://' + url
// http://
url = 'http://' + url
}
}
window.open(url, '_blank')

View File

@ -121,12 +121,12 @@ export default {
let url = service.linkUrl
// URL http:// https:// https://
if (!url.startsWith('http://') && !url.startsWith('https://')) {
// URL www. https://
// URL www. http://
if (url.startsWith('www.')) {
url = 'https://' + url
url = 'http://' + url
} else {
// https://
url = 'https://' + url
// http://
url = 'http://' + url
}
}

View File

@ -235,10 +235,10 @@ export default {
} else if (!url.startsWith('http://') && !url.startsWith('https://')) {
// URL http:// https:// https://
if (url.startsWith('www.')) {
url = 'https://' + url
url = 'http://' + url
} else {
// https://
url = 'https://' + url
// http://
url = 'http://' + url
}
}

View File

@ -2,13 +2,13 @@
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
return path.join(__dirname, dir)
}
const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '博诺思公共微服务平台' // 网页标题
const baseUrl = 'http://192.168.0.38:58080' // 后端接口
const baseUrl = 'http://192.168.0.50:58080/bnscloud/' // 后端接口d
const port = process.env.port || process.env.npm_config_port || 80 // 端口
@ -16,120 +16,121 @@ const port = process.env.port || process.env.npm_config_port || 80 // 端口
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 这里只列一部分,具体配置参考文档
module.exports = {
// 部署生产环境和开发环境下的URL。
// 默认情况下Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === 'production' ? '/pubSerPlatform' : '/',
// 在npm run build 或 yarn build 时 生成文件的目录名称要和baseUrl的生产环境路径一致默认dist
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
// 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
transpileDependencies: ['quill'],
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
// target: 'http://192.168.0.7:58080/bnscloud', // 产线环境
target: 'http://192.168.0.14:1999/bnscloud', // 测试环境
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '',
},
},
// springdoc proxy
'^/v3/api-docs/(.*)': {
target: baseUrl,
changeOrigin: true,
},
// 部署生产环境和开发环境下的URL。
// 默认情况下Vue CLI 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === 'production' ? '/pubSerPlatform' : '/',
// 在npm run build 或 yarn build 时 生成文件的目录名称要和baseUrl的生产环境路径一致默认dist
outputDir: 'dist',
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
assetsDir: 'static',
// 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
transpileDependencies: ['quill'],
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
// target: 'http://192.168.0.7:58080/bnscloud', // 产线环境
target: 'http://192.168.0.14:1999/bnscloud', // 测试环境
// target: 'http://192.168.0.50:58080/bnscloud', // 本地环境
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '',
},
disableHostCheck: true,
},
// springdoc proxy
'^/v3/api-docs/(.*)': {
target: baseUrl,
changeOrigin: true,
},
},
css: {
loaderOptions: {
sass: {
sassOptions: { outputStyle: 'expanded' },
},
disableHostCheck: true,
},
css: {
loaderOptions: {
sass: {
sassOptions: {outputStyle: 'expanded'},
},
},
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src'),
},
},
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式
filename: '[path][base].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩
deleteOriginalAssets: false, // 压缩后删除原文件
}),
],
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
.end()
config.when(process.env.NODE_ENV !== 'development', (config) => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/,
},
])
.end()
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial', // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true,
},
},
},
configureWebpack: {
name: name,
resolve: {
alias: {
'@': resolve('src'),
},
},
plugins: [
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
new CompressionPlugin({
cache: false, // 不启用文件缓存
test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式
filename: '[path][base].gz[query]', // 压缩后的文件名
algorithm: 'gzip', // 使用gzip压缩
minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩
deleteOriginalAssets: false, // 压缩后删除原文件
}),
],
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module.rule('svg').exclude.add(resolve('src/assets/icons')).end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
})
.end()
config.when(process.env.NODE_ENV !== 'development', (config) => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/,
},
])
.end()
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial', // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true,
},
},
})
config.optimization.runtimeChunk('single')
})
},
})
config.optimization.runtimeChunk('single')
})
},
}