登录接口加密 产品中心等页面完善
This commit is contained in:
parent
e89086eb42
commit
ad83188c6f
|
|
@ -39,6 +39,7 @@
|
|||
"nprogress": "0.2.0",
|
||||
"quill": "2.0.2",
|
||||
"screenfull": "5.0.2",
|
||||
"sm-crypto": "^0.3.13",
|
||||
"sortablejs": "1.10.2",
|
||||
"splitpanes": "2.4.1",
|
||||
"vue": "2.6.12",
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
export function login(username, password, code, uuid, loginType) {
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
uuid,
|
||||
loginType,
|
||||
}
|
||||
return request({
|
||||
url: '/login',
|
||||
headers: {
|
||||
isToken: false,
|
||||
repeatSubmit: false
|
||||
repeatSubmit: false,
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -24,10 +25,10 @@ export function register(data) {
|
|||
return request({
|
||||
url: '/register',
|
||||
headers: {
|
||||
isToken: false
|
||||
isToken: false,
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ export function register(data) {
|
|||
export function getInfo() {
|
||||
return request({
|
||||
url: '/getInfo',
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ export function getInfo() {
|
|||
export function logout() {
|
||||
return request({
|
||||
url: '/logout',
|
||||
method: 'post'
|
||||
method: 'post',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -52,9 +53,18 @@ export function getCodeImg() {
|
|||
return request({
|
||||
url: '/captchaImage',
|
||||
headers: {
|
||||
isToken: false
|
||||
isToken: false,
|
||||
},
|
||||
method: 'get',
|
||||
timeout: 20000
|
||||
timeout: 20000,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取短信验证码
|
||||
export function getPhoneCodeApi(data) {
|
||||
return request({
|
||||
url: '/sendPhone',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import request from '@/utils/request'
|
||||
import request_formdata from '@/utils/request_formdata'
|
||||
|
||||
// 查询产品中心列表
|
||||
export function getProductCenterListAPI(data) {
|
||||
|
|
@ -9,3 +8,12 @@ export function getProductCenterListAPI(data) {
|
|||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询产品中心详情
|
||||
export function getProductCenterDetailAPI(data) {
|
||||
return request({
|
||||
url: '/product/screen/getProductDetails',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.el-image {
|
||||
border-radius: 5px;
|
||||
// border-radius: 5px;
|
||||
background-color: #ebeef5;
|
||||
box-shadow: 0 0 5px 1px #ccc;
|
||||
// box-shadow: 0 0 5px 1px #ccc;
|
||||
::v-deep .el-image__inner {
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -5,35 +5,39 @@ let loadingInstance
|
|||
export default {
|
||||
// 消息提示
|
||||
msg(content) {
|
||||
Message.closeAll()
|
||||
Message.info(content)
|
||||
},
|
||||
// 错误消息
|
||||
msgError(content) {
|
||||
Message.closeAll()
|
||||
Message.error(content)
|
||||
},
|
||||
// 成功消息
|
||||
msgSuccess(content) {
|
||||
Message.closeAll()
|
||||
Message.success(content)
|
||||
},
|
||||
// 警告消息
|
||||
msgWarning(content) {
|
||||
Message.closeAll()
|
||||
Message.warning(content)
|
||||
},
|
||||
// 弹出提示
|
||||
alert(content) {
|
||||
MessageBox.alert(content, "系统提示")
|
||||
MessageBox.alert(content, '系统提示')
|
||||
},
|
||||
// 错误提示
|
||||
alertError(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'error' })
|
||||
MessageBox.alert(content, '系统提示', { type: 'error' })
|
||||
},
|
||||
// 成功提示
|
||||
alertSuccess(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'success' })
|
||||
MessageBox.alert(content, '系统提示', { type: 'success' })
|
||||
},
|
||||
// 警告提示
|
||||
alertWarning(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'warning' })
|
||||
MessageBox.alert(content, '系统提示', { type: 'warning' })
|
||||
},
|
||||
// 通知提示
|
||||
notify(content) {
|
||||
|
|
@ -53,18 +57,18 @@ export default {
|
|||
},
|
||||
// 确认窗体
|
||||
confirm(content) {
|
||||
return MessageBox.confirm(content, "系统提示", {
|
||||
return MessageBox.confirm(content, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: "warning",
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
// 提交内容
|
||||
prompt(content) {
|
||||
return MessageBox.prompt(content, "系统提示", {
|
||||
return MessageBox.prompt(content, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: "warning",
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
// 打开遮罩层
|
||||
|
|
@ -72,12 +76,12 @@ export default {
|
|||
loadingInstance = Loading.service({
|
||||
lock: true,
|
||||
text: content,
|
||||
spinner: "el-icon-loading",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
})
|
||||
},
|
||||
// 关闭遮罩层
|
||||
closeLoading() {
|
||||
loadingInstance.close()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import router from '@/router'
|
||||
import { MessageBox, } from 'element-ui'
|
||||
import { MessageBox } from 'element-ui'
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
import { isHttp, isEmpty } from '@/utils/validate'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
import { encryptWithSM4 } from '@/utils/sm'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
|
|
@ -13,7 +14,7 @@ const user = {
|
|||
nickName: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
permissions: [],
|
||||
},
|
||||
|
||||
mutations: {
|
||||
|
|
@ -37,22 +38,27 @@ const user = {
|
|||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const username = encryptWithSM4(userInfo.username.trim())
|
||||
const password = encryptWithSM4(userInfo.password)
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
const loginType = userInfo.loginType
|
||||
|
||||
console.log(userInfo, 'userInfo')
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
login(username, password, code, uuid, loginType)
|
||||
.then((res) => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
|
@ -61,13 +67,17 @@ const user = {
|
|||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
getInfo()
|
||||
.then((res) => {
|
||||
const user = res.user
|
||||
let avatar = user.avatar || ""
|
||||
let avatar = user.avatar || ''
|
||||
if (!isHttp(avatar)) {
|
||||
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
|
||||
avatar = isEmpty(avatar)
|
||||
? defAva
|
||||
: process.env.VUE_APP_BASE_API + avatar
|
||||
}
|
||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
} else {
|
||||
|
|
@ -78,19 +88,46 @@ const user = {
|
|||
commit('SET_NICK_NAME', user.nickName)
|
||||
commit('SET_AVATAR', avatar)
|
||||
/* 初始密码提示 */
|
||||
if(res.isDefaultModifyPwd) {
|
||||
MessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
if (res.isDefaultModifyPwd) {
|
||||
MessageBox.confirm(
|
||||
'您的密码还是初始密码,请修改密码!',
|
||||
'安全提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
router.push({
|
||||
name: 'Profile',
|
||||
params: { activeTab: 'resetPwd' },
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
/* 过期密码提示 */
|
||||
if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
|
||||
MessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
|
||||
}).catch(() => {})
|
||||
if (!res.isDefaultModifyPwd && res.isPasswordExpired) {
|
||||
MessageBox.confirm(
|
||||
'您的密码已过期,请尽快修改密码!',
|
||||
'安全提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
router.push({
|
||||
name: 'Profile',
|
||||
params: { activeTab: 'resetPwd' },
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
|
@ -99,13 +136,15 @@ const user = {
|
|||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
logout(state.token)
|
||||
.then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
|
@ -113,13 +152,13 @@ const user = {
|
|||
|
||||
// 前端 登出
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// SM 配置
|
||||
export const SM_CONFIG = {
|
||||
SALT: '2cc0c5f9f1749f1632efa9f63e902323', // SM3 盐值(16 字节)
|
||||
SM4_KEY:"78d1295afa99449b99d6f83820e6965c", // SM4 对称加密密钥
|
||||
SM4_SALT:"f555adf6c01d0ab0761e626a2dae34a2",
|
||||
SM2_PUBLIC_KEY: 'your-public-key', // SM2 公钥
|
||||
SM2_PRIVATE_KEY: 'your-private-key' // SM2 私钥
|
||||
}
|
||||
// AES 配置
|
||||
export const AES_CONFIG = {
|
||||
AES_KEY: 'zhgd@bonus@zhgd@bonus@1234567890', // AES key值
|
||||
AES_IV: '1234567812345678' // AES 偏移量
|
||||
}
|
||||
|
||||
export function generateUUID() {
|
||||
// 使用当前时间戳和随机数生成一个 UUID
|
||||
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0; // 生成随机数
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8); // 根据 UUID 规范生成相应的值
|
||||
return v.toString(16); // 转换为十六进制
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// src/utils/encryption.js
|
||||
import { sm2, sm3, sm4 } from 'sm-crypto'
|
||||
// 配置项,例如盐值、SM2 公私钥、SM4 密钥
|
||||
import { SM_CONFIG } from './configure'
|
||||
import SM4 from 'sm-crypto/src/sm4'
|
||||
import { hexToArray } from 'sm-crypto/src/sm2/utils'
|
||||
|
||||
// SM3 哈希
|
||||
export function hashSM3(text) {
|
||||
// 对数据进行哈希计算
|
||||
return sm3(text)
|
||||
}
|
||||
|
||||
// 使用 SM3 进行哈希并加入盐值
|
||||
export function hashWithSM3AndSalt(text) {
|
||||
// 将文本和盐值拼接在一起
|
||||
const textWithSalt = SM_CONFIG.SALT + text
|
||||
// 使用 SM3 进行哈希
|
||||
return hashSM3(textWithSalt)
|
||||
}
|
||||
|
||||
// SM2 加密
|
||||
export function encryptWithSM2(text) {
|
||||
// SM2 公钥加密
|
||||
return sm2.doEncrypt(text, SM_CONFIG.SM2_PUBLIC_KEY)
|
||||
}
|
||||
|
||||
// SM2 解密
|
||||
export function decryptWithSM2(encryptedText) {
|
||||
// SM2 私钥解密
|
||||
return sm2.doDecrypt(encryptedText, SM_CONFIG.SM2_PRIVATE_KEY)
|
||||
}
|
||||
/**
|
||||
* 加密函数
|
||||
* @param {string} plainText
|
||||
* @returns {string} 加密后的密文(Hex 编码格式)
|
||||
*/
|
||||
export function encryptWithSM4(plainText) {
|
||||
return sm4.encrypt(plainText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5',iv:SM_CONFIG.SM4_SALT});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密函数
|
||||
* @param {string} cipherText
|
||||
* @returns {string} 解密后的明文
|
||||
*/
|
||||
export function decryptWithSM4(cipherText){
|
||||
return SM4.decrypt(cipherText, SM_CONFIG.SM4_KEY,{ mode: 'cbc', padding: 'pkcs#5' ,iv:SM_CONFIG.SM4_SALT});
|
||||
}
|
||||
|
||||
|
|
@ -338,12 +338,12 @@ export default {
|
|||
handleCancel() {
|
||||
// 重置基本信息表单
|
||||
this.$refs.addAndEditForm?.resetFields()
|
||||
// 重置所有案例表单
|
||||
this.editableTabs.forEach((tab) => {
|
||||
this.$refs[`caseForm_${tab.name}`]?.resetFields()
|
||||
})
|
||||
// 回到第一个Tab
|
||||
this.currentTab = '1'
|
||||
// // 重置所有案例表单
|
||||
// this.editableTabs.forEach((tab) => {
|
||||
// this.$refs[`caseForm_${tab.name}`]?.resetFields()
|
||||
// })
|
||||
// // 回到第一个Tab
|
||||
// this.currentTab = '1'
|
||||
|
||||
this.$emit('closeDialog', false)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,21 +19,15 @@
|
|||
<!-- 登录方式切换 -->
|
||||
<div class="login-type-switch">
|
||||
<span
|
||||
:class="[
|
||||
'switch-item',
|
||||
{ active: loginType === 'password' },
|
||||
]"
|
||||
@click="switchLoginType('password')"
|
||||
:class="['switch-item', { active: loginType === 1 }]"
|
||||
@click="switchLoginType(1)"
|
||||
>
|
||||
密码登录
|
||||
</span>
|
||||
<span class="divider">|</span>
|
||||
<span
|
||||
:class="[
|
||||
'switch-item',
|
||||
{ active: loginType === 'sms' },
|
||||
]"
|
||||
@click="switchLoginType('sms')"
|
||||
:class="['switch-item', { active: loginType === 2 }]"
|
||||
@click="switchLoginType(2)"
|
||||
>
|
||||
验证码登录
|
||||
</span>
|
||||
|
|
@ -47,7 +41,7 @@
|
|||
class="login-form"
|
||||
>
|
||||
<!-- 密码登录表单 -->
|
||||
<template v-if="loginType === 'password'">
|
||||
<template v-if="loginType === 1">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
|
|
@ -178,6 +172,8 @@
|
|||
import { getCodeImg } from '@/api/login'
|
||||
import Cookies from 'js-cookie'
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import { getPhoneCodeApi } from '@/api/login'
|
||||
import { encryptWithSM4 } from '@/utils/sm'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
|
|
@ -185,7 +181,7 @@ export default {
|
|||
return {
|
||||
title: process.env.VUE_APP_TITLE,
|
||||
codeUrl: '',
|
||||
loginType: 'password', // 登录类型:password 或 sms
|
||||
// loginType: 'password', // 登录类型:password 或 sms
|
||||
loginForm: {
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
|
|
@ -245,6 +241,7 @@ export default {
|
|||
redirect: undefined,
|
||||
// 短信验证码倒计时
|
||||
smsCountdown: 0,
|
||||
loginType: 1,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -269,8 +266,10 @@ export default {
|
|||
})
|
||||
},
|
||||
// 发送短信验证码
|
||||
sendSmsCode() {
|
||||
async sendSmsCode() {
|
||||
this.$message.closeAll()
|
||||
|
||||
if (this.smsCountdown > 0) return
|
||||
if (!this.loginForm.mobile) {
|
||||
this.$message.warning('请先输入手机号')
|
||||
return
|
||||
|
|
@ -282,12 +281,18 @@ export default {
|
|||
|
||||
// 这里应该调用发送短信验证码的API
|
||||
// 模拟发送成功
|
||||
|
||||
// console.log(this.loginForm.mobile, 'encryptWithSM4')
|
||||
const res = await getPhoneCodeApi({
|
||||
username: encryptWithSM4(this.loginForm.mobile),
|
||||
})
|
||||
|
||||
this.$message.success('验证码已发送')
|
||||
this.startCountdown()
|
||||
},
|
||||
// 开始倒计时
|
||||
startCountdown() {
|
||||
this.smsCountdown = 60
|
||||
this.smsCountdown = 120
|
||||
const timer = setInterval(() => {
|
||||
this.smsCountdown--
|
||||
if (this.smsCountdown <= 0) {
|
||||
|
|
@ -326,7 +331,7 @@ export default {
|
|||
this.loading = true
|
||||
|
||||
// 根据登录类型处理不同的登录逻辑
|
||||
if (this.loginType === 'password') {
|
||||
if (this.loginType === 1) {
|
||||
// 密码登录逻辑
|
||||
if (this.loginForm.rememberMe) {
|
||||
Cookies.set('username', this.loginForm.username, {
|
||||
|
|
@ -349,6 +354,7 @@ export default {
|
|||
Cookies.remove('password')
|
||||
Cookies.remove('rememberMe')
|
||||
}
|
||||
this.loginForm.loginType = 1
|
||||
} else {
|
||||
// 短信验证码登录逻辑
|
||||
// 这里可以添加短信验证码登录的特殊处理
|
||||
|
|
@ -357,6 +363,10 @@ export default {
|
|||
this.loginForm.mobile,
|
||||
this.loginForm.smsCode,
|
||||
)
|
||||
|
||||
this.loginForm.loginType = 2
|
||||
this.loginForm.code = this.loginForm.smsCode
|
||||
this.loginForm.username = this.loginForm.mobile
|
||||
}
|
||||
|
||||
// 调用登录接口
|
||||
|
|
@ -364,7 +374,7 @@ export default {
|
|||
.dispatch('Login', this.loginForm)
|
||||
.then(() => {
|
||||
this.$router
|
||||
.push({ path: this.redirect || '/' })
|
||||
.push({ path: '/index_1' })
|
||||
.catch(() => {})
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<!-- 产品卡片 -->
|
||||
<div class="card-container">
|
||||
<div class="card-header">
|
||||
<span> {{ cardTitle }} </span>
|
||||
<span> 查看更多 </span>
|
||||
</div>
|
||||
|
||||
<div class="card-list" ref="cardList">
|
||||
<div class="card-item" v-for="item in cardList" :key="item.id">
|
||||
<div class="card-item-image" ref="cardItemImage">
|
||||
<ImagePreview
|
||||
:height="160"
|
||||
:src1="item.url"
|
||||
:width="itemWidth"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-item-text">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CardItem',
|
||||
props: {
|
||||
// 卡片标题
|
||||
cardTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 卡片列表
|
||||
cardList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
itemWidth: 100,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getItemWidth()
|
||||
},
|
||||
methods: {
|
||||
// 动态获取 itemWidth
|
||||
getItemWidth() {
|
||||
this.itemWidth = (this.$refs.cardList.clientWidth - 260) / 4
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-container {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
|
||||
& span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
& span:last-child {
|
||||
font-size: 16px;
|
||||
background: linear-gradient(90deg, #00c7ef 0%, #005eef 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.card-list {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 60px;
|
||||
|
||||
.card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 55, 202, 0.2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.card-item-image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-item-text {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
<!-- 产品案例 -->
|
||||
<div class="case-container">
|
||||
<div class="case-header">
|
||||
<span> {{ cardTitle }} </span>
|
||||
<span> 查看更多 </span>
|
||||
</div>
|
||||
|
||||
<div class="case-list" v-for="item in cardList" :key="item.id">
|
||||
<div class="case-left-img">
|
||||
<ImagePreview
|
||||
:height="160"
|
||||
:width="320"
|
||||
:src1="item.imageList[0].url"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="case-right-content">
|
||||
<h3>{{ item.caseCompany }}</h3>
|
||||
<div>{{ item.caseIntroduction }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CaseContainer',
|
||||
props: {
|
||||
cardTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
cardList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
itemWidth: 100,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getItemWidth()
|
||||
},
|
||||
methods: {
|
||||
// 动态获取 itemWidth
|
||||
getItemWidth() {
|
||||
this.itemWidth = this.$refs.cardItemImage[0].clientWidth
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.case-container {
|
||||
width: 100%;
|
||||
padding-bottom: 20px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.case-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
|
||||
& span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
& span:last-child {
|
||||
font-size: 16px;
|
||||
background: linear-gradient(90deg, #00c7ef 0%, #005eef 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.case-list {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.case-left-img {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.case-right-content {
|
||||
flex: 1;
|
||||
height: 160px;
|
||||
padding-left: 36px;
|
||||
background-color: #f0f0f0;
|
||||
|
||||
h3 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-top: 36px;
|
||||
height: 70px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
div {
|
||||
height: 90px;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1.8;
|
||||
|
||||
// 多行文本截断:最多显示两行
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2 !important;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,13 +6,13 @@
|
|||
<!-- 左侧分类菜单 -->
|
||||
<div class="sidebar">
|
||||
<div
|
||||
:key="category.key"
|
||||
:key="item.value"
|
||||
class="category-item"
|
||||
v-for="category in categories"
|
||||
@click="activeCategory = category.key"
|
||||
:class="{ active: activeCategory === category.key }"
|
||||
v-for="item in leftMenuList"
|
||||
@click="activeTypeValue = item.value"
|
||||
:class="{ active: activeTypeValue === item.value }"
|
||||
>
|
||||
{{ category.label }}
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -22,9 +22,7 @@
|
|||
<div
|
||||
:key="item.id"
|
||||
class="service-card"
|
||||
@mouseleave="handleCardLeave"
|
||||
v-for="item in productListAll"
|
||||
@mouseenter="handleCardHover(service.id)"
|
||||
v-for="item in showProductList"
|
||||
>
|
||||
<div class="card-image">
|
||||
<img :src="item.linkImage" :alt="item.name" />
|
||||
|
|
@ -33,8 +31,12 @@
|
|||
<h3 class="card-title">{{ item.name }}</h3>
|
||||
<div class="card-actions">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
class="btn"
|
||||
@click="handleDemo(item)"
|
||||
:class="{
|
||||
isDisabled: item.isAccess == 0,
|
||||
'btn-primary': item.isAccess == 1,
|
||||
}"
|
||||
>
|
||||
访问演示
|
||||
</button>
|
||||
|
|
@ -57,152 +59,91 @@
|
|||
import { getProductCenterListAPI } from '@/api/publicService/productCenter'
|
||||
export default {
|
||||
name: 'ProductCenter',
|
||||
dicts: ['tb_product_type'],
|
||||
data() {
|
||||
return {
|
||||
activeNav: 'products',
|
||||
activeCategory: 'all',
|
||||
searchKeyword: '',
|
||||
hoveredCard: null,
|
||||
apiBase: process.env.VUE_APP_BASE_API,
|
||||
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',
|
||||
},
|
||||
],
|
||||
categories: [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'smart', label: '智慧基建' },
|
||||
{ key: 'system', label: '智慧后勤' },
|
||||
{ key: 'safety', label: '数智安监' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 1,
|
||||
title: '绿智食堂',
|
||||
image: '/profile/avatar/2025/09/03/001_20250903132300A001.JPG',
|
||||
category: 'smart',
|
||||
description: '智能化食堂管理系统',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '智能机具管理系统',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'system',
|
||||
description: '设备智能化管理平台',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '智慧楼宇',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'smart',
|
||||
description: '楼宇智能化控制系统',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '产业工人实名制',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'system',
|
||||
description: '工人身份认证管理',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '安全质量智慧决策中心',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'safety',
|
||||
description: '安全质量智能决策平台',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '智慧工地',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'smart',
|
||||
description: '工地智能化管理系统',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: 'VR警示教育室',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'safety',
|
||||
description: 'VR安全教育培训系统',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '应急智中心建设',
|
||||
image: '/placeholder.svg?height=200&width=300',
|
||||
category: 'safety',
|
||||
description: '应急响应智能化中心',
|
||||
},
|
||||
],
|
||||
|
||||
activeTypeValue: 'all', // 当前激活的分类
|
||||
leftMenuList: [], // 左侧菜单列表
|
||||
productListAll: [], // 所有产品列表
|
||||
showProductList: [], // 需要显示的产品列表
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredServices() {
|
||||
// let filtered = this.services
|
||||
// // 按分类筛选
|
||||
// if (this.activeCategory !== 'all') {
|
||||
// filtered = filtered.filter(
|
||||
// (service) => service.category === this.activeCategory,
|
||||
// )
|
||||
// }
|
||||
// // 按搜索关键词筛选
|
||||
// if (this.searchKeyword.trim()) {
|
||||
// const keyword = this.searchKeyword.toLowerCase()
|
||||
// filtered = filtered.filter(
|
||||
// (service) =>
|
||||
// service.title.toLowerCase().includes(keyword) ||
|
||||
// service.description.toLowerCase().includes(keyword),
|
||||
// )
|
||||
// }
|
||||
// return filtered
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getProductCenterListInScreenFun()
|
||||
},
|
||||
mounted() {
|
||||
// 监听字典数据加载完成事件
|
||||
this.$on('dictReady', (dict) => {
|
||||
this.initLeftMenuList()
|
||||
})
|
||||
|
||||
// 如果字典数据已经加载完成,直接初始化
|
||||
if (this.dict && this.dict.type && this.dict.type.tb_product_type) {
|
||||
this.initLeftMenuList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNavChange(navKey) {
|
||||
this.activeNav = navKey
|
||||
// 可以根据导航项做页面跳转或其他操作
|
||||
// 初始化左侧菜单列表
|
||||
initLeftMenuList() {
|
||||
try {
|
||||
if (
|
||||
this.dict &&
|
||||
this.dict.type &&
|
||||
this.dict.type.tb_product_type
|
||||
) {
|
||||
// 清空现有菜单
|
||||
this.leftMenuList = [
|
||||
{
|
||||
label: '全部',
|
||||
value: 'all',
|
||||
},
|
||||
]
|
||||
|
||||
// 添加字典数据到菜单
|
||||
this.dict.type.tb_product_type.forEach((item) => {
|
||||
this.leftMenuList.push({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
// 访问演示地址
|
||||
handleDemo(service) {
|
||||
window.open(service.link, '_blank')
|
||||
},
|
||||
// 在 index.vue 中的 handleDetail 方法
|
||||
handleDetail(service) {
|
||||
// console.log('查看详情:', service.title)
|
||||
// this.$router.push({
|
||||
// name: 'ProductDetail',
|
||||
// params: { id: service.id },
|
||||
// })
|
||||
if (service.isAccess == 0) {
|
||||
this.$modal.msgWarning('该产品不支持访问')
|
||||
return
|
||||
}
|
||||
|
||||
// 确保 URL 是完整的绝对路径
|
||||
let url = service.linkUrl
|
||||
// 如果 URL 不是以 http:// 或 https:// 开头,则添加 https://
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
// 如果 URL 以 www. 开头,直接添加 https://
|
||||
if (url.startsWith('www.')) {
|
||||
url = 'https://' + url
|
||||
} else {
|
||||
// 其他情况,添加 https://
|
||||
url = 'https://' + url
|
||||
}
|
||||
}
|
||||
|
||||
window.open(url, '_blank')
|
||||
},
|
||||
// 查看详情
|
||||
handleDetail(service) {
|
||||
this.$router.push({
|
||||
name: 'ProductDetail',
|
||||
params: { id: service.id },
|
||||
})
|
||||
},
|
||||
// 鼠标悬浮
|
||||
handleCardHover(serviceId) {
|
||||
this.hoveredCard = serviceId
|
||||
},
|
||||
// 鼠标离开
|
||||
handleCardLeave() {
|
||||
this.hoveredCard = null
|
||||
},
|
||||
|
|
@ -210,10 +151,21 @@ export default {
|
|||
// 获取产品列表
|
||||
async getProductCenterListInScreenFun() {
|
||||
const res = await getProductCenterListAPI({})
|
||||
|
||||
console.log(res, 'res')
|
||||
this.productListAll = res.data
|
||||
// this.services = res.data
|
||||
this.showProductList = this.productListAll
|
||||
},
|
||||
},
|
||||
|
||||
// 监听分类变化
|
||||
watch: {
|
||||
activeTypeValue(newVal) {
|
||||
if (newVal === 'all') {
|
||||
this.showProductList = this.productListAll
|
||||
} else {
|
||||
this.showProductList = this.productListAll.filter(
|
||||
(item) => item.typeId === newVal,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -364,7 +316,7 @@ export default {
|
|||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
|
@ -427,26 +379,45 @@ export default {
|
|||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
border: none;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #4a90e2;
|
||||
// background-color: #4a90e2;
|
||||
color: white;
|
||||
border-color: #4a90e2;
|
||||
// border-color: #4a90e2;
|
||||
|
||||
background: linear-gradient(180deg, #00c7ef 0%, #005eef 100%);
|
||||
}
|
||||
|
||||
.isDisabled {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
border-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #357abd;
|
||||
border-color: #357abd;
|
||||
// background-color: #357abd;
|
||||
// border-color: #357abd;
|
||||
background: linear-gradient(180deg, #00c7ef 0%, #005eef 100%);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: #4a90e2;
|
||||
border-color: #4a90e2;
|
||||
// border-color: #4a90e2;
|
||||
|
||||
// border-image: linear-gradient(
|
||||
// 180deg,
|
||||
// rgba(0, 199, 239, 1),
|
||||
// rgba(0, 94, 239, 1)
|
||||
// )
|
||||
// 1 1;
|
||||
|
||||
border: 1px solid #00c7ef;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
|
|
@ -475,6 +446,12 @@ export default {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -17,7 +17,7 @@
|
|||
<div class="section product-intro">
|
||||
<div class="intro-content">
|
||||
<div class="intro-image">
|
||||
<img :src="apiBase + intro" alt="产品介绍" />
|
||||
<img :src="productDetail.linkImage" alt="产品介绍" />
|
||||
</div>
|
||||
<div class="intro-text">
|
||||
<div class="intro-header">
|
||||
|
|
@ -25,171 +25,33 @@
|
|||
<button class="btn-primary">访问演示</button>
|
||||
</div>
|
||||
<p>
|
||||
基于移动互联网(微信、APP),整合所有数据资源(在线订餐、充值、点评),由内到外的为学校师生提供便民服务,深度定制餐厅、校
|
||||
本文档精准、适用于智慧食堂数据资源,通过设备、消费设备、自助充值、智慧收银、零售管理、多样化计
|
||||
方式),致力提升学校整体服务和管理的能力,打造名副其实与时俱进的互联网餐厅。
|
||||
{{ productDetail.introduction }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h3>宣传手册</h3>
|
||||
<a href="#" class="view-more">查看更多</a>
|
||||
</div>
|
||||
<div class="carousel-container">
|
||||
<button class="carousel-btn prev" @click="prevBrochure">
|
||||
<i class="arrow-left"></i>
|
||||
</button>
|
||||
<div
|
||||
class="carousel-content"
|
||||
ref="brochureCarousel"
|
||||
@mouseenter="pauseBrochureScroll"
|
||||
@mouseleave="resumeBrochureScroll"
|
||||
>
|
||||
<div class="carousel-track" :style="brochureTrackStyle">
|
||||
<div
|
||||
v-for="(item, index) in displayBrochures"
|
||||
:key="`brochure-${index}`"
|
||||
class="carousel-item brochure-item"
|
||||
>
|
||||
<div class="item-image">
|
||||
<img
|
||||
:src="apiBase + item.image"
|
||||
:alt="item.title"
|
||||
/>
|
||||
</div>
|
||||
<p class="item-title">{{ item.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="carousel-btn next" @click="nextBrochure">
|
||||
<i class="arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promotional Videos -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h3>宣传视频</h3>
|
||||
<a href="#" class="view-more">查看更多</a>
|
||||
</div>
|
||||
<!-- Updated carousel with infinite loop scrolling -->
|
||||
<div class="carousel-container">
|
||||
<button class="carousel-btn prev" @click="prevVideo">
|
||||
<i class="arrow-left"></i>
|
||||
</button>
|
||||
<div
|
||||
class="carousel-content"
|
||||
ref="videoCarousel"
|
||||
@mouseenter="pauseVideoScroll"
|
||||
@mouseleave="resumeVideoScroll"
|
||||
>
|
||||
<div class="carousel-track" :style="videoTrackStyle">
|
||||
<div
|
||||
v-for="(item, index) in displayVideos"
|
||||
:key="`video-${index}`"
|
||||
class="carousel-item video-item"
|
||||
@click="playVideo(item)"
|
||||
>
|
||||
<!-- <div class="item-image video-thumbnail">
|
||||
<img :src="apiBase + item.thumbnail" :alt="item.title" />
|
||||
<div class="play-overlay">
|
||||
<div class="play-button">
|
||||
<i class="play-icon">▶</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="item-image video-thumbnail">
|
||||
<video
|
||||
:src="apiBase + item.thumbnail"
|
||||
:alt="item.title"
|
||||
preload="metadata"
|
||||
muted
|
||||
@error="handleVideoError"
|
||||
@loadedmetadata="handleVideoLoaded"
|
||||
style="height: 180px; width: 240px"
|
||||
>
|
||||
<img
|
||||
:src="'/placeholder.svg?height=180&width=240'"
|
||||
:alt="item.title"
|
||||
<CardContainer cardTitle="宣传手册" :cardList="productBrochures" />
|
||||
<CardContainer
|
||||
cardTitle="宣传视频"
|
||||
:cardList="productVideos"
|
||||
v-if="productVideos.length > 0"
|
||||
/>
|
||||
</video>
|
||||
<div class="play-overlay">
|
||||
<div class="play-button">
|
||||
<i class="play-icon">▶</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="item-title">{{ item.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="carousel-btn next" @click="nextVideo">
|
||||
<i class="arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Cases -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h3>产品案例</h3>
|
||||
<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"
|
||||
>
|
||||
<i class="arrow-left"></i>
|
||||
</button>
|
||||
<div class="carousel-content" ref="caseCarousel">
|
||||
<div class="carousel-track" :style="caseTrackStyle">
|
||||
<div
|
||||
v-for="(item, index) in cases"
|
||||
:key="index"
|
||||
class="case-item"
|
||||
>
|
||||
<div class="case-image">
|
||||
<img
|
||||
:src="apiBase + item.image"
|
||||
:alt="item.company"
|
||||
/>
|
||||
</div>
|
||||
<div class="case-content">
|
||||
<h4>{{ item.company }}</h4>
|
||||
<p>{{ item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="carousel-btn next"
|
||||
@click="nextCase"
|
||||
:disabled="caseIndex >= maxCaseIndex"
|
||||
>
|
||||
<i class="arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaseContainer cardTitle="产品案例" :cardList="productCases" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入导航栏组件
|
||||
import NavBar from '@/views/psp/navBar.vue'
|
||||
import CardContainer from './components/card-container'
|
||||
import CaseContainer from './components/case-container'
|
||||
import { getProductCenterDetailAPI } from '@/api/publicService/productCenter'
|
||||
|
||||
export default {
|
||||
name: 'ProductDetail',
|
||||
components: {
|
||||
NavBar,
|
||||
CardContainer,
|
||||
CaseContainer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -304,13 +166,18 @@ export default {
|
|||
},
|
||||
],
|
||||
animationFrame: null,
|
||||
|
||||
productDetail: {}, // 产品详情
|
||||
productBrochures: [], // 产品宣传手册
|
||||
productVideos: [], // 产品宣传视频
|
||||
productCases: [], // 产品案例
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.productId = this.$route.params.id
|
||||
console.log('产品ID:', this.productId)
|
||||
// this.productId = this.$route.params.id
|
||||
// console.log('产品ID:', this.productId)
|
||||
|
||||
console.log('产品详情:', this.$route)
|
||||
this.getProductCenterDetailInScreenFun(this.$route.params?.id)
|
||||
},
|
||||
computed: {
|
||||
maxBrochureIndex() {
|
||||
|
|
@ -360,6 +227,41 @@ export default {
|
|||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
// 获取产品中心详情
|
||||
async getProductCenterDetailInScreenFun(id) {
|
||||
const res = await getProductCenterDetailAPI({ id })
|
||||
console.log(res, 'res详情')
|
||||
|
||||
const {
|
||||
name,
|
||||
list,
|
||||
image,
|
||||
typeId,
|
||||
linkUrl,
|
||||
typeName,
|
||||
linkImage,
|
||||
isAccess,
|
||||
introduction,
|
||||
videoList,
|
||||
fileList,
|
||||
} = res?.data
|
||||
|
||||
this.productDetail = {
|
||||
name,
|
||||
image,
|
||||
typeId,
|
||||
linkUrl,
|
||||
typeName,
|
||||
linkImage,
|
||||
isAccess,
|
||||
introduction,
|
||||
}
|
||||
|
||||
this.productCases = list
|
||||
this.productVideos = videoList
|
||||
this.productBrochures = fileList
|
||||
},
|
||||
|
||||
handleNavChange(navKey) {
|
||||
this.activeNav = navKey
|
||||
// 可以根据导航项做页面跳转或其他操作
|
||||
|
|
|
|||
Loading…
Reference in New Issue