登录接口加密 产品中心等页面完善
This commit is contained in:
parent
e89086eb42
commit
ad83188c6f
|
|
@ -39,6 +39,7 @@
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"quill": "2.0.2",
|
"quill": "2.0.2",
|
||||||
"screenfull": "5.0.2",
|
"screenfull": "5.0.2",
|
||||||
|
"sm-crypto": "^0.3.13",
|
||||||
"sortablejs": "1.10.2",
|
"sortablejs": "1.10.2",
|
||||||
"splitpanes": "2.4.1",
|
"splitpanes": "2.4.1",
|
||||||
"vue": "2.6.12",
|
"vue": "2.6.12",
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,70 @@
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
|
||||||
// 登录方法
|
// 登录方法
|
||||||
export function login(username, password, code, uuid) {
|
export function login(username, password, code, uuid, loginType) {
|
||||||
const data = {
|
const data = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
code,
|
code,
|
||||||
uuid
|
uuid,
|
||||||
}
|
loginType,
|
||||||
return request({
|
}
|
||||||
url: '/login',
|
return request({
|
||||||
headers: {
|
url: '/login',
|
||||||
isToken: false,
|
headers: {
|
||||||
repeatSubmit: false
|
isToken: false,
|
||||||
},
|
repeatSubmit: false,
|
||||||
method: 'post',
|
},
|
||||||
data: data
|
method: 'post',
|
||||||
})
|
data: data,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册方法
|
// 注册方法
|
||||||
export function register(data) {
|
export function register(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/register',
|
url: '/register',
|
||||||
headers: {
|
headers: {
|
||||||
isToken: false
|
isToken: false,
|
||||||
},
|
},
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户详细信息
|
// 获取用户详细信息
|
||||||
export function getInfo() {
|
export function getInfo() {
|
||||||
return request({
|
return request({
|
||||||
url: '/getInfo',
|
url: '/getInfo',
|
||||||
method: 'get'
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出方法
|
// 退出方法
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return request({
|
return request({
|
||||||
url: '/logout',
|
url: '/logout',
|
||||||
method: 'post'
|
method: 'post',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
export function getCodeImg() {
|
export function getCodeImg() {
|
||||||
return request({
|
return request({
|
||||||
url: '/captchaImage',
|
url: '/captchaImage',
|
||||||
headers: {
|
headers: {
|
||||||
isToken: false
|
isToken: false,
|
||||||
},
|
},
|
||||||
method: 'get',
|
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 from '@/utils/request'
|
||||||
import request_formdata from '@/utils/request_formdata'
|
|
||||||
|
|
||||||
// 查询产品中心列表
|
// 查询产品中心列表
|
||||||
export function getProductCenterListAPI(data) {
|
export function getProductCenterListAPI(data) {
|
||||||
|
|
@ -9,3 +8,12 @@ export function getProductCenterListAPI(data) {
|
||||||
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>
|
<style lang="scss" scoped>
|
||||||
.el-image {
|
.el-image {
|
||||||
border-radius: 5px;
|
// border-radius: 5px;
|
||||||
background-color: #ebeef5;
|
background-color: #ebeef5;
|
||||||
box-shadow: 0 0 5px 1px #ccc;
|
// box-shadow: 0 0 5px 1px #ccc;
|
||||||
::v-deep .el-image__inner {
|
::v-deep .el-image__inner {
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
||||||
|
|
@ -3,81 +3,85 @@ import { Message, MessageBox, Notification, Loading } from 'element-ui'
|
||||||
let loadingInstance
|
let loadingInstance
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// 消息提示
|
// 消息提示
|
||||||
msg(content) {
|
msg(content) {
|
||||||
Message.info(content)
|
Message.closeAll()
|
||||||
},
|
Message.info(content)
|
||||||
// 错误消息
|
},
|
||||||
msgError(content) {
|
// 错误消息
|
||||||
Message.error(content)
|
msgError(content) {
|
||||||
},
|
Message.closeAll()
|
||||||
// 成功消息
|
Message.error(content)
|
||||||
msgSuccess(content) {
|
},
|
||||||
Message.success(content)
|
// 成功消息
|
||||||
},
|
msgSuccess(content) {
|
||||||
// 警告消息
|
Message.closeAll()
|
||||||
msgWarning(content) {
|
Message.success(content)
|
||||||
Message.warning(content)
|
},
|
||||||
},
|
// 警告消息
|
||||||
// 弹出提示
|
msgWarning(content) {
|
||||||
alert(content) {
|
Message.closeAll()
|
||||||
MessageBox.alert(content, "系统提示")
|
Message.warning(content)
|
||||||
},
|
},
|
||||||
// 错误提示
|
// 弹出提示
|
||||||
alertError(content) {
|
alert(content) {
|
||||||
MessageBox.alert(content, "系统提示", { type: 'error' })
|
MessageBox.alert(content, '系统提示')
|
||||||
},
|
},
|
||||||
// 成功提示
|
// 错误提示
|
||||||
alertSuccess(content) {
|
alertError(content) {
|
||||||
MessageBox.alert(content, "系统提示", { type: 'success' })
|
MessageBox.alert(content, '系统提示', { type: 'error' })
|
||||||
},
|
},
|
||||||
// 警告提示
|
// 成功提示
|
||||||
alertWarning(content) {
|
alertSuccess(content) {
|
||||||
MessageBox.alert(content, "系统提示", { type: 'warning' })
|
MessageBox.alert(content, '系统提示', { type: 'success' })
|
||||||
},
|
},
|
||||||
// 通知提示
|
// 警告提示
|
||||||
notify(content) {
|
alertWarning(content) {
|
||||||
Notification.info(content)
|
MessageBox.alert(content, '系统提示', { type: 'warning' })
|
||||||
},
|
},
|
||||||
// 错误通知
|
// 通知提示
|
||||||
notifyError(content) {
|
notify(content) {
|
||||||
Notification.error(content)
|
Notification.info(content)
|
||||||
},
|
},
|
||||||
// 成功通知
|
// 错误通知
|
||||||
notifySuccess(content) {
|
notifyError(content) {
|
||||||
Notification.success(content)
|
Notification.error(content)
|
||||||
},
|
},
|
||||||
// 警告通知
|
// 成功通知
|
||||||
notifyWarning(content) {
|
notifySuccess(content) {
|
||||||
Notification.warning(content)
|
Notification.success(content)
|
||||||
},
|
},
|
||||||
// 确认窗体
|
// 警告通知
|
||||||
confirm(content) {
|
notifyWarning(content) {
|
||||||
return MessageBox.confirm(content, "系统提示", {
|
Notification.warning(content)
|
||||||
confirmButtonText: '确定',
|
},
|
||||||
cancelButtonText: '取消',
|
// 确认窗体
|
||||||
type: "warning",
|
confirm(content) {
|
||||||
})
|
return MessageBox.confirm(content, '系统提示', {
|
||||||
},
|
confirmButtonText: '确定',
|
||||||
// 提交内容
|
cancelButtonText: '取消',
|
||||||
prompt(content) {
|
type: 'warning',
|
||||||
return MessageBox.prompt(content, "系统提示", {
|
})
|
||||||
confirmButtonText: '确定',
|
},
|
||||||
cancelButtonText: '取消',
|
// 提交内容
|
||||||
type: "warning",
|
prompt(content) {
|
||||||
})
|
return MessageBox.prompt(content, '系统提示', {
|
||||||
},
|
confirmButtonText: '确定',
|
||||||
// 打开遮罩层
|
cancelButtonText: '取消',
|
||||||
loading(content) {
|
type: 'warning',
|
||||||
loadingInstance = Loading.service({
|
})
|
||||||
lock: true,
|
},
|
||||||
text: content,
|
// 打开遮罩层
|
||||||
spinner: "el-icon-loading",
|
loading(content) {
|
||||||
background: "rgba(0, 0, 0, 0.7)",
|
loadingInstance = Loading.service({
|
||||||
})
|
lock: true,
|
||||||
},
|
text: content,
|
||||||
// 关闭遮罩层
|
spinner: 'el-icon-loading',
|
||||||
closeLoading() {
|
background: 'rgba(0, 0, 0, 0.7)',
|
||||||
loadingInstance.close()
|
})
|
||||||
}
|
},
|
||||||
|
// 关闭遮罩层
|
||||||
|
closeLoading() {
|
||||||
|
loadingInstance.close()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,125 +1,164 @@
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { MessageBox, } from 'element-ui'
|
import { MessageBox } from 'element-ui'
|
||||||
import { login, logout, getInfo } from '@/api/login'
|
import { login, logout, getInfo } from '@/api/login'
|
||||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
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 defAva from '@/assets/images/profile.jpg'
|
||||||
|
import { encryptWithSM4 } from '@/utils/sm'
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
state: {
|
state: {
|
||||||
token: getToken(),
|
token: getToken(),
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
nickName: '',
|
nickName: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
roles: [],
|
roles: [],
|
||||||
permissions: []
|
permissions: [],
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {
|
|
||||||
SET_TOKEN: (state, token) => {
|
|
||||||
state.token = token
|
|
||||||
},
|
|
||||||
SET_ID: (state, id) => {
|
|
||||||
state.id = id
|
|
||||||
},
|
|
||||||
SET_NAME: (state, name) => {
|
|
||||||
state.name = name
|
|
||||||
},
|
|
||||||
SET_NICK_NAME: (state, nickName) => {
|
|
||||||
state.nickName = nickName
|
|
||||||
},
|
|
||||||
SET_AVATAR: (state, avatar) => {
|
|
||||||
state.avatar = avatar
|
|
||||||
},
|
|
||||||
SET_ROLES: (state, roles) => {
|
|
||||||
state.roles = roles
|
|
||||||
},
|
|
||||||
SET_PERMISSIONS: (state, permissions) => {
|
|
||||||
state.permissions = permissions
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 登录
|
|
||||||
Login({ commit }, userInfo) {
|
|
||||||
const username = userInfo.username.trim()
|
|
||||||
const password = userInfo.password
|
|
||||||
const code = userInfo.code
|
|
||||||
const uuid = userInfo.uuid
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
login(username, password, code, uuid).then(res => {
|
|
||||||
setToken(res.token)
|
|
||||||
commit('SET_TOKEN', res.token)
|
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取用户信息
|
mutations: {
|
||||||
GetInfo({ commit, state }) {
|
SET_TOKEN: (state, token) => {
|
||||||
return new Promise((resolve, reject) => {
|
state.token = token
|
||||||
getInfo().then(res => {
|
},
|
||||||
const user = res.user
|
SET_ID: (state, id) => {
|
||||||
let avatar = user.avatar || ""
|
state.id = id
|
||||||
if (!isHttp(avatar)) {
|
},
|
||||||
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
|
SET_NAME: (state, name) => {
|
||||||
}
|
state.name = name
|
||||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
},
|
||||||
commit('SET_ROLES', res.roles)
|
SET_NICK_NAME: (state, nickName) => {
|
||||||
commit('SET_PERMISSIONS', res.permissions)
|
state.nickName = nickName
|
||||||
} else {
|
},
|
||||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
SET_AVATAR: (state, avatar) => {
|
||||||
}
|
state.avatar = avatar
|
||||||
commit('SET_ID', user.userId)
|
},
|
||||||
commit('SET_NAME', user.userName)
|
SET_ROLES: (state, roles) => {
|
||||||
commit('SET_NICK_NAME', user.nickName)
|
state.roles = roles
|
||||||
commit('SET_AVATAR', avatar)
|
},
|
||||||
/* 初始密码提示 */
|
SET_PERMISSIONS: (state, permissions) => {
|
||||||
if(res.isDefaultModifyPwd) {
|
state.permissions = permissions
|
||||||
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 => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 退出系统
|
actions: {
|
||||||
LogOut({ commit, state }) {
|
// 登录
|
||||||
return new Promise((resolve, reject) => {
|
Login({ commit }, userInfo) {
|
||||||
logout(state.token).then(() => {
|
const username = encryptWithSM4(userInfo.username.trim())
|
||||||
commit('SET_TOKEN', '')
|
const password = encryptWithSM4(userInfo.password)
|
||||||
commit('SET_ROLES', [])
|
const code = userInfo.code
|
||||||
commit('SET_PERMISSIONS', [])
|
const uuid = userInfo.uuid
|
||||||
removeToken()
|
const loginType = userInfo.loginType
|
||||||
resolve()
|
|
||||||
}).catch(error => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 前端 登出
|
console.log(userInfo, 'userInfo')
|
||||||
FedLogOut({ commit }) {
|
return new Promise((resolve, reject) => {
|
||||||
return new Promise(resolve => {
|
login(username, password, code, uuid, loginType)
|
||||||
commit('SET_TOKEN', '')
|
.then((res) => {
|
||||||
removeToken()
|
setToken(res.token)
|
||||||
resolve()
|
commit('SET_TOKEN', res.token)
|
||||||
})
|
resolve()
|
||||||
}
|
})
|
||||||
}
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
GetInfo({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo()
|
||||||
|
.then((res) => {
|
||||||
|
const user = res.user
|
||||||
|
let avatar = user.avatar || ''
|
||||||
|
if (!isHttp(avatar)) {
|
||||||
|
avatar = isEmpty(avatar)
|
||||||
|
? defAva
|
||||||
|
: process.env.VUE_APP_BASE_API + avatar
|
||||||
|
}
|
||||||
|
if (res.roles && res.roles.length > 0) {
|
||||||
|
// 验证返回的roles是否是一个非空数组
|
||||||
|
commit('SET_ROLES', res.roles)
|
||||||
|
commit('SET_PERMISSIONS', res.permissions)
|
||||||
|
} else {
|
||||||
|
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||||
|
}
|
||||||
|
commit('SET_ID', user.userId)
|
||||||
|
commit('SET_NAME', user.userName)
|
||||||
|
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 && res.isPasswordExpired) {
|
||||||
|
MessageBox.confirm(
|
||||||
|
'您的密码已过期,请尽快修改密码!',
|
||||||
|
'安全提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
router.push({
|
||||||
|
name: 'Profile',
|
||||||
|
params: { activeTab: 'resetPwd' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 退出系统
|
||||||
|
LogOut({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logout(state.token)
|
||||||
|
.then(() => {
|
||||||
|
commit('SET_TOKEN', '')
|
||||||
|
commit('SET_ROLES', [])
|
||||||
|
commit('SET_PERMISSIONS', [])
|
||||||
|
removeToken()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 前端 登出
|
||||||
|
FedLogOut({ commit }) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
commit('SET_TOKEN', '')
|
||||||
|
removeToken()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default user
|
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() {
|
handleCancel() {
|
||||||
// 重置基本信息表单
|
// 重置基本信息表单
|
||||||
this.$refs.addAndEditForm?.resetFields()
|
this.$refs.addAndEditForm?.resetFields()
|
||||||
// 重置所有案例表单
|
// // 重置所有案例表单
|
||||||
this.editableTabs.forEach((tab) => {
|
// this.editableTabs.forEach((tab) => {
|
||||||
this.$refs[`caseForm_${tab.name}`]?.resetFields()
|
// this.$refs[`caseForm_${tab.name}`]?.resetFields()
|
||||||
})
|
// })
|
||||||
// 回到第一个Tab
|
// // 回到第一个Tab
|
||||||
this.currentTab = '1'
|
// this.currentTab = '1'
|
||||||
|
|
||||||
this.$emit('closeDialog', false)
|
this.$emit('closeDialog', false)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,15 @@
|
||||||
<!-- 登录方式切换 -->
|
<!-- 登录方式切换 -->
|
||||||
<div class="login-type-switch">
|
<div class="login-type-switch">
|
||||||
<span
|
<span
|
||||||
:class="[
|
:class="['switch-item', { active: loginType === 1 }]"
|
||||||
'switch-item',
|
@click="switchLoginType(1)"
|
||||||
{ active: loginType === 'password' },
|
|
||||||
]"
|
|
||||||
@click="switchLoginType('password')"
|
|
||||||
>
|
>
|
||||||
密码登录
|
密码登录
|
||||||
</span>
|
</span>
|
||||||
<span class="divider">|</span>
|
<span class="divider">|</span>
|
||||||
<span
|
<span
|
||||||
:class="[
|
:class="['switch-item', { active: loginType === 2 }]"
|
||||||
'switch-item',
|
@click="switchLoginType(2)"
|
||||||
{ active: loginType === 'sms' },
|
|
||||||
]"
|
|
||||||
@click="switchLoginType('sms')"
|
|
||||||
>
|
>
|
||||||
验证码登录
|
验证码登录
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -47,7 +41,7 @@
|
||||||
class="login-form"
|
class="login-form"
|
||||||
>
|
>
|
||||||
<!-- 密码登录表单 -->
|
<!-- 密码登录表单 -->
|
||||||
<template v-if="loginType === 'password'">
|
<template v-if="loginType === 1">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginForm.username"
|
v-model="loginForm.username"
|
||||||
|
|
@ -178,6 +172,8 @@
|
||||||
import { getCodeImg } from '@/api/login'
|
import { getCodeImg } from '@/api/login'
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||||
|
import { getPhoneCodeApi } from '@/api/login'
|
||||||
|
import { encryptWithSM4 } from '@/utils/sm'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
|
|
@ -185,7 +181,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
title: process.env.VUE_APP_TITLE,
|
title: process.env.VUE_APP_TITLE,
|
||||||
codeUrl: '',
|
codeUrl: '',
|
||||||
loginType: 'password', // 登录类型:password 或 sms
|
// loginType: 'password', // 登录类型:password 或 sms
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: 'admin123',
|
password: 'admin123',
|
||||||
|
|
@ -245,6 +241,7 @@ export default {
|
||||||
redirect: undefined,
|
redirect: undefined,
|
||||||
// 短信验证码倒计时
|
// 短信验证码倒计时
|
||||||
smsCountdown: 0,
|
smsCountdown: 0,
|
||||||
|
loginType: 1,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -269,8 +266,10 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 发送短信验证码
|
// 发送短信验证码
|
||||||
sendSmsCode() {
|
async sendSmsCode() {
|
||||||
this.$message.closeAll()
|
this.$message.closeAll()
|
||||||
|
|
||||||
|
if (this.smsCountdown > 0) return
|
||||||
if (!this.loginForm.mobile) {
|
if (!this.loginForm.mobile) {
|
||||||
this.$message.warning('请先输入手机号')
|
this.$message.warning('请先输入手机号')
|
||||||
return
|
return
|
||||||
|
|
@ -282,12 +281,18 @@ export default {
|
||||||
|
|
||||||
// 这里应该调用发送短信验证码的API
|
// 这里应该调用发送短信验证码的API
|
||||||
// 模拟发送成功
|
// 模拟发送成功
|
||||||
|
|
||||||
|
// console.log(this.loginForm.mobile, 'encryptWithSM4')
|
||||||
|
const res = await getPhoneCodeApi({
|
||||||
|
username: encryptWithSM4(this.loginForm.mobile),
|
||||||
|
})
|
||||||
|
|
||||||
this.$message.success('验证码已发送')
|
this.$message.success('验证码已发送')
|
||||||
this.startCountdown()
|
this.startCountdown()
|
||||||
},
|
},
|
||||||
// 开始倒计时
|
// 开始倒计时
|
||||||
startCountdown() {
|
startCountdown() {
|
||||||
this.smsCountdown = 60
|
this.smsCountdown = 120
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
this.smsCountdown--
|
this.smsCountdown--
|
||||||
if (this.smsCountdown <= 0) {
|
if (this.smsCountdown <= 0) {
|
||||||
|
|
@ -326,7 +331,7 @@ export default {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
// 根据登录类型处理不同的登录逻辑
|
// 根据登录类型处理不同的登录逻辑
|
||||||
if (this.loginType === 'password') {
|
if (this.loginType === 1) {
|
||||||
// 密码登录逻辑
|
// 密码登录逻辑
|
||||||
if (this.loginForm.rememberMe) {
|
if (this.loginForm.rememberMe) {
|
||||||
Cookies.set('username', this.loginForm.username, {
|
Cookies.set('username', this.loginForm.username, {
|
||||||
|
|
@ -349,6 +354,7 @@ export default {
|
||||||
Cookies.remove('password')
|
Cookies.remove('password')
|
||||||
Cookies.remove('rememberMe')
|
Cookies.remove('rememberMe')
|
||||||
}
|
}
|
||||||
|
this.loginForm.loginType = 1
|
||||||
} else {
|
} else {
|
||||||
// 短信验证码登录逻辑
|
// 短信验证码登录逻辑
|
||||||
// 这里可以添加短信验证码登录的特殊处理
|
// 这里可以添加短信验证码登录的特殊处理
|
||||||
|
|
@ -357,6 +363,10 @@ export default {
|
||||||
this.loginForm.mobile,
|
this.loginForm.mobile,
|
||||||
this.loginForm.smsCode,
|
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)
|
.dispatch('Login', this.loginForm)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$router
|
this.$router
|
||||||
.push({ path: this.redirect || '/' })
|
.push({ path: '/index_1' })
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
.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 class="sidebar">
|
||||||
<div
|
<div
|
||||||
:key="category.key"
|
:key="item.value"
|
||||||
class="category-item"
|
class="category-item"
|
||||||
v-for="category in categories"
|
v-for="item in leftMenuList"
|
||||||
@click="activeCategory = category.key"
|
@click="activeTypeValue = item.value"
|
||||||
:class="{ active: activeCategory === category.key }"
|
:class="{ active: activeTypeValue === item.value }"
|
||||||
>
|
>
|
||||||
{{ category.label }}
|
{{ item.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -22,9 +22,7 @@
|
||||||
<div
|
<div
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="service-card"
|
class="service-card"
|
||||||
@mouseleave="handleCardLeave"
|
v-for="item in showProductList"
|
||||||
v-for="item in productListAll"
|
|
||||||
@mouseenter="handleCardHover(service.id)"
|
|
||||||
>
|
>
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<img :src="item.linkImage" :alt="item.name" />
|
<img :src="item.linkImage" :alt="item.name" />
|
||||||
|
|
@ -33,8 +31,12 @@
|
||||||
<h3 class="card-title">{{ item.name }}</h3>
|
<h3 class="card-title">{{ item.name }}</h3>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn"
|
||||||
@click="handleDemo(item)"
|
@click="handleDemo(item)"
|
||||||
|
:class="{
|
||||||
|
isDisabled: item.isAccess == 0,
|
||||||
|
'btn-primary': item.isAccess == 1,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
访问演示
|
访问演示
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -57,152 +59,91 @@
|
||||||
import { getProductCenterListAPI } from '@/api/publicService/productCenter'
|
import { getProductCenterListAPI } from '@/api/publicService/productCenter'
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductCenter',
|
name: 'ProductCenter',
|
||||||
|
dicts: ['tb_product_type'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeNav: 'products',
|
activeTypeValue: 'all', // 当前激活的分类
|
||||||
activeCategory: 'all',
|
leftMenuList: [], // 左侧菜单列表
|
||||||
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: '应急响应智能化中心',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
productListAll: [], // 所有产品列表
|
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() {
|
created() {
|
||||||
this.getProductCenterListInScreenFun()
|
this.getProductCenterListInScreenFun()
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
// 监听字典数据加载完成事件
|
||||||
|
this.$on('dictReady', (dict) => {
|
||||||
|
this.initLeftMenuList()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果字典数据已经加载完成,直接初始化
|
||||||
|
if (this.dict && this.dict.type && this.dict.type.tb_product_type) {
|
||||||
|
this.initLeftMenuList()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
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) {
|
handleDemo(service) {
|
||||||
window.open(service.link, '_blank')
|
if (service.isAccess == 0) {
|
||||||
},
|
this.$modal.msgWarning('该产品不支持访问')
|
||||||
// 在 index.vue 中的 handleDetail 方法
|
return
|
||||||
handleDetail(service) {
|
}
|
||||||
// console.log('查看详情:', service.title)
|
|
||||||
// this.$router.push({
|
|
||||||
// name: 'ProductDetail',
|
|
||||||
// params: { id: service.id },
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
// 确保 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({
|
this.$router.push({
|
||||||
name: 'ProductDetail',
|
name: 'ProductDetail',
|
||||||
params: { id: service.id },
|
params: { id: service.id },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 鼠标悬浮
|
||||||
handleCardHover(serviceId) {
|
handleCardHover(serviceId) {
|
||||||
this.hoveredCard = serviceId
|
this.hoveredCard = serviceId
|
||||||
},
|
},
|
||||||
|
// 鼠标离开
|
||||||
handleCardLeave() {
|
handleCardLeave() {
|
||||||
this.hoveredCard = null
|
this.hoveredCard = null
|
||||||
},
|
},
|
||||||
|
|
@ -210,10 +151,21 @@ export default {
|
||||||
// 获取产品列表
|
// 获取产品列表
|
||||||
async getProductCenterListInScreenFun() {
|
async getProductCenterListInScreenFun() {
|
||||||
const res = await getProductCenterListAPI({})
|
const res = await getProductCenterListAPI({})
|
||||||
|
|
||||||
console.log(res, 'res')
|
|
||||||
this.productListAll = res.data
|
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 {
|
.services-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,26 +379,45 @@ export default {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border: 1px solid transparent;
|
border: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: #4a90e2;
|
// background-color: #4a90e2;
|
||||||
color: white;
|
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 {
|
.btn-primary:hover {
|
||||||
background-color: #357abd;
|
// background-color: #357abd;
|
||||||
border-color: #357abd;
|
// border-color: #357abd;
|
||||||
|
background: linear-gradient(180deg, #00c7ef 0%, #005eef 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #4a90e2;
|
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 {
|
.btn-outline:hover {
|
||||||
|
|
@ -475,6 +446,12 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.services-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
.services-grid {
|
.services-grid {
|
||||||
grid-template-columns: 1fr;
|
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="section product-intro">
|
||||||
<div class="intro-content">
|
<div class="intro-content">
|
||||||
<div class="intro-image">
|
<div class="intro-image">
|
||||||
<img :src="apiBase + intro" alt="产品介绍" />
|
<img :src="productDetail.linkImage" alt="产品介绍" />
|
||||||
</div>
|
</div>
|
||||||
<div class="intro-text">
|
<div class="intro-text">
|
||||||
<div class="intro-header">
|
<div class="intro-header">
|
||||||
|
|
@ -25,171 +25,33 @@
|
||||||
<button class="btn-primary">访问演示</button>
|
<button class="btn-primary">访问演示</button>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
基于移动互联网(微信、APP),整合所有数据资源(在线订餐、充值、点评),由内到外的为学校师生提供便民服务,深度定制餐厅、校
|
{{ productDetail.introduction }}
|
||||||
本文档精准、适用于智慧食堂数据资源,通过设备、消费设备、自助充值、智慧收银、零售管理、多样化计
|
|
||||||
方式),致力提升学校整体服务和管理的能力,打造名副其实与时俱进的互联网餐厅。
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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"
|
|
||||||
/>
|
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
|
<CardContainer cardTitle="宣传手册" :cardList="productBrochures" />
|
||||||
|
<CardContainer
|
||||||
|
cardTitle="宣传视频"
|
||||||
|
:cardList="productVideos"
|
||||||
|
v-if="productVideos.length > 0"
|
||||||
|
/>
|
||||||
|
<CaseContainer cardTitle="产品案例" :cardList="productCases" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 引入导航栏组件
|
import CardContainer from './components/card-container'
|
||||||
import NavBar from '@/views/psp/navBar.vue'
|
import CaseContainer from './components/case-container'
|
||||||
|
import { getProductCenterDetailAPI } from '@/api/publicService/productCenter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductDetail',
|
name: 'ProductDetail',
|
||||||
components: {
|
components: {
|
||||||
NavBar,
|
CardContainer,
|
||||||
|
CaseContainer,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -304,13 +166,18 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
animationFrame: null,
|
animationFrame: null,
|
||||||
|
|
||||||
|
productDetail: {}, // 产品详情
|
||||||
|
productBrochures: [], // 产品宣传手册
|
||||||
|
productVideos: [], // 产品宣传视频
|
||||||
|
productCases: [], // 产品案例
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.productId = this.$route.params.id
|
// this.productId = this.$route.params.id
|
||||||
console.log('产品ID:', this.productId)
|
// console.log('产品ID:', this.productId)
|
||||||
|
|
||||||
console.log('产品详情:', this.$route)
|
this.getProductCenterDetailInScreenFun(this.$route.params?.id)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
maxBrochureIndex() {
|
maxBrochureIndex() {
|
||||||
|
|
@ -360,6 +227,41 @@ export default {
|
||||||
window.removeEventListener('resize', this.handleResize)
|
window.removeEventListener('resize', this.handleResize)
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
handleNavChange(navKey) {
|
||||||
this.activeNav = navKey
|
this.activeNav = navKey
|
||||||
// 可以根据导航项做页面跳转或其他操作
|
// 可以根据导航项做页面跳转或其他操作
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue