第一步:创建store目录结构
src/
├── store/
│ ├── modules/
│ │ └── robot.js # 机器人专用状态模块
│ └── index.js # Vuex 主配置文件
第二步:创建机器人状态模块
创建 src/store/modules/robot.js 文件,内容如下:
// 从本地存储读取初始位置(实现持久化)
const getInitialPosition = () => {
try {
return JSON.parse(localStorage.getItem('robotPosition')) || { x: 100, y: 100 }
} catch {
return { x: 100, y: 100 }
}
}
export default {
namespaced: true, // 启用命名空间
state: () => ({
position: getInitialPosition(),
visibility: true
}),
mutations: {
UPDATE_POSITION(state, newPos) {
state.position = newPos
// 同步到本地存储
localStorage.setItem('robotPosition', JSON.stringify(newPos))
},
TOGGLE_VISIBILITY(state) {
state.visibility = !state.visibility
}
},
actions: {
setPosition({ commit }, position) {
commit('UPDATE_POSITION', position)
},
resetPosition({ commit }) {
commit('UPDATE_POSITION', getInitialPosition())
}
},
getters: {
formattedPosition: state => {
return `X: ${state.position.x}px, Y: ${state.position.y}px`
}
}
}
第三步:配置主Store文件
修改 src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import robot from './modules/robot'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
robot // 注册机器人模块
}
})
第四步:初始化Vuex
在 main.js 中挂载 store:
import Vue from 'vue'
import App from './App.vue'
import store from './store' // 自动识别 index.js
new Vue({
store, // 注入全局 store
render: h => h(App)
}).$mount('#app')
第五步(可选):增强持久化配置
如果要实现更复杂的持久化,可以使用 vuex-persistedstate:
1.安装插件:
npm install vuex-persistedstate
2.修改 store 配置:
// store/index.js
import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
modules: { /*...*/ },
plugins: [
createPersistedState({
paths: ['robot.position'] // 只持久化位置信息
})
]
})
第六步:创建全局机器人组件(components/GlobalRobot.vue)
<template>
<div class="global-robot" :style="robotStyle" @mousedown="startDrag" @click="opendialog">
🤖
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: 'GlobalRobot',
data() {
return {
isDragging: false,
dragOffset: { x: 0, y: 0 },
elementWidth: '',
elementHeight: ''
}
},
computed: {
...mapState('robot', ['position']),
robotStyle() {
return {
left: `${this.position.x}px`,
top: `${this.position.y}px`,
zIndex: 9999
}
}
},
methods: {
...mapActions('robot', ['setPosition']),
startDrag(e) {
this.isDragging = true
const rect = this.$el.getBoundingClientRect()
this.elementWidth = rect.width
this.elementHeight = rect.height
console.log(this.elementWidth)
console.log(this.elementHeight)
this.dragOffset = {
x: e.clientX - this.position.x,
y: e.clientY - this.position.y
}
document.addEventListener('mousemove', this.onDrag)
document.addEventListener('mouseup', this.stopDrag)
e.preventDefault()
},
onDrag(e) {
if (!this.isDragging) return
const maxX = window.innerWidth - this.elementWidth
const maxY = window.innerHeight - this.elementHeight
const newX = e.clientX - this.dragOffset.x
const newY = e.clientY - this.dragOffset.y
// 严格边界限制
this.setPosition({
x: Math.max(0, Math.min(newX, maxX)),
y: Math.max(0, Math.min(newY, maxY))
})
console.log(
`边界检测:
newX=${newX},
maxX=${maxX},
clampedX=${Math.max(0, Math.min(newX, maxX))}`
)
},
stopDrag() {
this.isDragging = false
document.removeEventListener('mousemove', this.onDrag)
document.removeEventListener('mouseup', this.stopDrag)
},
opendialog() {
this.dialogVisible = true
}
}
}
</script>
<style scoped>
.global-robot {
position: fixed;
width: 100px;
height: 100px;
/* background-image: linear-gradient(92deg, #407cd6 15%, #3ebcb4 48.8525390625%, #397ace 100%); */
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
cursor: move;
user-select: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition:
transform 0.2s,
box-shadow 0.2s;
pointer-events: auto;
/* 确保可交互 */
}
.global-robot:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
}
.global-robot:active {
cursor: grabbing;
transform: scale(0.95);
}
</style>